Libertas data model reference
This document describes the language-neutral data model emitted by the Libertas TypeScript and Rust parsers and consumed by Libertas Studio and the Hub. It is a reference for the generated artifacts and their invariants, not a source-language annotation manual.
This reference defines generated artifact shape, identity, compatibility, and validation guarantees. Source annotations and the language manuals define authoring syntax; linked topic references own expanded behavior. Generate schemas through Studio rather than editing them.
Language extraction resolves native declarations. Studio validates the resulting schema against its default document and exact materialized dependencies. Source locations accompany diagnostics, but transient parser metadata is absent from final published artifacts.
Libertas Schema Path
A Libertas Schema Path addresses a node in a schema-described value tree. Schema compilation resolves the declared structure and checks the path in its usage context; runtime evaluation locates the corresponding value. Fields use case-sensitive Native Names, not localized Display Names. Named type references and inheritance are resolved without introducing extra value-tree levels. A Libertas Schema Path is distinct from a package/type fully qualified name, a protocol capability prefix, or a filesystem path. The attribute or expression that contains it determines its root, allowed syntax, cardinality, and treatment of unavailable values.
A reference context is the lookup environment supplied by an operation. Its reference objects are schema-described values in the defined source order. A reference path selects a value within one such object; the term does not imply that the value is a record. The context table below specifies each operation's sources. The interaction reference-context rule owns their membership, order, and lifetime. An operation-specific copying source does not implicitly replace the interaction's reference context.
Relationship to JSONPath
Libertas Schema Path uses familiar JSONPath ideas, including $ for a root and
named child selection. It is a schema-aware addressing facility, not an
implementation of the full JSONPath query language. Standard JSONPath defines
@ within filter evaluation and also provides selectors such as wildcards,
slices, filters, and descendant queries. Libertas uses its own context rules,
including host- or calculation-root-relative @, parent ^, the
reference-context marker &, and typed-ancestor selection where
specified below. Those Libertas forms do not imply support for general JSONPath
queries; the descendant combinator of Display Template selectors
exists only in that context. See JSONPath, RFC 9535.
Shared CopyFrom and CopyTo rules
CopyFrom and CopyTo describe a directional, typed mapping between one source node and one destination node. CopyFrom identifies the source for the annotated receiving node; CopyTo identifies the destination for the annotated source node. Either node may be a primitive or a composite value. Copying a Table or an array, including a reduced projection, remains one mapping to the selected node.
For each annotated receiving node, CopyFrom visits reference objects in context order. Within each object, it evaluates the comma-delimited alternative paths in declaration order. It skips absent/undefined values and stops at the first available value, including valid null. Source order takes priority over path order: exhaust the paths in the first object before trying any path in the next. Calculation CopyFrom instead names each alternative's source and tries its alternatives in declaration order under the context table. In a subscription context, therefore, all alternatives inspect the most recent successful correlated Response first, when present, then the current SubscriptionData snapshot. The interaction context rule owns which values occupy those positions.
Selection is independent for each annotated receiving node. A selected composite value is projected from that one source; do not merge reference objects or fill its absent members by searching another object. With no reference objects or no available path, CopyFrom performs no copy. Ordinary request construction then uses the normal initialization rules below. A required calculation mapping with no available input instead blocks that calculation. The operation supplies the context; it does not redefine path matching or field projection.
An explicit path may address a node at a different nesting depth or with a different Native Name. Type or Protocol Variant qualifiers identify the permitted schema context; a compatible type alone does not identify the intended value. Within a copied Table, corresponding members retain exact, case-sensitive Native Name matching. Selecting a differently located Table does not implicitly rename its members; such a difference requires an explicit mapping of the relevant node where permitted, or a payload with corresponding member names.
Both CopyFrom and CopyTo use the same reduced-field projection rules. Match record fields by exact, case-sensitive Native Name and recurse through records and array elements. Source-only fields absent from the destination type MUST be ignored without a copy error; they are not assignments and do not participate in destination compatibility, overlap, or protection checks. Matching fields retain the applicable type, nullability, semantic scalar, and Union identity requirements. This does not make a required destination field optional or permit an incompatible matching field. A valid null assigns null to a nullable destination; it does not mean unchanged. A received CopyTo payload must remain valid under its own schema. CopyFrom may project an unfinished editor under the destination's completeness rules.
Design rationale: structural matching by field name. Because copied records
match members by Native Name and ignore source-only fields, a Rust or TypeScript
App composes values the way a scripting language does: any record with a member
of the expected name and a compatible type can supply or receive it, whatever its
declaring type. A calculation input can list @.Business.assets,@.Farm.assets,@.Rental.assets,
and each branch's rows project into the input's element type because every branch
names that member assets. A field name is therefore part of an App's interface,
not only a label. Name fields that play the same role across types deliberately
and identically, and fields with different meanings differently; one
calculation, projection or alternative list then serves every type that shares
the role, with no adapter types.
CopyFrom and CopyTo override initialization only at the nodes they actually
assign. On a newly constructed destination, every unaffected node retains its
ordinary Fixed, Default, localized default, nullable, and type initialization.
This includes destination-only fields within a copied Table or array element.
Ordinary initialization may yield a value, valid null, or undefined; it need not
make the constructed request complete. Type initialization, which applies to a
node with no Fixed, Default, or localized default, is the same on every client.
An editable Nullable node is undefined, unanswered like a Union with no
branch selected, because its editor asks a two-answer question
(Nullable choices); the reserved Default
$null starts it at null, a complete answer, and
any other Default starts it at that value. A ReadOnly, Hidden, or Fixed
Nullable node, which the person cannot answer, is null, and so is a Nullable
node of a Response or SubscriptionData payload. A Table is its fields, each
initialized by these rules; a List is empty; a Union has no selected branch.
Every other required node is undefined, which is unanswered: a String, a
number, a Boolean, an Enumeration, Bytes, a DateOnly, a TimeOnly, and a
DateTime alike. A client never supplies an empty string, zero, a range bound,
or the current time in its place, so a present value always means an answered
one. Merely supplying a reference context
does not change an unaffected node's initialization. On an existing destination,
unaffected nodes keep their current values; copying another field does not
reset them.
CopyFrom on a Union selector may supply its initial branch. Browsing or explicitly choosing another branch does not reapply that selector's CopyFrom; the chosen payload and its descendants retain their own ordinary construction, including their CopyFrom mappings and the current reference context.
Initialization and validation are separate. Studio rejects Fixed together
with CopyFrom on one field: a copied value that differed from the Fixed
value would leave a protected field that blocks submission. Within a copied
Table or array, a copied member takes precedence over
a Fixed initializer, but still has to satisfy the Fixed constraint and every
other applicable constraint. Copying does not waive destination protections
such as the calculation CopyTo prohibition on overwriting Fixed fields.
An unfinished value in a mapped calculation source is also distinct from a
destination-only field: ordinary defaults must not replace that missing input.
The calculation completeness rules
determine whether the constructed request can be sent.
The operation defines how the projected values are applied. CopyFrom constructs its destination; CopyTo merges into an existing destination. Source selection, destination uniqueness, array correspondence, and operation ownership retain their respective contracts.
A CopyTo path list expresses alternative destination shapes. For each CopyTo declaration, exactly one destination must match in the eligible bound context. Multiple matching paths, including duplicate paths to the same node, are an App design error and are rejected before any assignment; list order does not resolve that ambiguity. A report may reach several independent subscribers, each with its own unique destination. See copy path grammar. Paths do not search or broadcast across array elements: an array must be selected as one value, or an item must have an explicit index or binding permitted by the interaction. Reduced-array copying follows the typed projection rules.
Both directions require compatible receiving types and preserved operation ownership. Schema compilation checks resolvable declarations; runtime checks actual values and bindings, including any ambiguity not decidable statically. The context below determines allowed syntax, source availability, and failure handling.
Context and syntax
| Usage | Accepted form | Source or destination context |
|---|---|---|
| EnumSource; EndpointBaseObjects/BaseObjects | $, @, or one or more ^, followed by named child segments. EnumSource also accepts a relaxed ^<Type> or strict ^<Variant.field...> typed ancestor followed by named child segments |
Root, annotated host, or actual parents in that schema-data tree. For EnumSource in an Endpoint protocol Request or Response, $ is the active variant's payload; in function data it is the data root. EnumSource takes one path; BaseObjects takes a comma-delimited list. |
| LLMDocTrans validation | One $, @, or parent-relative ^ Schema Path to a Table |
The current edited Request: $ is its root, @ is the bound document struct, and ^ follows actual value-tree parents. Omission supplies no facts ({}); an unavailable declared target blocks the action. The Table's children become document validation facts. |
| Ordinary CopyFrom | Strict ^<Variant> or ^<Variant.field...> with a field/index suffix; $ root-relative shorthand also remains supported; the reference-context marker is accepted but not required: &<Variant>.path equals strict ^<Variant>.path, and &.path equals $.path; comma-delimited alternatives |
The current interaction reference context, when present, under the shared source-order rule. |
| Ordinary CopyFrom: link's bound occurrence on a Response or SubscriptionData | Relaxed ^<Type> or strict ^<Variant.field...> naming a self-or-ancestor occurrence on the tapped node's path, at any depth and across nested Lists, with a field/index suffix, on a link target |
The occurrence the tap bound in the displayed value; with no tap, unbound, so the next alternative applies. See bound occurrences. |
| CopyFrom when constructing a PrevRequest action | The same variant-rooted or root-relative copy syntax | The construction context contains the current edited request as its reference object. |
| Display Template leading-page loader CopyFrom | The same variant-rooted or root-relative copy syntax | Initial load uses the current complete editing Request as the construction reference object, without changing its server-supplied interaction reference context. Subsequent refresh uses the current leading page under the ordinary rules. See master-detail panes. |
| Calculation CopyFrom/CopyTo | Relative to the calculation root: @, @.field, @[0].x or @.Branch.field for the root occurrence and its subtree (a Union branch is an explicit segment); ^, ^^, ... with a suffix for the root's parent, grandparent, ...; a relaxed ^<Type> or strict ^<Variant.field.item> with a suffix for the nearest self-or-ancestor occurrence from the root; &<Variant>.path for the reference object of that protocol variant; &.path for the first reference object, in context order, that has the path. CopyFrom takes comma-delimited alternatives of any of these forms. CopyTo takes only @ with an optional suffix. $ is rejected in both, so the request root is written ^<Variant>; & is rejected in CopyTo |
Inputs resolve their alternatives in declaration order: @, ^ and ^<Type> in the current request from the root, never in a reference object; & only in the interaction reference context, whose owner keeps membership, order and lifetime. A bare & alternative tries each reference object in context order before the next alternative is tried. CopyTo addresses the bound destination inside the root occurrence's subtree. See calculation requests. |
| Request-editor link CopyFrom and link-return CopyTo | Root-relative copy syntax, parent-relative ^/^^, relaxed ^<Type>, or strict ^<Variant.field.item> with a field/index suffix; CopyFrom also accepts & as in ordinary CopyFrom |
A link declared in a request editor binds its host field's occurrence self-inclusively, and parent-relative ^ steps from the host field. Its target's inputs use the current-editor-first lookup defined by the interaction reference-context owner; the editor and a reference object may designate the same value. Its return addresses the corresponding bound current-request destination. |
| AppendTo | Exactly one Variant.field using native names |
A root array of an admitting working SubscriptionData; see array append. |
| AppendIndex | Exactly one root-field $.field |
A non-null integer control field in the incoming append report; never copied as ordinary data. |
| ConstrainedBy | Request-field form: exactly one root-relative copy path; or one or more ^, or a relaxed ^<Type> or strict ^<Variant.field...> typed ancestor, followed by named child segments. Response form: exactly one root-relative copy path |
Request-field form: $ is the current request's selected payload; ^ steps from the constrained field to its actual value-tree parents, so each List row can carry its own allowed set. Response form: that response's payload. It is neither an interaction reference-context read nor an alternatives list. |
| CopyTo-directed reduced SubscriptionData | One or more comma-delimited strict ^<StableVariant> or ^<StableVariant.field...> destinations |
Each admitted stable working value, subject to the shared destination-uniqueness rule above. |
| ValidationRules, RangeMin, RangeMax: current-data reference | [name/child], [items/0/name], or []; an in-scope cursor can be the first segment |
The attribute owner's evaluation root or the concrete node bound to that cursor. |
| ValidationRules, RangeMin, RangeMax: external reference | {$.name} or {$.first,$.fallback}; dot-separated fields only |
The same interaction reference context in its defined source order, including editable-request and inherited rules. |
| Display Template selector | The forms and extensions of Display Template selectors; no literal index and no comma alternatives | Studio resolves it statically against the package schema; a client matches every displayed node the selector names. A :condition(...) reads the selected node and the interaction reference context. |
For host-relative schema paths, a child can use .name, [name], ["name"],
or ['name']. $ selects the current value-tree root; @ selects the host;
each ^ moves to its actual parent. An enclosing named type is not another
runtime parent. List items and selected Union variants remain value-tree levels
for these schema-data paths. For example, ^.choices selects a sibling List.
The root-relative copy grammar is:
copy-path = "$" { "." identifier | "[" index "]" }
identifier = (letter | "_" | "$") { letter | digit | "_" | "$" }
index = "0" | nonzero-digit { digit }
Here letters and digits are ASCII. An index must be a non-negative safe integer;
negative, computed, wildcard, and slice indices are not accepted. Quoted bracket
field names from the host-relative grammar are not part of this copy grammar.
An indexed path such as $.items[0].quantity selects one literal position.
A comma in CopyFrom separates complete alternative paths, not array selectors.
Curly validation references use the dot-only form: their entries do not accept
[index], even though ordinary CopyFrom does.
Strict variant-rooted copy paths use ^<Variant> to select the active variant's
payload and ^<Variant.field...> to select a schema descendant. Every schema
level, including a selected nested Union branch, must be explicit. Ordinary
field and literal index segments may follow the selector, for example
^<Variant.items>[0].value. A path rooted in a different active variant does
not match. $ is shorthand for the reference object's root where the context
table permits it.
Typed ancestors and bound occurrences
Calculation paths are relative to the
calculation root occurrence: @ selects the
root occurrence, ^ its parent, ^^ its grandparent, and further parent
steps, each followed by ordinary field/index segments. Calculation CopyFrom also
accepts two selector forms:
^<EditOrder.items.item>.subtotalfollows a strict, top-down Libertas Schema Path. The first name is the active protocol variant's native name; each following name is an immediate schema child, in order. Include the array element's schema native name (for exampleitem) and any selected nested Union branch. These names identify schema occurrences, not numeric array positions. No intermediate node may be skipped.^<EditOrder.items>selects the array.^<OrderItem>.subtotalis relaxed: resolve the named schema type within the active variant's reachable schema, then select its nearest enclosing value occurrence. Intermediate schema levels need not be written. Named types use normal schema/package qualification (package::Type), resolved identity, and never display labels or structural resemblance.
See strict and relaxed typed selectors.
Both forms cover the active variant itself: ^<EditOrder> selects its entire
payload, including a primitive payload. A bare name matching the active variant
selects that root before named-type lookup (a
Display Template selector instead checks the
collision statically and accepts it only when the type occurs solely as that
variant's payload); qualification can disambiguate a
named type. Strict paths must start at the variant, not an arbitrary package
root type. Within a selected source tree, neither form can select a sibling
value, another active variant, or climb into the enclosing protocol Union. Missing bindings are unavailable, not
null; unknown or ambiguous type names and invalid Libertas Schema Paths fail validation.
Resolution is grounded in a top-down traversal from the active variant. Each
operation binds from one node. A calculation binds self-or-ancestor from its
root occurrence, so a typed selector naming the root's own type selects the
root itself. A link binds self-or-ancestor from its
invoking node, the tapped node or the host field, so ^<TopicRow> on a link
declared by TopicRow selects that row. EnumSource and request-field
ConstrainedBy bind only occurrences on the host field's ancestry, starting at
its parent and including the variant root; the variant root remains eligible
when it is the host, which makes primitive variant roots addressable. A strict
path pins the schema occurrence; relaxed matching selects the nearest enclosing occurrence of
its named type. The selector may omit a suffix to select the bound value itself;
otherwise suffixes use ordinary fields and literal zero-based array indices.
$ still selects the complete current request where the context table admits
it; calculation paths reject it and write the request root as ^<Variant>.
Ordinary CopyFrom uses each reference object in context order as a candidate
variant root. Calculation @, ^ and typed selectors read only the current
request, never a reference object; a calculation reads the interaction
reference context only through & paths, under
calculation requests. Calculation
bindings do not permit CalculationRequest on protocol variants. The two logical
contexts do not require separate stored copies.
EnumSource and request-field ConstrainedBy accept both selectors, followed only
by named field segments, and bind them from the host field, starting at its
parent. A field's schema ancestry is static, so Studio resolves
the selector to the equivalent parent-relative ^ path, counted in the value-tree
levels the context table gives that attribute, and the finalized schema carries
that path. Every occurrence of the host must resolve to the
same path; otherwise validation fails.
Ordinary CopyFrom accepts a bound occurrence only on the target of a
link on a Response or SubscriptionData, whose invoking
node is the tapped node. The selector names the nearest self-or-ancestor
occurrence of its type on the tapped node's path, at any depth and across
nested Lists; a tap pins the whole path, so a type held in several places or in
nested Lists is not ambiguous. The relaxed ^<TopicRow>.section names that
type; a name matching a protocol variant selects that variant instead. In the
strict ^<Hub.topics.topic>.section, the first name is the displayed variant
and each following name is an immediate schema child down to the bound
occurrence, including List element names. Real schemas use the relaxed form
today: both parsers emit an empty native name for a List element (the same
holds for calculation selectors), so the strict form becomes usable once they
emit element names. The displayed value remains the reference object; the
binding only selects which occurrence the selector denotes. With no tap, from
Actions, a menu, automatic selection, or any other entry, the selector is
unbound, which is unavailable rather than null, and CopyFrom continues with the
next alternative. Parent-relative ^ remains limited to calculations and
request-editor links, and a relaxed ^<Type> in ordinary CopyFrom denotes only
this bound occurrence. A link in a request editor binds from the host field,
self-inclusively, under the context table.
Bindings belong to the operation. A later reply cannot retarget another item because it occupies the same index; different root occurrences remain distinct calculation instances, and different link invocations distinct constructions. The calculation contract owns scheduling, invalidation, atomic assignment, and deferred re-execution.
Current-data references in validation expressions
Square-bracket references use slash-separated field names and non-negative
integer array positions. [] selects the owner root. [item] selects an
in-scope traversal cursor's concrete node; [item/value] continues from it.
Otherwise the first segment begins at the evaluation root. For example,
[items/0/name] accesses one element's field.
A leading run of ^ ascends from the annotated value before descent:
[^/minimum] reads minimum in its enclosing value and
[^^/limits/minimum] ascends twice before reading limits/minimum.
[^] selects the parent itself. This binds the concrete current occurrence,
including its actual List item and selected Union payload; named type
references and inheritance add no parent levels. It uses the same value-tree
levels as parent-relative Libertas Schema Paths. The prefix is permitted only
at the beginning, starts at the expression owner even inside a traversal, and
cannot climb above the current data root or into the enclosing protocol Union.
Studio checks parent-sensitive paths at every occurrence of a reusable type;
an invalid climb is an error rather than an unavailable value.
If a path exists, it resolves to its value; otherwise, it resolves to null. Resolve segments over actual values in source order:
- A named segment selects a child of each current non-array node.
- At an array, a named segment fans out to each element, applying the same unconsumed segment to each one.
- An integer segment selects that position from each current array.
- Results retain parent-path order followed by array-element order.
Multiple results form an ordered collection. Collection-aware operations consume collections; scalar-only operators reject them. A traversal source must select terminal arrays; their elements are concatenated in path order for cursor binding. A cursor preserves its concrete absolute path for diagnostics. Union access follows the active payload without a special variant selector: an absent field in the active variant resolves to null. This is expression resolution, not an instruction to copy or mutate data.
The expression forms use these productions (with the same ASCII identifier meaning as above):
current_data_schema_path = "[" [ ( parents [ "/" segment { "/" segment } ] )
| ( segment { "/" segment } ) ] "]"
parents = "^" { "^" }
segment = identifier | digit { digit }
reference_object_schema_path = "{" dot_path { "," dot_path } "}"
dot_path = "$" { "." identifier }
Surrounding whitespace in slash-separated segments and comma-separated references is ignored. Neither form evaluates an index expression. Curly references select ordered alternatives; square references follow current data.
Availability, null, and interaction ownership
Reference-context lookup is a shared schema reference capability. Any schema attribute whose semantics support reading a referenced value may use this context when its contract permits it. Each attribute defines its accepted path or expression syntax, expected value type, and handling of an unavailable reference; this capability does not make every attribute accept a path. The interaction reference-context rule owns the server-supplied context and its replacement. A reference lookup never performs an implicit server fetch or substitutes task parameters, sibling request values, task-upgrade data, pooled endpoint data, or another interaction's reference context.
Curly validation references visit reference objects in the same context order. Within each object, they inspect comma-delimited paths in declaration order and return the first non-null value. Missing, undefined, and null alternatives are skipped, continuing into later objects when necessary; no match yields null. They are pure reads. Unlike CopyFrom, a valid null does not stop this lookup. With no reference context, including first-time composition, every such read yields null; authors must handle that absence. CopyFrom follows the distinct copy-selection and initialization rules. Studio checks their syntax but treats their result type as schema-unknown and nullable because server-supplied reference-object schemas may differ.
Editable-request rules use the interaction's current reference context through curly references. Function rules see the argument Table; a field/type rule is rooted at its owner. A required field can be temporarily unanswered locally, but a valid path expression does not make it optional or permit an incomplete transmitted request. EditRequest construction follows the interaction reference-context rule.
A syntactically valid path does not grant permission, create a missing value, prove a runtime index exists, or transfer ownership of a pending reply. The containing attribute defines assignment, validation, and lifecycle behavior. ConstrainedBy, EnumSource, BaseObjects, and calculation rules retain those operation-specific requirements in their owning sections.
Display Template selectors
A Display Template rule selects the nodes it styles with one selector. Selectors reuse the Libertas Schema Path forms, and Studio validates each one against the package schema exactly as it validates CopyFrom paths. A template is not evaluated inside one active interaction, so a selector names schema occurrences: at runtime it matches every displayed node that is such an occurrence, and it never binds a single value.
| Form | Selects | Rank in the cascade |
|---|---|---|
^<Variant> |
The whole value of a variant of an Endpoint protocol Union or a function data Union: a protocol screen or a stored record. | Strict |
^<Variant.field...> |
One schema occurrence, explicit at every level including selected Union branches, with the strict-path rules. | Strict |
^<function>, ^<function.parameter...> |
An App function's configuration editor, the synthetic Table of its arguments, or one occurrence inside it. | Strict |
$.field... |
Root-relative in whichever variant or configuration is displayed. | Strict |
^<Type>, ^<Type>.field... |
Every occurrence of a named type, or that field in every occurrence. Named types use package::Type qualification; a Rust crate::Type maps to it. |
Relaxed |
Template selectors add these extensions, valid only in a template:
| Extension | Meaning |
|---|---|
[] after a List |
Every member of that List, as in ^<Hub.topics>[], optionally followed by field segments. Parsers emit an empty native name for a List element, so the strict element form cannot be written; [] stands for it. |
... descendant combinator |
Every node beneath the selected one, as in ^<Review> .... A selector using it ranks as relaxed. |
:pending, :error, :selected |
A runtime state that no data expresses, as the conditions define. Style only; never on layout. |
:condition(expression) |
A data condition: a Boolean ValidationRules expression rooted at the selected node, with [] the node, [status] a child, and curly references reading the interaction reference context. |
selector = base [ " ..." ] [ state ]
base = "^<" name { "." name } ">" { suffix } | "$" suffix { suffix }
suffix = "." identifier | "[]"
state = ":pending" | ":error" | ":selected" | ":condition(" expression ")"
name is a native name, or a qualified package::Type in the relaxed form. A
state applies to each selected node. Literal indices, comma-delimited
alternatives, and parent-relative ^ steps are not selector syntax.
Name collisions follow the rule for typed ancestors:
a bare name in ^<...> that matches a variant selects the variant, and a named
type with the same name is written qualified, package::Type. Because a
template rule is not evaluated inside one active variant, Studio checks
collisions statically across every protocol and data Union of the App and its
functions. A bare ^<Name> that matches both a protocol variant and a named
type is accepted when that type occurs in the protocol only as that variant's
payload, since the variant and the type then denote the same node; whenever the
type occurs anywhere else, the name is rejected and the diagnostic names the
qualified form. A bare name that matches both a function and a named type, or
more than one variant or function, is likewise rejected.
1. Artifact model
A Libertas App version has two related, independently useful JSON artifacts:
- The schema package is the machine contract. It describes callable functions, a graph of named and inline types, constraints, protocol roles, cross-package identities, and revision metadata.
- The package document is the localized presentation layer. It describes package, function, type, field, enumeration, and string-resource text without changing wire identity or field order.
Each package also has one locale-independent Display Template, which decides how the schema's values are arranged and styled on a device. It is not part of the schema: it is absent from the schema JSON and its revision digest, and it is published and versioned as a document.
The Hub assigns an application version when deployed code or schema changes. It
captures one UTC timestamp and encodes the version as
yyyyMMdd.HHmmss.milliseconds.counter: major is the UTC date, minor is the
UTC time, build is the 0..999 millisecond component within that second, and
revision is a counter that distinguishes versions created during the same UTC
minute. A document-only update preserves the application version and
independently versions the changed document metadata.
The normal generation pipeline is:
TypeScript or Rust source
-> language parser and external-reference discovery
-> expanded compiler graph for validation
-> ownership pruning and FQN reference rewriting
-> language-neutral local schema with AppDependencies=[] + default-locale document
-> Studio dependency-first deployment and authoritative Hub version read-back
-> complete flattened AppDependencies list
-> deterministic schema sorting and revision digest
-> document normalization/localization and Display Template compilation
-> deployment as schema + documents (including the template) + code
The separation is deliberate. NativeName remains a stable programmatic name in
the schema, while localized DisplayName, field Name, Description, and
package StringResources values live in documents. Translating presentation text therefore does not
break type references or other schema links.
2. Schema package
The shared in-memory contract is:
interface QWHAScriptPackageJson {
AppDependencies: QWHAAppDependencyJson[];
Types: QWHATypeInfoJson[];
Functions: QWHAScriptSignatureJson[];
Attributes: QWHAScriptAttributeJson[];
Revision?: QWHASchemaRevisionJson;
}
interface QWHAAppDependencyJson {
package: string; // canonical developer-package identity
Version: string; // required exact yyyyMMdd.HHmmss.mmm.counter pin
}
interface QWHAScriptSignatureJson {
NativeName: string;
Parameters: QWHATypeInfoJson[];
Attributes: QWHAScriptAttributeJson[];
}
interface QWHATypeInfoJson {
ID: number;
NativeName: string;
Type: string;
BuiltinType: boolean;
Children: QWHATypeInfoJson[];
Attributes: QWHAScriptAttributeJson[];
Heritages?: string[];
NativeType?: string;
ForeignSchema?: string; // parser/normalization carrier
Namespace?: string;
isNullableFlag?: boolean; // parser/normalization carrier
}
interface QWHAScriptAttributeJson {
Name: string;
Value: string;
}
interface QWHASchemaRevisionJson {
SourceTimestampUtcMs: number;
ContentSha256: string;
}
AppDependencies, Types, Functions, and Attributes are always arrays in a
normalized schema. The language parsers emit AppDependencies: [] and initially
omit Revision; Studio finalizes both deployment-owned fields before submitting
the schema.
2.1 Top-level collections
| Field | Meaning |
|---|---|
AppDependencies |
Studio-generated complete flattened logical App dependency lock. Each entry carries a canonical package identity and the exact Hub App version whose schema was used for this deployment. Version is required and identifies that exact version. |
Types |
Named types whose declarations are owned by this App. A type declared by another package is never copied here, even when it is imported, used, or re-exported locally. |
Functions |
Exported callable functions that are not explicitly ignored. Functions are identified by NativeName; parameters are positional and retain source order. |
Attributes |
Package-wide string key/value metadata. |
Revision |
Studio-owned source timestamp and canonical content digest. It is not part of the schema content being hashed. |
The generated function model contains parameters but no return-schema field. Request/response flows are modeled through endpoint protocol unions and persistent function data through a function data-schema union.
2.2 Deployment dependency list
AppDependencies records the complete flattened set of exact App-version pins.
The required Version key means exact identity, never a lower bound.
The deployment dependency contract owns source discovery,
preflight, installation order, read-back verification, and failure behavior.
A dependency entry contains only package and Version. Missing, null,
malformed, noncanonical, or unknown version fields are rejected, as are duplicate
package entries even when they name the same version. These requirements apply
to every new installation and save. The Hub can still read already-retained
schemas using minimumVersion as the same exact pin, or predating the
AppDependencies collection. This compatibility path never writes legacy data.
To save an updated schema, regenerate and redeploy it in dependency-first order.
Do not rename fields in retained artifacts: the original bytes and revision
digest are immutable. The new schema receives a new revision and deployed App
version.
Android and iOS retain legacy-Hub operation through their separate legacy protocol and schema adapters. That path does not relax modern dependency validation. Studio does not support legacy Hubs.
2.3 Type-node fields
| Field | Meaning and invariant |
|---|---|
ID |
A parser-assigned structural ID used to associate schema nodes with localized field documents. It is local to a named-type tree or function parameter tree, not a package-global type identity. |
NativeName |
The source-level simple name of a type, field, parameter, union variant, or enum member. An unnamed list element may use an empty string. |
Type |
For a built-in node, the Libertas runtime type. For a reference node, either the simple name of a locally owned type in Types or the canonical FQN of a type owned by another package. |
BuiltinType |
true when the node directly describes a Libertas runtime type; false when Type is a named-type reference resolved locally or through the FQN package resource graph. |
Children |
Ordered structural children. Child order is wire-significant for tables, lists, and unions. Named references normally have no inline children. |
Attributes |
String-valued constraints, roles, UI hints, identity metadata, and parser-generated markers. Marker attributes use Value: "true". |
Heritages |
Optional ordered list of base type references. A local base uses its simple name; an externally owned base uses its canonical FQN. Consumers merge inherited fields without replacing same-named fields declared by the derived type. |
NativeType |
Native-language type metadata. The Rust parser records concrete types such as u16; the TypeScript parser normally emits an empty string. Shared validation initializes a missing value to "". Wire behavior is defined by Type and wire-affecting attributes, not by this field. |
Namespace |
Legacy normalization carrier for older expanded schemas. New external references carry the complete package identity directly in Type; local nodes normally use "". |
ForeignSchema, isNullableFlag, and Rust's EnumValues may exist transiently
inside a parser:
- Shared normalization converts
ForeignSchematoNamespaceand removes it. isNullableFlagis converted toNullable=trueand removed.EnumValuesis consumed while constructing/normalizing an inline enumeration and removed by the Rust parser.
The Rust parser resolves and extracts its complete native source graph. Studio materializes required FQN dependencies before running shared semantic validation, so individual parsers do not need a parallel local-only implementation of rules decidable from the emitted graph.
2.4 Runtime types
The parsers reduce source-language types to the following Libertas runtime model:
| Category | Type values |
Children |
|---|---|---|
| Primitive | Nil, Boolean, Number, String, Bytes |
Empty. Numeric width and bounds are attributes rather than distinct Type values. |
| Composite | Table, List, Enumeration, Union |
Table fields, one list element schema, enum members, or union variants in wire order. |
| Semantic scalar | DateTime, TimeOnly, User, Device, LanDevice, VirtualDevice, Endpoint |
Empty. The semantic type selects platform validation and UI behavior while retaining its runtime scalar representation. |
| Reserved placeholder | Action |
Unsupported. Its enum slot is retained for numbering stability; it has no authoring alias, picker, value codec, or invocation API. |
System identity scalars (User, Device, LanDevice, VirtualDevice, and
Endpoint) use signed 64-bit IDs: Rust i64 and TypeScript bigint. User
also represents groups. Durable allocated IDs are positive; negative IDs are
runtime dynamic identities and are not durable references. Zero and other
sentinels have only their operation-specific meanings. The semantic type
already defines the width and signedness; no NumberType attribute is needed.
Numbers use Type=Number plus NumberType. TypeScript schema annotations accept
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, and
f64; native number does not preserve this information. The Rust parser
currently maps concrete u8 through u64, i8 through i64, f32, and f64.
Rust derives the width from the native type. Integer nodes default to
NumberStep=1 in parser paths that construct integer constraints. A
cross-language contract must use a width supported by both representations.
2.5 Structural order and IDs
- Table fields, function parameters, list elements, and union variants preserve parser/source order. This order is part of the wire contract.
- IDs are assigned in structural traversal order and restart for each named type and each function's parameter forest.
- TypeScript enum-member IDs use an enum-local counter beginning at zero and do not consume the surrounding tree's sequence. The Rust parser currently applies its ordinary depth-first allocator to enum members. Cross-language tooling maps IDs through verified structure rather than assuming the two native schemas use identical enum IDs.
- An ID is meaningful together with its owning function/type document. It is not a substitute for a canonical type identity.
- Top-level sorting never sorts `Children`; it therefore cannot alter wire order.
- A document carries the corresponding node ID in
FieldDocumentJson.Type.
3. Named-type and package identity
Libertas separates native source identity from canonical wire identity.
3.1 Local named types
A local reference has BuiltinType=false and Type=<simple type name>. Lookup
matches that name in the current package's Types collection.
Every self-canonical App type must be publicly exported from the App root
index.ts or lib.rs under exactly its declaration name. Renamed exports and
duplicate flattened simple names are rejected. Qualified source-language paths
resolve declarations. If resolution finds a declaration in another package, the
emitted runtime reference is the canonical package FQN regardless of how the type
was imported or re-exported.
3.2 Foreign wire types
The canonical cross-package form is:
developer-package::TypeName
Both developer and package use lowercase snake case, separated by exactly one
hyphen. TypeName is a source-language identifier-like simple name.
For every externally owned reference, this complete FQN is stored directly in
Type, Heritages, Bitflags, ForeignTypeSchema, ProtocolSchema, or
FunctionDataSchema, depending on the reference site. Every such FQN contributes
its package to the direct dependency set used for pre-deployment Hub exploration
and the finalized AppDependencies closure. An FQN always means external ownership, so its package component
must differ from the package that owns the referencing schema; local references
use the simple type name.
See native Rust resolution. These reference FQNs are always inferred by a parser from language-native source references. An annotation may spell a qualified native reference inside a string, but that spelling must resolve through the native-language symbol graph and is never copied as the final schema identity. Every unqualified native reference, whether used in a type slot, heritage, or annotation, must resolve in the declaration's actual Rust or TypeScript scope. An imported external declaration is canonicalized to its owning App FQN; an unbound name is a parser error even when an unrelated source file or dependency declares the same terminal identifier. Parsers never use a global same-name fallback. The owning type definition and its type document remain only in the original package. Importing, using, or publicly re-exporting a type does not transfer ownership to the consuming App.
See exact native owner exports. Every resolved type outside the current package is foreign by definition. Its reference uses the native owning App FQN after verifying that the owner is a valid Libertas App and publicly exports that exact type from its root. A fabricated Bitflags catalog is the visibility exception: it may be a private exact-name declaration at the owning App root because it never becomes a runtime source-language type.
ForeignTypeSchema appears only on a published named compatible type. Its value
is the direct official type FQN from another App. The compatible type remains published under its
own App FQN; the attribute is not inherited or copied onto its fields or
references. The official target may not itself be compatible.
ForeignTypeSchema is the only source annotation whose FQN value is interpreted
literally as schema identity. Language extraction resolves native ownership;
Studio validates the emitted declaration's placement, external official target,
and structural compatibility against the materialized dependency graph.
3.3 Native compiler metadata
Final artifacts contain each local named type once and external references as
owning-package FQNs. They do not embed copied external type definitions or
transient resolution/source-location metadata. NativeType does not
independently change wire bytes. ExternalNativeType is removed; native imports
resolve declarations while serialized references keep their actual owner.
4. Attribute model
All attributes use the same { Name, Value } representation. Scope and host type
determine their meaning. The tables below list the data-model attributes
emitted or synthesized by the current parsers and shared validator. Interaction
attributes owned by another contract (DefaultRequest, LinkOnly,
ConstrainedBy, ActionControl, AppendTo, AppendIndex, CacheIdentity,
AINavigationPrompt, LLMDocTrans, and LLMDocField) are listed with their
owners in the source annotation reference; Studio rejects any
attribute name that neither lists.
4.1 Package attributes
| Attribute | Meaning |
|---|---|
DefaultLocale |
Locale of the generated default document. Defaults to en. It must be 1–128 ASCII alphanumeric characters in segments separated by one - or _; leading, trailing, and repeated separators are invalid. See the owning reference. |
StringResources |
One non-empty, unique, comma-delimited package catalog resolved from a source constant. The default package document stores matching LMF1 templates in its top-level StringResources object. Every function and type in the package uses this catalog implicitly. |
TypesOnly |
true for a schema/document package with no executable functions. Every public package-owned named type exported from the language entry root is automatically included unless ignored; Functions must be empty. See the owning reference. |
no_std |
Rust parser marker recording a crate-level #![no_std]. It is source/runtime metadata rather than a data-node constraint. |
4.2 Function attributes
| Attribute | Meaning |
|---|---|
FunctionDataSchema |
Required key=KeyUnion,value=ValueUnion binding. Both references resolve to unions; validation marks the value union LibertasData=true. |
Permissions |
On a function, comma-delimited permissions resolved from a source constant. Source order is preserved and every permission must have a same-key, non-empty reason in the package document's StringResources. |
AccessHost |
One or more comma-delimited network names required by the function's task process. Normal entries are DNS hostnames; {network_access_local} and {network_access_external} are reserved broad grants. Every entry is a package StringResources key with a non-empty localized access explanation. See App network access. |
Singleton |
Exact marker value true. At most one task for this App function may exist on a Hub. |
DataErrorStatus |
Exact marker value true, valid only on a function. It opts the function's task into checked Data API status returns; omission keeps Hub-owned termination on a data failure. See the Data API failure contract. |
ValidationRules |
Pure boolean expressions evaluated against one synthetic Table containing all function arguments. Each expression is followed by :, a quoted package StringResources error-message key, and ;. |
4.3 Value, constraint, and presentation attributes
| Attribute | Meaning |
|---|---|
Nullable |
The value may be absent/null. Optional source types normalize to this marker. |
Fixed |
The node has a fixed encoded value. Enumeration members use it for their numeric values; union wire identities use Discriminator. On an Endpoint client it instead carries a task-process FQN, developer-package::function, which constrains otherwise schema-compatible servers and forces that package into AppDependencies; the Endpoint-ID choice remains editable. See the owning reference. |
Default |
Initial value. On a Nullable field the reserved value $null (Rust #[libertas_default($null)]) declares null as the default for every type, including DateOnly, and skips the type's value grammar; the text None and any other value are ordinary values, checked as for a non-Nullable field, and $null on a field that is not Nullable is rejected. Enumeration defaults follow the shared enumeration contract. On a Union field it names, by native name, the variant that initialization selects; that variant's payload initializes as an ordinary selection, and Studio rejects a name that is not one of the union's variants. User and List<User> values follow User and group defaults. On an Endpoint client it instead names the task process recommended for provisioning when no schema-compatible server exists; it does not filter existing candidates and the Endpoint-ID choice remains editable. |
NumberType |
Numeric width/signedness (u*, i*, or f*). |
NumberMin, NumberMax, NumberStep |
Numeric bounds and step on Number or DateTime. DateTime is implicitly an unsigned 64-bit integer of Unix epoch seconds; an explicit NumberType must be u64. Step is measured from NumberMin, or zero when no minimum exists. |
RangeMin, RangeMax |
One Data Validation bound expression compatible with the host type on a Number or DateTime; applies inclusively to the GUI and final validation. Result typing, compatible dates, and unavailable bounds follow dynamic numeric ranges. |
LengthMin, LengthMax |
Retained UTF-8 byte-length bounds from the removed TypeScript String annotation, using the same String size unit as SizeMin/SizeMax. Current TypeScript and Rust source uses SizeMin/SizeMax. |
MinLengthAbs, MaxLengthAbs |
Parser-generated absolute native string constraints, currently used by the Rust char mapping. These count Unicode scalar values and remain separate from String byte-size bounds. |
SizeMin, SizeMax |
Inclusive size bounds for List element count, String UTF-8 byte count, or ordinary Bytes byte count. String size is the number of bytes in its UTF-8 encoding, counted identically on every platform without normalizing the text. MIME-aware Bytes use content length only when media is defined. Nullable null is governed separately by Nullable. Table is not a valid host. |
Pattern |
A portable full-string regex on an effective String field, list element, or named String type, including nullable String. Field overrides use the ordinary effective-attribute resolver. Studio checks placement, syntax, and effective Default/Fixed values; clients enforce committed edits and submission while retaining unfinished draft text. See full-string patterns. |
Unique |
Requires a scalar value to be unique within the requested number of ancestor-list scopes. The no-argument default level is 99. |
Unordered |
Declares list ordering semantically irrelevant to management/UI behavior. |
AskOnAdd |
Marker value true: the person's Add asks this List-member value first, and adds no member without it. Only the person's Add; other writers are unaffected. See add-time values. |
ReadOnly |
Value is displayed but not user-editable. Initialization, CopyFrom, EditRequest, calculation or link-return CopyTo and draft restoration still write it; only Fixed protects a value. See the ReadOnly rule. |
Hidden |
Value is hidden from ordinary editors. On a chart carrier table, composition, or chart-bearing Union variant wrapper, it also marks direct non-chart fields as preserved transaction context that renderers exclude from chart projection. |
SuppressLabel |
Parser result only; compiled into the Display Template. Presentation marker suppressing the visual row label of a scalar, Table, or Union; on a List of Tables it suppresses only List-contributed member names and positions (label-free value rows). |
Link |
One non-empty target variant native name of the same Endpoint protocol Union, on any field or Table, Union, or Enumeration type declaration; the field's complete declaration wins over its type's. Ordinary form Target invokes a Request. Optional Boolean source argument stacked defaults to false: omission or false emits exactly Target; true emits exactly Target,stacked=true. The upcoming stacked form invokes a LinkOnly, fully filled input-free Request or SubscriptionRequest and displays a Response above the live sending surface under links; see rendering. |
InlineChoices |
Parser result only; compiled into the Display Template. Exact marker value true on an Enumeration or Union field or type declaration, never a Bitflags catalog. Clients show the permitted choices inline as radio controls (inline choices). |
Foldable |
Parser result only; compiled into the Display Template. Boolean initially_open on an effective Table, Union, or List; presence permits manual folding. |
UIIncludeDescription |
Parser result only; compiled into the Display Template. Exact marker value true. Clients render the node's localized long Description inline together with its short DisplayName; it changes presentation only, not the typed value or wire encoding. See the owning reference. |
TimeInterval |
Displays a numeric value as a duration. It does not create a distinct wire type. |
DateOnly |
Exact marker value true on an integer Number. The stored and wire value is a valid Gregorian calendar date in decimal yyyyMMdd form; clients present a date chooser and localized calendar-date labels without applying a timezone. |
Bitflags |
true on a fabricated Enumeration catalog, or that catalog's local type name/external App FQN on an integer Number. Catalog members have explicit Fixed values. Clients display selected localized names comma-delimited and edit them with a multiple-choice chooser; the wire value remains an ordinary integer. |
Format |
Parser result only; compiled into the Display Template. One Unicode LDML display pattern on Number, DateOnly, DateTime, or TimeOnly. The effective host type selects number-pattern versus date/time-pattern grammar. It never changes storage, wire encoding, validation, or comparison. See the owning reference. |
Money |
Currency/scale metadata such as USD/2 on signed i64 Number. Raw values, bounds, and defaults are integer minor units. See Money for the exact representation, supported CLDR currency tokens, inline editing, and chart rules. |
PhysicalUnit |
One canonical CLDR core unit identifier on a Number, including generated compounds such as millimeter-per-day. It preserves the raw unit without changing serialization; a client localizes the unit when its ICU implementation supports it. |
FormattedText |
Marker value true on an effective Bytes host; serialization is unchanged. Follow the formatted-output contract for mandatory static ReadOnly, permitted request initialization, and runtime-use restrictions, and LMF1 for the canonical tuple. |
MimeTypes |
Comma-delimited exact media types or ranges on an effective Bytes host. It restricts the value domain and uses the LMB1 MIME-aware Bytes encoding. It cannot coexist with FormattedText or occur within a CacheIdentity field. |
ProtocolVariant |
One protocol variant native name on an ordinary Enumeration member; see enum/protocol associations. |
EnumSource |
One Libertas Schema Path to a List; an unsigned integer host stores the selected item's zero-based index, or, when the List's member declares a Key field, a host of the key's type stores that member's key (EnumSource keys). A List of that type stores several distinct chosen indices or keys (EnumSource List hosts). |
Key |
Exact marker true on the one non-Nullable String or integer Number field, also carrying Unique, that identifies a member of a List of Tables to an EnumSource host (EnumSource keys). |
Chart |
One primitive mark (point, line, area, bar, rect, rule, arc, text, polygon) or composition (layer, hconcat, vconcat) token on a response/subscription-data payload. A primitive may use a carrier Table with exactly one non-hidden List of rows; compositions and single-chart Union wrappers may contain additional direct non-chart fields only when those fields carry Hidden=true. |
ChartChannel |
Canonically ordered comma-delimited visual roles on a direct scalar field of a chart row. |
ChartScale |
Canonically ordered scale properties (id,type,min,max,mid,zero,reverse) for one scale-bearing chart channel. The final schema omits type, which is compiled into the Display Template's scale hint. |
ChartGuide |
Canonically ordered generic guide properties (target,source,position,title,domain,ticks,labels,grid) on a target binding or guide-label field. The final schema omits position, which is compiled into the Display Template's guide hint. |
Chart3D |
One 3D primitive (point, line, bar, surface, wireframe, stem) or composition (layer, hconcat, vconcat) token on a response/subscription-data payload. |
Chart3DChannel |
Canonically ordered comma-delimited 3D visual roles on a direct scalar field of a 3D chart row. |
Chart3DScale |
Canonically ordered scale properties (id,type,logBase,min,max,mid,zero,reverse,growMin,growMax,padding) for one scale-bearing 3D chart channel. logBase is greater than 1 and valid only for log; non-negative growMin/growMax expand automatic continuous position domains; padding is an inclusive 0..1 bar-base setting. The final schema omits type, which is compiled into the Display Template's scale hint. |
Chart3DGuide |
Canonically ordered 3D guide properties (target,source,title,domain,ticks,labels,grid,tickCount,tickStep) on a target binding or guide-label field; detail is also a guide target. |
Chart3DView |
Canonically ordered camera/projection properties (projection,azimuth,elevation,zoom,fov,orbit,aspectX,aspectY,aspectZ,targetX,targetY,targetZ,pan,zoomEnabled) on a 3D chart root. The three booleans independently gate fixed client actions; no camera/gesture mode exists. |
CalculationRequest |
Field-only comma-delimited list of distinct calculation Request variant names in the same protocol. Each participating field is a trigger of the instance of its nearest calculation root and of every other instance whose inputs read it. Its input CopyFrom mappings and participating fields must agree in both directions, except that invariant inputs need not participate. See field calculation requests for root resolution, validity, and implementation scope. |
CalculationRoot |
Comma-delimited list of distinct calculation Request variant names, under the CalculationRequest name rules, on the root node: any field, a record type, a List member type, a Union field or type, or the request's own payload type. Mandatory: every calculation Request is named by at least one. Each root occurrence has one calculation instance, and every calculation path is relative to it; nested roots are valid and the nearest wins. See the calculation root. |
CopyTo |
Declares destinations under the shared copy rules. The context table selects the grammar and destination; calculation results and reduced subscriptions define application and lifetime. |
CopyFrom |
Declares source paths on protocol Request/SubscriptionRequest data under the shared copy rules. The context table selects the reference context and grammar. |
ValidationRules |
Pure boolean expressions on any field or schema data type. Each expression is paired with a quoted package StringResources error-message key using expression : "KEY";. Field-local, inherited-field, and referenced named-type rules are cumulative. |
DeviceType |
Device-type restriction for a Device value. |
VirtualDevice |
Virtual-device-type restriction for a VirtualDevice value. |
Rows marked "parser result only" are presentation attributes. Studio validates them on the parser result, compiles every occurrence into the package's Display Template at the lowest specificity, and then removes them from the final schema, with the transient source metadata and before sorting and revision hashing. The final schema never carries them, so changing one alone never changes schema identity.
Promotion, flattening and data-titled rows belong to the Display Template. Source migration is documented under removed annotations.
The exact versioned Base64 attribute layouts used by DeviceType and
VirtualDevice are specified in
Matter device type attribute encodings.
ValidationRules is string-valued and is valid on a function, any schema data
field, and any named or inline schema data type. Function rules are rooted at a
synthetic Table of all arguments. Field/type rules are rooted at the annotated
value, and [] denotes that value itself. Square paths such as [limits/max]
read the current evaluation root. Curly paths such as
{$.limits.max,$.legacy.max} are ordered, pure reads from the optional current
interaction reference context,
including in editable Request/SubscriptionRequest trees.
These reads do not depend on CopyFrom being present. Source order, path order,
and first-non-null selection follow the
shared lookup rule.
RangeMin and RangeMax use the same field-local [] and optional
external {...} references. Their result-type requirements, date
compatibility, null handling, GUI/final enforcement, and interaction with
static constraints are defined once in
dynamic numeric ranges.
Within any validation expression, single | is the value-preserving null
fallback operator. It evaluates left to right, returns the first non-null
operand unchanged, and does not evaluate later operands. false, zero, empty
strings, empty arrays/collections, and objects are non-null winners. | binds
after +/- but before is and comparisons. It may produce any supported
intermediate type, but the complete rule must remain Boolean; non-Boolean
values require explicit comparison because there is no general truthiness.
The string may begin with FinalOnly=true; or FinalOnly=false;; omission
defaults to false. This is a leading directive within that ValidationRules
value, not a separate FinalOnly schema attribute. Only explicit true keeps
that rule set out of incremental editor validation; every rule remains included
in final validation.
Each cumulative field, inherited, or named-type rule set retains its own phase.
Every rule expression must then be followed by :, a quoted package
StringResources key, and its terminating semicolon. Studio rejects an
undeclared key or an empty default-locale message. When a rule fails, clients
display the localized value for the first failing rule's key in declaration
order. They stop at that failure without generating a structural explanation.
If its installed message is missing or blank, they display a generic validation
failure.
Studio owns grammar and every schema-decidable semantic check. It validates
square paths against the owner root and treats curly-path results as unknown
and nullable because an interaction-compatible peer may have a different
schema. Clients evaluate the rules against incomplete editor state as well as
complete data: == null and != null are legal even for a non-nullable field.
Mandatory-field validation and ValidationRules are complementary. A
null-tolerant rule may pass for an incomplete value, but it never changes the
field's Nullable contract or suppresses the final mandatory-field failure.
Clients use the same rules and semantics for mandatory incremental editor
validation and required final validation. Final submission independently
requires every static schema constraint and every validation rule to pass.
After every proposed Task Editor node change, Android and iOS evaluate all
incrementally applicable rule sets across the complete editable data. A failed
change is rolled back to the exact prior data before the client displays the
violation as an error toast. Rule sets led by an explicit FinalOnly=true;
do not participate in this
incremental transaction.
The complete language, precedence, null behavior, arrays, unions, and source
spellings are normative in the
Libertas Data Validation Rules Specification.
PhysicalUnit is emitted by exactly one positional quoted
@LibertasPhysicalUnit("identifier") or
#[libertas_physical_unit("identifier")] declaration. Its CLDR identifier
rules, provided-unit/no-conversion rule, localization behavior, chart surfaces,
and compatibility requirements are normative in
Client-side charting §5.5.1.
It is valid only on Number, follows the
exclusions of numeric semantic attributes,
and never changes raw constraints, storage, requests, grouping,
or scale geometry. Invalid CLDR identifiers and invalid placement are schema
errors. The schema accepts generated CLDR compound identifiers such as
millimeter-per-day independently of any particular client's ICU catalog. A
client localizes a supported identifier and may omit unit decoration when its
platform cannot format it; the metadata and numeric value remain intact.
Format is emitted by exactly one positional double-quoted
@LibertasFormat("pattern") or #[libertas_format("pattern")] declaration.
Its language-neutral syntax comes from the Unicode Locale Data Markup Language:
UTS #35 number format patterns
for ordinary Number, and
UTS #35 date/time format patterns
for DateOnly, DateTime, and TimeOnly. The viewing user's preferred locale
supplies localized digits, decimal/grouping signs, month and weekday names, day
periods, and time-zone names; the pattern supplies their order, width, and
literal punctuation.
See portable Format patterns.
The portable number subset supports required 0 and optional # digits,
decimal ., primary/secondary grouping ,, one positive and optional negative
subpattern separated by ;, %, ‰, scientific E, affixes, and LDML
apostrophe quoting. Currency ¤ and ¤¤ require explicit Money metadata.
Significant digit @, padding *, wider currency tokens, and rounding-increment
digits 1–9 are rejected. A pattern is at most 128 Unicode characters, 21
integer digits, and 20 fractional digits.
See portable Format patterns.
The portable date/time subset supports G, y, M, d, and E for dates;
H, h, m, s, S, and a for times; and z for a DateTime time-zone
name. Repetition follows LDML width rules, and apostrophes quote literal text.
DateOnly accepts only date fields and never acquires a timezone. TimeOnly
accepts only time fields and remains seconds from local midnight. DateTime
accepts both groups, formats epoch seconds in the viewer's local timezone, and
alone permits z. A pattern cannot combine H and h. Other LDML fields are
reserved until every Libertas presenter can implement them identically.
The following documented field fragments belong inside a documented schema type:
/**
* Invoice amount
* Shows the billed amount with two decimal places and parentheses when negative.
* @LibertasFormat("#,##0.00;(#,##0.00)")
*/
amount: number;
/**
* Service day
* Identifies the calendar day when the service occurs.
* @LibertasNumber(type=u32)
* @LibertasDateOnly
* @LibertasFormat("yyyy-MM-dd")
*/
serviceDay: number;
/**
* Last updated
* Identifies when the latest service record became available.
* @LibertasFormat("d MMMM yyyy, HH:mm:ss z")
*/
updatedAt: LibertasDateTime;
Format is valid only on the four effective hosts above and may be inherited
through a named type. On an ordinary Number it follows the
exclusions of numeric semantic attributes;
on a DateOnly Number it uses date-pattern
grammar. It may coexist with PhysicalUnit, in which case it formats only the
numeric portion and the unit remains separate localized semantic metadata.
Money uses the exact currency editor. Other editors retain native number/date/time controls and may show a formatted
preview. Renderers apply Format only at presentation boundaries—labels,
summaries, table cells, tooltips, chart ticks, and accessibility text. Parsing
user input, Avro payloads, defaults, bounds, sorting, grouping, chart geometry,
cache keys, and protocol compatibility always use the raw scalar.
Singleton is function metadata because a task instantiates a function, not a
package. The Hub identifies the function by App ID plus NativeName, independent
of the selected App version. Creating, retargeting, or changing the version of a
task is rejected when another task has the same function identity and either
selected function schema carries Singleton=true. An update may replace its own
task record. Checking both schemas prevents an older non-singleton version from
bypassing an already installed singleton declaration.
@LibertasExport remains a TypeScript source-level parser directive, but it
does not emit a schema attribute. The former Library=true marker has been
removed from the language-neutral schema; schemas that still contain it are
rejected.
DateOnly is an integer encoded as yyyyMMdd. Client GUIs present a date
chooser. For example, August 15, 2026 is stored as 20260815; the chooser may
display 2026-08-15 and serializes the selection back to 20260815.
The marker accepts no arguments,
requires an effective integer NumberType, and follows the
exclusions of numeric semantic attributes.
When Format is present, its date pattern replaces only the localized label;
the chooser and encoded value remain governed by this DateOnly contract.
Bitflags projects a localized flag catalog onto an ordinary integer. It has
two complementary forms:
- a named built-in
EnumerationcarriesBitflags=true; every child is aNumberwith one explicit non-negative decimalFixedvalue; - an effective integer
NumbercarriesBitflags=<local catalog type name>orBitflags=<developer-package::external catalog type name>.
Every nonzero catalog value must have exactly one bit set, and no two members
may have the same value. One zero member is allowed so documents can localize
the empty selection as None, N/A, or similar. The integer host must be wide
enough for the union of all declared bits. A field's Default, Fixed, task
configuration value, outgoing runtime value, and received runtime value may
contain any combination of declared nonzero bits and no undeclared bit.
See native catalog declarations.
The catalog is fabricated presentation/validation metadata and need not be a
public source-language type. A private external catalog must be declared under
its exact name at its owning App root. Parsers resolve the complete native
qualified reference and serialize the owner FQN; Studio fetches that package
from the selected Hub during deployment planning, materializes its catalog and
localized document for validation/UI, and records its authoritative version in
the final AppDependencies. Documents localize Enumeration members in the
normal way. Read-only surfaces join the selected localized names with , ;
editors use a multiple-choice chooser, one checkbox per nonzero member, and
encode the bitwise union of the choices. The zero member is never a choice:
leaving every box unchecked is the empty selection 0, and the zero label is
shown only as the read-only text of an empty selection.
{
"NativeName": "Access",
"Type": "Enumeration",
"BuiltinType": true,
"Attributes": [{ "Name": "Bitflags", "Value": "true" }],
"Children": [
{ "NativeName": "None", "Type": "Number", "Attributes": [{ "Name": "Fixed", "Value": "0" }] },
{ "NativeName": "Read", "Type": "Number", "Attributes": [{ "Name": "Fixed", "Value": "1" }] },
{ "NativeName": "Write", "Type": "Number", "Attributes": [{ "Name": "Fixed", "Value": "2" }] }
]
}
Bitflags is intentionally invisible to the Hub. It does not change Avro,
storage, protocol compatibility, or any Hub parser/runtime behavior; the field
is encoded exactly as its NumberType. Studio/shared schema validation owns
the portable catalog rules, while Studio clients own UI presentation and
runtime-mask validation. Bitflags follows the
exclusions of numeric semantic attributes.
Exclusions of numeric semantic attributes. Each of these attributes gives a Number one meaning, so two of them cannot stand on the same number unless the table permits the pair. The relation is symmetric. Studio rejects an excluded pair on the effective attributes of the field and its type.
| Attribute | Cannot coexist with |
|---|---|
DateOnly |
TimeInterval, EnumSource, PhysicalUnit, Money, Bitflags |
TimeInterval |
DateOnly, PhysicalUnit, Money, Bitflags, Format |
EnumSource |
DateOnly, PhysicalUnit, Money, Bitflags, Format |
PhysicalUnit |
DateOnly, TimeInterval, EnumSource, Money, Bitflags |
Money |
DateOnly, TimeInterval, EnumSource, PhysicalUnit, Bitflags |
Bitflags |
DateOnly, TimeInterval, EnumSource, PhysicalUnit, Money, Format |
Format on a Number |
TimeInterval, EnumSource, Bitflags |
Format may stand with DateOnly, Money, or PhysicalUnit: it then
formats the date, the amount, or the numeric portion.
FormattedText follows the static read-only placement and request-value
contract,
including for reusable and TypesOnly declarations. The concrete App that
emits values supplies its one package catalog.
Resource declarations are package-local. Exactly one package-level
StringResources attribute may declare the stable keys used by all functions
and types in that App. Its default document contains the matching top-level
StringResources map. A formatted tuple carries only a package-local key and
typed arguments: task/package context already identifies the catalog, so adding
a type owner or resource FQN would complicate and bloat the artifact without
benefit. The validator checks declared catalog syntax, requires the schema key
set and package-document key set to match exactly, and rejects duplicate
package declarations. A source catalog cannot contain the same key twice, so
Studio reports that conflict before deployment. Validation does not prove the
runtime key encoded in every Bytes value.
AccessHost uses the same package catalog for informed network-access
decisions. Its source forms are
@LibertasAccessHost("api.example.com,telemetry.example.net") and
#[libertas_access_host("api.example.com,telemetry.example.net")], and it is
valid only on a function. The emitted value contains DNS identities rather than
resolved addresses: resolution can change over time, while the localized
same-key resource remains a stable explanation shown to the user or
administrator. The list has no scheme, port, URL path, wildcard, or IP literal;
transport and port policy are separate concerns. Hostname comparison for
duplicates is case-insensitive, but each source spelling must exactly match its
StringResources key.
Task-access approval presents function Permissions and AccessHost entries
together. Clients always render each raw key as the summary and the same App
package's localized value separately as its explanation. For AccessHost, the
raw key is also the actual network identity. The explanation supplements the
permission or hostname; it never replaces or hides it. Different Apps may give
different same-permission explanations because each App owns its own catalog.
App TCP and UDP networking is denied by default. A concrete hostname grants
both protocols to its Hub-resolved IPv4/IPv6 addresses. The reserved names
{network_access_local} and {network_access_external} grant their respective
address classes and remain mandatory localized string-resource keys. They are
reserved for this purpose; Studio rejects either key when it is not used by a
function AccessHost. While a task process runs, the Hub refreshes DNS and
monotonically adds new answers; old answers remain until process restart.
Changing the declaration during a reload forces that restart. These are
kernel-enforced process invariants, not SDK behavior. See
App network access for the complete address-class, DNS,
and lifecycle contract.
Runtime payloads never carry format templates. A formatted Bytes value is the
canonical encoded tuple of a resource key and ordered, typed
LibertasMessageArgument values, including an empty argument list.
Localized templates use zero-based Libertas Message Format 1 (LMF1), not
printf. See Libertas Message Format.
FormattedText does not allow a String host containing Base64. That design would encode the tuple only to decode it again for every render, adding no information or storage benefit and obscuring the difference between formatted data and literal text. Keeping the runtime host as Bytes provides a sharp type distinction that makes developer mistakes less likely. A generic JSON transport may spell its Bytes value as Base64, but that is only the transport's byte codec; the schema and runtime value remain Bytes end to end.
Reserved Default values and constant references
A published Default is ordinary data except for these reserved values, which
this section alone defines:
| Value | Initial value | Valid on |
|---|---|---|
$null |
null | Any Nullable field, as a Default only; never Fixed. It skips the host type's value grammar and a non-Nullable field rejects it. |
current-user |
The authenticated session's user | User and, as the only Fixed value, the security constraint; see User and group defaults. |
current-user-groups |
The current user's groups | List<User> only; see User and group defaults. |
See Rust Default spelling.
The text None is not reserved: on a Nullable String it is the word. In
Rust source #[libertas_default($null)] is the one spelling of the null
default, on any Option<T> field including an Option of a Union or
Enumeration. An unquoted None on an Option<T> field is a parser error that
asks for $null, so the old spelling cannot silently change meaning. On a
Union or Enumeration field that is not Option<T> an unquoted None names the
member None, as any member name does; on any other field it is a parser
error. "None" in quotes stays the word everywhere, so it is how an Option<T>
Union or Enumeration selects its None member.
See Rust constant references.
In a value attribute (Default, Fixed, NumberMin, NumberMax,
NumberStep, SizeMin, SizeMax, and the rule text of ValidationRules,
RangeMin and RangeMax) a leading $ marks a reserved value or a constant
reference: $null, or $NAME / $path::to::NAME, which the Rust parser
replaces by the const item's literal before emission (see
Rust constant references). The
published schema carries only the literal, so consumers never see a constant
reference. An unknown $word in a value attribute is a parser error. In a
path attribute (EnumSource, ConstrainedBy, CopyFrom, CopyTo) $ is
instead the request root of the Libertas Schema Path.
The two kinds of attribute never collide, so no escape is needed.
Pattern accepts a whole source constant argument through its
source-language syntax. Inside
a quoted pattern, $ remains regex text; decoded patterns are never scanned
for constant references. The emitted attribute contains only the decoded
pattern.
User and group defaults
User stores a user or group ID. Neither a User nor a List<User> implicitly
selects the current user, including when only one picker candidate exists.
Without Default, ordinary initialization applies, including undefined.
Copying retains the shared initialization precedence.
The Default string accepts these case-sensitive selectors:
| Selector | Initial identity |
|---|---|
admin |
The Hub's built-in administrator user. |
admin-group |
The Hub's built-in administrator group. |
user-group |
The Hub's built-in regular-user group. |
guest-group |
The Hub's built-in guest group. |
current-user |
The authenticated session's user. |
Decimal ID from 0 through 9223372036854775807 |
That explicit user or group ID; legacy ID 0 remains valid. |
current-user-groups |
The current user's direct and inherited groups; List<User> only. |
A scalar User accepts exactly one selector except current-user-groups.
A List<User> accepts a comma-delimited sequence; an empty or whitespace-only
string means an empty List. Trim surrounding whitespace for every selector.
Unknown tokens, empty entries within a nonempty sequence, and unsupported
scalar/list combinations are schema errors.
Resolve built-in identities from Hub metadata and current-user identities from the authenticated session, never from display names. Expand current-user groups in ascending numeric ID order, removing duplicates; then process the complete selector sequence left to right, keeping each ID's first occurrence. Missing required Hub, session, or membership context leaves the whole default unresolved (undefined), never a partial selection. A resolved empty membership yields an empty group expansion. Final values still satisfy ordinary field constraints.
For example, @LibertasDefault("current-user") initializes one User, while
#[libertas_default("current-user,current-user-groups")] on a List<User>
initializes the current user followed by their groups. These are initialization
selectors, not runtime string values or new schema attributes.
Fixed current-user security constraint
Fixed on User (LibertasUser in source) is always a security constraint
that the Hub MUST validate. The only currently valid value is the exact string
current-user. Numeric IDs, built-in identity selectors, group selectors, and
selector lists are invalid for Fixed; they remain available under Default
where documented. Future Fixed values require an explicit security contract
and corresponding Hub enforcement; an unknown value MUST be rejected.
Fixed("current-user") is not merely an initialization hint. It requires a scalar User at a Request or SubscriptionRequest root, or
within its nested structs and selected union branches. List items, list-level selector combinations,
configuration parameters, response-only uses, and declarations with no valid
request use are schema errors. Reuse in a forbidden position remains an error
even when the same declaration also has a valid request use. The client resolves the authenticated session's user ID and prevents
editing. It may be combined with Hidden; hiding the field changes neither
its encoded value nor its validation and authorization requirements.
Recommended declaration order: declare one Fixed("current-user") field
as the first payload field of each Request or SubscriptionRequest variant,
and reuse that verified identity in App code. This keeps the identity near
the start of the payload after any protocol variant selectors. First position
is a recommendation, not a schema requirement; nested protocol variants and
identity fields within nested request values remain supported, excluding Lists.
If additional fields carry this constraint, the Hub MUST validate every
applicable occurrence; validating the first never exempts later fields.
Declaration order enables efficient identity checks but does not guarantee
that a Hub implementation decodes only one field.
Before invoking App code, the Hub MUST compare the submitted value with the user identity established by authentication for that invocation. It MUST reject a mismatch, null, an unresolved identity, or malformed input. It MUST NOT repair a submitted mismatch by replacing the value. Client autofill, ReadOnly, Hidden, labels, and a claimed user ID in the payload are never evidence of identity.
The same requirement applies to ordinary requests, subscription requests, workflow submissions, calculation requests, and AI tool calls. An App-to-App or forwarded invocation without a trusted authenticated user binding MUST be rejected when it requires this constraint; the App owner or a user ID copied from another invocation is not a substitute. Copies and supplied EditRequest values retain their bytes and must pass the constraint when submitted. Stored results do not constitute proof of the current caller's identity.
The rule uses effective attributes, including aliases, inheritance, and materialized dependencies. Every submission must carry the resolved current user ID. A client that cannot resolve it must leave the request unfinished; Hub admission rejects it rather than assuming a user or weakening the check.
For mandatory notification recipients, combine Default with static ReadOnly:
on a scalar User it protects that ID; on a List it protects membership and order,
so mark the User item type ReadOnly as well to protect each recipient ID. Follow
the ReadOnly placement rules. An unresolved
explicit default blocks submission even when ReadOnly; never silently substitute recipients.
MIME-aware Bytes
Android, iOS, Studio, and the pure Web Control client implement this contract. The pure Web client requires origin-private file storage to retain selected media links across page reloads; without it, file selection uses an in-memory value for the current editor and camera capture is unavailable.
MimeTypes is a field or named-type attribute on an effective Bytes host. A
field declaration replaces its type's entire filter. Its value is one or more
unique lowercase ASCII entries separated by commas without spaces. An entry
is an exact type/subtype, a type/* range, or the sole entry */*. Each
name component has 1–127 characters, begins with a lowercase letter or digit,
and then uses lowercase letters, digits, or ! # $ & ^ _ . + -. Parameters,
file extensions, MIME aliases, and quality values are excluded. A value always
names one exact MIME type. Studio validates the grammar, effective Bytes host,
duplicates, literal defaults/fixed values, and conflicts. MimeTypes cannot
coexist with FormattedText and cannot occur in a CacheIdentity field or
its descendants, because the draft identity tuple persists its value.
An unannotated Bytes field remains a fully supported opaque binary type,
including for machine-to-machine protocols. MimeTypes is optional. A defined
MIME-aware value has this byte sequence inside the ordinary Avro bytes
primitive:
ASCII "LMB1" | u8 mime_length | mime_length ASCII MIME bytes | content bytes to end
mime_length is 3–255. The MIME string is exact lowercase type/subtype,
matches the effective filter, and has no parameters. The enclosing Avro length
delimits the content; there is no content-length field, filename, local path,
or file-link token in the wire value. A decoder rejects malformed, truncated,
unknown-version, or disallowed nonempty bytes. JSON transports may Base64-wrap
the entire sequence. An App must validate actual content when the declared
MIME label matters to its processing or security.
Zero-length Bytes means media undefined. It is a present and valid wire
value, including on a non-Nullable field. A server may send it; clients show
no selected media. It is distinct from nullable null and from a valid LMB1
value with zero content bytes. For ValidationRules,
zero-length MIME-aware Bytes evaluates as null; ordinary Bytes retain their
existing valuation. A mandatory media field needs an explicit rule such as
[] != null : "IMAGE_REQUIRED";. SizeMin and SizeMax run only for defined
media and measure content bytes, excluding the LMB1 header. Thus SizeMin=1
rejects a defined empty file but does not require a file to be selected.
Transport limits count the entire sequence.
Clients may hold the field as undefined, as a client-local file link, or as nonempty in-memory bytes. Server-sent full values use the in-memory form; server-sent empty bytes become undefined. A selected file or camera image is staged privately and held by a local link. Every outbound serialization opens and validates linked files, then materializes the complete LMB1 value before sending. If a link disappears during submission, the client stops that attempt, marks the field undefined, reports the field error, and retains other edits. It never silently submits empty bytes for a disappeared file. The local draft rule governs recovery and cleanup; media presentation governs the default controls and preview.
MimeTypes changes the semantic Bytes type without changing the Avro
primitive. Raw and MIME-aware Bytes are not implicitly compatible. A producer
MIME filter is compatible with a recipient only if every exact type it admits
is accepted by the recipient; dynamic values are checked against the
destination at runtime. Copies preserve the complete envelope; clients do not
guess or transcode media from a filename or destination filter. Adding
MimeTypes to an existing raw field requires a new immutable App version.
Legacy mobile-Hub paths retain their raw Bytes behavior.
4.4 Chart presentation attributes
Chart is valid only on an effective Response or SubscriptionData payload.
The 2D chart data contract owns
primitive, carrier Table, composition, and Union-wrapper shapes, including
preservation of Hidden context. Chart metadata is schema presentation
semantics: it adds no payload bytes, message type, or Hub state.
ChartChannel uses the canonical role order defined in
Client-side charting. Roles are
deduplicated and ordered by Studio's shared validator after language-specific
extraction. A field may have only one scale-bearing role; tooltip and detail
are the only roles repeatable across fields. Secondary endpoints (x2, y2,
theta2, radius2) require their primary and cannot own a scale.
FormattedText chart fields use the same custom LMF1 renderer as notifications
and data names. LMF1 changes only localized labels and their measurement; raw
resource keys or tuple bytes remain the categorical domain, grouping, ordering,
shared-scale, and interaction identity. An LMF1 argument's number/date style is
not replaced by a field Format or a chart scale formatter.
point, line, rule, and text accept either their Cartesian or polar
coordinate form; polygon accepts Cartesian or polar vertices and fills its
implicitly closed compound subpaths with the fixed evenOdd rule. Theta and
radius remain quantitative numeric positions: their explicit scale type may be
linear, log, symlog, sqrt, or identity with already normalized
0...1 values, never ordinal, band, point, time, or utc. The complete
equal-aspect mapping, polygon
grouping, guide, interaction, and limit rules are defined in
Client-side charting.
A layer accepts unit children or nested layers (flattened in schema order),
not a direct hconcat or vconcat; concat grids may nest freely.
Enumeration, Device, VirtualDevice, User, Endpoint, and LanDevice
are typed categorical chart values. The raw member/reference value defines
category identity, grouping, domain order, and focus keys; the client localizes
an Enumeration member or resolves a Libertas object through its normal
permission-aware display resolver. Apps do not
add duplicate String label fields solely for charts.
A plain String on any role that can emit human-readable chart text is a
conformance assertion that the value is genuine literal domain/user data, such
as an externally supplied proper name or free-form annotation. A closed
App-defined vocabulary uses an Enumeration; App-authored dynamic wording uses
formatted Bytes output; and an identity that is a Libertas system object
retains its Device, VirtualDevice, User, Endpoint, or LanDevice type.
This rule covers positions, facets, ordinal legends, text marks, tooltips,
value/span guides, and accessibility. A key-only String or guide-less
identity-scale appearance String is not rendered as text and is exempt. Studio
can validate the type/role mechanics but cannot infer String provenance from
schema JSON, so App review and response fixtures must enforce that assertion.
The reconciled bubble, activity-heatmap, direct-label, network, Sankey, and
traffic-source field dispositions are listed in
Client-side charting §8.1.
A Bytes value carrying FormattedText=true is also categorical chart data.
It may be used only for categorical and textual roles with an automatic,
ordinal, band, or point scale. Quantitative roles, continuous domains,
and identity scales are invalid. Its raw encoded tuple remains
the category, group, order, and interaction identity; localized rendering is a
presentation view and never changes those semantics. Plain Bytes is not a
chart scalar.
An integer Number carrying DateOnly=true is a temporal calendar-date chart
scalar. Position scales infer time; explicit time and utc both preserve
the same timezone-free civil date, while band and point opt into sparse
date categories. Quantitative roles and linear, log, symlog, sqrt,
ordinal, or identity scales are invalid. Shared scales distinguish DateOnly
from ordinary Number values. Continuous DateOnly geometry maps valid
yyyyMMdd identities through their timezone-free Gregorian day ordinal, not
through decimal integer distance. Generated min and max must be exact valid
Gregorian yyyyMMdd values after source-number canonicalization.
ChartScale properties are emitted in id,type,min,max,mid,zero,reverse
order with no whitespace. Source annotation key kind becomes wire key type.
Scale IDs, closed type tokens, booleans, finite domains, channel/type
compatibility, identity-scale restrictions, and same-ID compatibility are
validated by Studio's shared chart validator after parser extraction and
dependency materialization. Scalar properties use one shared shortest
round-tripping binary64 spelling, so lexically different equal values cannot
create false shared-scale conflicts. Canonical generated DateOnly bounds must
be valid eight-digit yyyyMMdd dates; equivalent source numeric spellings are
normalized before this check. DateTime bounds must be integral epoch seconds
in 0..253402214399 inclusive. TimeOnly bounds are seconds in 0..86400
inclusive, with 86400 reserved as
the end-of-day scale endpoint. The closed target/scalar/scale-type matrix,
including band-only offsets/facets and ordinal integral appearance values, is
normative in Client-side charting; TimeInterval remains continuous and never
acts as an ordinal or identity value. Attribute lookup follows ordinary nearest
same-name replacement, including official compatible-type materialization.
ChartGuide has required target and source properties followed canonically
by optional position,title,domain,ticks,labels,grid. Sources are scale,
value, span, and none; positions are auto, top, right, bottom,
and left; every component is auto or none. Scale/none declarations sit on
the matching primary scale binding. Value/span declarations sit on a direct
scalar guide-label field with no scale-bearing channel; value targets
x/y/theta/radius, while span targets a continuous Cartesian x or y with
its paired endpoint. Non-identity primary scales infer a scale guide; identity
and offset scales infer none. One explicit target declaration replaces that
default. Guide kinds, component and side matrices, shared-layer ownership,
typed localization, ticks, span grouping, resource limits, and fallback are
normative in Client-side charting.
The deterministic 200-point post-guide plot-width threshold and tap-activated
overlay are runtime layout policy defined there, not schema properties;
renderers do not invent a different breakpoint or relocation policy.
The same runtime contract sizes the preview's charting region from its pane,
with a 180-point floor and a viewport four thirds that tall
(preview height). Fullscreen uses three quarters of the current usable
height, capped in portrait by the current usable width, keeps complete legends
in the one outer vertical chart viewport, and initially scrolls only enough to
expose the first charting region. None of these policies adds schema grammar.
Discrete positional band and point scales likewise add no grammar. Clients
preserve every category in a bounded plot, use an exact 20-point minimum
leaf-category pitch (including nested offset lanes), and expose overflow through
an X and/or Y category viewport. A two-dimensional heatmap may pan both axes;
ordinary category charts consume only their overflowing dimension. Category
viewporting never aggregates or samples rows.
Dense monotone Cartesian lines and vertical areas use the contract's automatic
logical-point resampling with a visible ≈ disclosure. That is renderer-owned
level of detail, not grammar or an App transform. There is no chart-specific
hard limit on source rows, primitives, or path vertices; structural/metadata
limits, checked scans, cancellation, viewport culling, and visible LOD provide
the safety boundary.
The complete token tables and geometry rules are normative in
Client-side charting.
The App task owns every data transformation, including arbitrary time
projection, binning, normalization, and layout. It may emit a native temporal
position, or transformed numeric geometry plus a typed ChartGuide
source=value/span label field. The client renders and localizes that
chart-ready snapshot; it does not infer or execute an App transform.
The opt-in 3D namespace is parallel to, and does not extend, the preceding 2D
namespace. Chart3D, Chart3DChannel, Chart3DScale, Chart3DGuide, and
Chart3DView add schema metadata only and remain mutually exclusive with their
2D counterparts on one chart path. Primitive Chart3D tokens are point,
line, bar, surface, wireframe, and stem; compositions are layer,
hconcat, and vconcat. The closed channel order is x,x2,y,y2,z,z2,u,v,
then color,fill,stroke,opacity,fillOpacity,strokeOpacity,size,shape,strokeWidth,strokeDash,
then tooltip,detail,order,key,row,column.
User-facing chart-family names never become additional schema tokens. Select
the primitive by geometry: scatter and point clouds use point; bubbles add
size; 3D lines and waterfall/spectrum traces use line; columns use bar;
height maps, non-uniform grids, and App-sampled mathematical surfaces use
surface; wire grids use wireframe; contour overlays compose surface and
line in a layer; and impulses use stem. Homogeneous multiple series use
detail, while heterogeneous series use layer.
Chart3DScale mirrors ChartScale; source kind is emitted as schema type.
It additionally accepts logBase > 1 for type=log, non-negative
growMin/growMax fractions for automatic continuous x/y/z domain growth, and
inclusive [0,1] padding on a bar base axis.
Chart3DGuide uses required target,source, optional
title,domain,ticks,labels,grid,tickCount,tickStep, and permits detail as a
target. Chart3DView properties are, in canonical order,
projection,azimuth,elevation,zoom,fov,orbit,aspectX,aspectY,aspectZ,targetX,targetY,targetZ,pan,zoomEnabled.
Projection is perspective or orthographic; orbit, pan, and
zoomEnabled are booleans;
azimuth, elevation, and target coordinates are finite schema values without
rewriting authored data. The client canonicalizes effective azimuth and clamps
effective authored/default and transient elevation to the closed
[-90,90]-degree interval; target coordinates retain no artificial range.
Zoom and each aspect are greater than zero, and 0 < fov < 180. Orthographic
projection accepts fov and ignores it while orthographic.
zoomEnabled=false disables user zoom independently without changing the
authored initial zoom.
The three interaction booleans enable or disable the universal client-owned
orbit, pan, and zoom actions independently. They do not select gestures, remap
a disabled action, or create an interaction mode. There is no mode, gesture
mapping, sensitivity, control-label, or other client-control property in schema
or runtime data. A preview remains gesture-neutral; the existing full-screen
Chart Explorer alone owns the fixed touch, tap-control, keyboard, and
accessibility vocabulary.
In full-screen portrait, every physical scene uses a single-height horizontal
control strip before the scene and lowers appearance/detail guides below it. In
full-screen landscape or square, it atomically uses a measured trailing
control-and-guide utility rail when the remaining final post-guide plot still
satisfies the normative M = min(200,C) width rule; otherwise it retains the
portrait stack. This placement is client runtime policy and adds no schema
property.
Point and stem-tip shape identity and automatic assignment use
circle,square,triangle,diamond,sphere,cube,pyramid,cylinder in that order;
bars use box,cylinder,pyramid,sphere. A numeric size may be zero to suppress
or degenerate the corresponding geometry without removing its source row.
bar accepts discrete or sparse continuous x/y bases with a continuous z
value. x2, y2, and z2 are independently optional: absent x/y extents and
the z baseline are inferred, while all three pairs form an explicit cuboid.
The legacy any-orientation form remains valid when exactly two primary axes are
band scales and the sole continuous primary is the value axis. Surface and
wireframe u,v values may be arbitrary finite topology coordinates; they need
not be integral or dense, and missing coordinates form holes. There are no
fixed Chart3D schema caps on admitted
rows, vertices, composition units, guides, or tooltips; ordinary transport and
runtime resource admission still applies. The complete geometry, topology,
view, interaction, fallback, and resource rules are normative in
Client-side 3D charting; its
interaction section
defines the universal client control vocabulary.
4.5 Union, endpoint, and data attributes
| Attribute | Meaning |
|---|---|
UnionTagName |
TypeScript parser metadata naming a native union discriminator field. |
UnionContentName |
Rust parser metadata naming the content field of an adjacently represented enum. |
Discriminator |
Zero-based consecutive integer wire identity on a Union member: Children[i] has Discriminator=i, independent of payload constraints. |
UnionTagField |
ScriptLanguage only (for example TypeScript and Python): marks the native tag field inside a union variant. Native compiled languages such as Rust must not emit it. |
ProtocolSchema |
Local simple name or external App FQN of the union carried by an Endpoint. |
LibertasProtocol |
Validator-generated marker on a union referenced as an endpoint protocol. |
EndpointServer |
Endpoint is the server side of the protocol. |
Endpoint-client Default / Fixed |
Mutually exclusive peer-selection/provisioning metadata using the established developer-package::function FQN grammar. Protocol-schema compatibility determines eligible servers. Default leaves that set unchanged; Fixed further restricts it to the named process and forces its package into finalized AppDependencies. Both Endpoint-ID selectors remain editable and may expose multiple choices. If none are eligible, either FQN recommends the process to provision. The separator is ::, never :. The string is neither an Endpoint ID nor a schema-type reference and is not materialized as a type. |
ExcludeUI |
Exact marker value true on an Endpoint server carrying a resolved ProtocolSchema, or on a protocol Request/SubscriptionRequest variant. The Hub Control service omits a marked server Endpoint from its UI-device list before grouping and from LLM tool collection; generated Control GUI and Hub LLM tool-definition generation omit marked request operations. Admin creation, editing, approval, persistence, compatibility, and dependency/assignment calculations use the complete schema. Runtime bindings identify a server by its resolved ProtocolSchema, so all Endpoint servers sharing one resolved protocol root must use the same ExcludeUI value. The endpoint remains registered; protocol roles, serialization, routing, subscriptions, and authorization are unchanged. |
RequestConfirmation |
Exact marker value true on a protocol variant carrying Request. A client asks the person to confirm before it sends the request, and the AI assistant asks the user before it calls the generated tool; the wire form and validation are unchanged. Rejected on any other node and with DefaultRequest. See critical requests. |
LLMToolCall |
Boolean switch. For the automatic AI tool, it defaults to true. On a protocol Union, false excludes descendant Request variants from Hub LLM tool generation; a variant's explicit true overrides the Union. The nearest declaration or occurrence on the path wins. false on one Request variant excludes only that variant. It does not remove an Endpoint or Request from Control, alter wire data, or bypass ExcludeUI or access checks. |
Permissions |
On a built-in Endpoint carrying EndpointServer, comma-delimited downstream permission requirements. No endpoint string-resource reasons are required. The Hub checks the selected client task's function permissions once during approval/configuration, not per endpoint message. |
Request, Response |
Protocol roles for a union variant. |
Error |
Exact marker value true on a variant reachable from an Endpoint protocol Union and carrying the effective Response role. It classifies the complete response as an error result that is out-of-band of the current protocol state transition. It does not imply Response, prescribe payload fields, or change serialization, routing, transitions, or wire compatibility. |
AccessPrivilege |
Integer bitmap on a Request or SubscriptionRequest: Read=1, Write=2. The set bits are alternative end-user endpoint access levels allowed to invoke that runtime operation. Omission means Read|Write (3). |
SubscriptionRequest, SubscriptionData |
Subscription protocol roles for a union variant. |
WorkflowRequest |
Response semi-role; see the WorkflowRequest definition. |
PrevRequest |
Request-only ordered list of distinct Request targets; workflow Back constructs the selected action from the current editor. Controls are suppressed in embedded trailing panes under the pane contract. |
NextRequest, NextResponse |
NextRequest names state-machine actions. On requests NextResponse names allowed server replies; on SubscriptionData it declares directional compatible updates. See the workflow contract for editor transitions and completion. |
ProtocolConformance |
Management-tooling expression over Union variant native names. Studio accepts it on a Union declaration or a Union-typed variant; when used in an endpoint protocol, it declares requirements for the opposite endpoint at that Union level. |
BaseObjects |
Comma-delimited Libertas Schema Paths defining the objects to which an endpoint instance is bound. |
LibertasData |
Validator-generated marker on a persistent data union, synthesized from FunctionDataSchema; there is no source marker. |
An Error-marked correlated Response completes the current request attempt without participating in the protocol state transition. The client must preserve the current interaction and its current response data while presenting the error, and the same interaction must remain available for retry. The error response must not be installed as current response data, used as transaction context, navigated to, or visualized as ordinary data or a chart.
Android and iOS inspect the complete decoded error response in deterministic
depth-first response order: fields follow schema order and List elements follow
encoded order, including hidden fields. A persistent error dialog shows the first non-empty
FormattedText value that can be rendered. If the response contains no
renderable FormattedText, the dialog shows their localized generalized error
message. Error classification takes precedence over default-response, chart,
and ordinary-data presentation. After consuming the correlated response, the
client clears only the in-flight wait state; it does not close the current
composer or replace the currently displayed response.
Outside a workflow, a normal correlated Response with no WorkflowRequest
may omit its effective NextRequest. This is a
deliberate non-transition, commonly used by shared Success or Failure
acknowledgements. The client consumes the response and completes the in-flight
attempt without installing it as the displayed protocol data, changing the
request choices, or adding a protocol-data navigation destination. The
interaction reference-context rule
still applies to that accepted Response. Omission does not authorize all Request
variants. A present NextRequest remains non-empty and installs exactly its
declared choices.
These correlated-response rules apply under the shared protocol role rule.
4.6 Identity attributes
| Attribute | Meaning |
|---|---|
ForeignTypeSchema |
Direct official type FQN declared on a published compatible type. Runtime consumers merge official attributes into a transient copy and preserve explicit compatible overrides. |
FullyQualifiedName |
Transient parser metadata identifying an externally declared type in the expanded compiler graph. Studio rewrites references to the owner's FQN and removes the attribute, with the external definitions, before publication. |
5. Composite-type semantics
5.1 Tables and inheritance
A Table is an ordered record. Each child is one field. A derived table can list
base types in Heritages; inherited children are resolved recursively and added
unless the derived table already declares the same NativeName.
5.2 Lists
A List contains one child schema describing every element. The element may be a
built-in node, an inline composite, or a named reference. List-level and
element-level documentation are distinct even though both originate around the
same source declaration.
5.3 Enumerations
An Enumeration contains ordered member nodes. A member is represented as a
number node and can carry Fixed with its source numeric value. Documents carry
an Enum array for display names and descriptions. Language parsers extract
the members, resolve native numeric constants, and preserve the supplied
Default. Studio's shared schema validator checks a Default supplied as a
member name or fixed numeric value and normalizes it to the selected member's
numeric value. This semantic validation and normalization is centralized;
individual language parsers must not duplicate it.
Enumeration protocol associations. ProtocolVariant on an ordinary
Enumeration member names exactly one variant native name in the Endpoint
protocol whose value tree consumes that Enumeration. For example,
@LibertasProtocolVariant("EditWages") or
#[libertas_protocol_variant("EditWages")] on the Wages enum member
associates the value Topic.Wages with that protocol variant. This is typed
schema metadata, independent of localized text, enum order, Link, and the
Display Template. It changes neither numeric encoding nor protocol roles,
request construction, allowed replies, or navigation.
Studio validates placement on an ordinary enum member, the single non-empty name, and the target against every consuming Endpoint protocol. Bitflags members, fields, types, functions, and packages cannot carry it. A standalone published enum may declare the association before a consuming protocol is available; that consumer supplies the target-validation context.
Several enum members may name the same protocol variant. Clients first search
the current editing Request's typed value tree for enums whose declarations
contain that association, including Hidden fields. If present, their selected
marked members identify the values to match in each eligible linked occurrence
of the leading page. Match the same resolved enum declaration and selected
member through its declared numeric value, independently of field names or
positions. For example, TaxSection.Wages and TaxSection.Retirement can both
name SaveIncomes: its topic: TaxSection value identifies the active income
row. An absent, invalid, or unmarked value in such a Request enum matches no
row; never replace it with the first choice. If the Request contains no enum
whose declaration associates with its variant, the association to the Request
variant alone may identify a unique linked occurrence. Both forms use the actual
editing Request, including direct WorkflowRequest and EditRequest entry. Exactly one matching linked occurrence is highlighted; no match,
unmapped or invalid enum values, or multiple matching occurrences clear the
selection. Recompute against every loaded or refreshed leading page, so
reordering never retains an old position. Hidden identity fields may identify
a visible linked occurrence, but hidden nodes and their descendants never
become visible or selectable. The pane contract
owns eligible linked occurrences and placement: association affects optional
highlighting, never pane membership or whether an editor remains usable.
5.4 Unions
A Union contains variants in wire order. Enum values may name one of its protocol variants through an enum/protocol association. An internally discriminated union uses:
UnionTagNameon the union;- a numeric discriminator field as the first field of each variant;
Discriminator=<numeric tag>on each variant.
UnionTagField=true is exclusively ScriptLanguage metadata, for languages
such as TypeScript and Python that represent the tag as a native field. It is
not general Union-member metadata and must never be emitted for Rust. The
TypeScript parser marks its native discriminator field this way. Rust payload enums use their variants as union cases and may name an
adjacent content field with UnionContentName; they do not emit tag, untagged,
or externally-tagged metadata.
The discriminator field is native compiler structure and is omitted when compatible union structures are compared or official documents are projected.
Every newly emitted Union member carries an explicit Discriminator. For a
Union with N members, values must be exactly 0, 1, …, N−1, and Children
must already be sorted so that Children[i].Discriminator == i. Parsers emit
that order. Studio and runtime schema loaders reject negative values, gaps,
duplicates, non-integers, and mismatched ordering. Decoding directly indexes
Children after checking the discriminator bounds; no search or remapping
within a Union is required. These identities are never positions in a filtered list. Native Rust payload
enums emit their encoded variant index; TypeScript emits the native numeric tag.
Fixed exclusively constrains a value (including the established Endpoint
process constraint). It never identifies a modern Union member. A Rust
VariantA(u32) remains one member named VariantA, with both
Discriminator=0 and Fixed=7 permitted: the first case carries the fixed payload 7.
A discriminator alone never initializes, validates, hides, or locks the payload.
The Hub and all clients resolve each Union member from its own explicit Discriminator, or
its zero-based declaration position when absent, without rewriting stored JSON
or digests. No runtime interprets Fixed as a Union discriminator, even
when Discriminator is absent. Fixed always retains its payload meaning.
An explicit discriminator must equal its declaration index; invalid values or
ordering are errors, never reasons to fall back or reinterpret Fixed. Cache the identity on the member occurrence owned by the effective
Union, never on a shared payload type. Discriminator=true is not a supported
native tag-field marker; ScriptLanguage parsers emit UnionTagField=true.
Published protocol and persistent-data schemas are backward-compatible, append-only contracts. Their Union type names are versionless and remain invariant for the App's lifetime. New App versions append new payload variants to the end of the same Union and preserve every existing variant's position, discriminator, name, payload shape, meaning, and compatibility contract. They must not replace the container with a version-named Union. See the App data-type design guidelines for the authoring and review rules.
6. Endpoint protocols and persistent data
6.1 Endpoint protocol validation
Enum/protocol associations are checked against each consuming protocol alongside its variant graph.
Every ProtocolSchema target must resolve in the expanded compiler graph and be a Union. Validation adds
LibertasProtocol=true. Each reachable protocol union variant must then satisfy
one of these rules:
- The variant or its resolved named type has at least one role:
Request,Response,SubscriptionRequest, orSubscriptionData; or - it is itself a union; or
- it contains exactly one field whose resolved type is a union, and validation recurses into that union.
A role-less circular union chain is invalid.
Automatic and explicit subscription selection, access privileges, and lifetime
follow the protocol contract. An effective Nil
payload alone does not make every subscription automatic.
On requests, each NextResponse entry names a protocol variant marked Response,
SubscriptionData, or Request for EditRequest delivery. On SubscriptionData, it
names other non-null Table SubscriptionData variants under the
compatible-data contract. Each NextRequest entry must
name one marked Request or SubscriptionRequest. The transition attributes
are optional; Studio validates their non-empty target lists when present and
does not synthesize targets when they are absent.
AccessPrivilege is valid only on a protocol Request or
SubscriptionRequest; the same variant may carry both roles. Roles declare
permitted uses and never select the runtime operation. A client invocation must
carry an explicit Request or SubscriptionRequest opcode, which the Hub validates
against the selected variant's complete role set. Its schema value is a
canonical decimal integer bitmap containing only currently defined bits:
Read=1, Write=2, or Read|Write=3. The bitmap lists alternatives, so a
operation is authorized when the caller's effective access to the owning endpoint
has any listed bit. The omitted default is 3: because the platform cannot infer
whether an arbitrary operation only reads data or changes state, a read-only user
may invoke an unannotated operation. A developer who knows that an operation
changes state must explicitly set Write; a caller with only Read is then
blocked by the client UI and rejected authoritatively by the Hub. More bitmap
values are reserved for future access classes.
The word privilege is deliberate. Permissions describe data/resource access
granted to an entire task process and are checked when tasks and endpoint peers
are configured. AccessPrivilege instead constrains an end user's individual
runtime protocol operation against that user's access to the owning endpoint.
It does not grant task-process permissions and cannot replace Permissions.
ProtocolConformance is accepted on any Union declaration and on a child of
a Union whose effective type resolves to another Union. A Union declaration
does not identify whether it will later be used as an endpoint protocol, so
parsers do not attempt to enforce protocol usage or a client/server role. On a
Union-typed variant, the variant's attribute becomes the conformance expression
for that nested Union in that declaration context and takes precedence over an
attribute on the referenced Union declaration. The expression uses the direct
variant NativeName operands of the Union being evaluated with !
(NOT), & (AND), ^ (XOR), | (OR), and parentheses. Operator precedence,
from highest to lowest, is !, &, ^, then |.
Endpoint client/server compatibility is a mutual configuration-time check. The
client context's conformance rule is evaluated against the server's advertised
variants, and the server context's rule is evaluated against the client's
advertised variants; both results must be true. A scalar variant operand
evaluates to whether the opposite Union advertises that name. A Union-typed
variant operand instead evaluates to the nested Union's conformance result,
using the attribute on that variant as the nested context's expression. Thus
recursion follows the variants actually evaluated by the containing rule rather
than making unrelated optional branches mandatory. If either Union context has
no ProtocolConformance attribute, every direct variant declared by that
context is mandatory (equivalent to joining its variant names with &). A
malformed explicit expression is incompatible; management tooling must fail
closed rather than falling back to the default.
Name presence is not sufficient by itself. Corresponding capabilities must have pass-through-compatible payload layouts: positional Table fields and overlapping nested Union slots retain the same names, order, effective types, nullability, numeric representation, and enumeration values. The client task host rewrites only the capability prefix, so a capability may sit at a different Union depth or position in each peer. Discriminators inside a payload are not remapped; an optional nested branch there is safe only when appended beyond the smaller Union, so the smaller receiver rejects that out-of-range index. The task-host translator does not deserialize the payload. Each receiving App's decoder remains responsible for payload validation.
Every task editor uses the same filtered candidate sets for manual selection
and initialization. After a ScriptValue is created or populated,
the editor visits every uninitialized leaf in the editable value tree of type
Device, client Endpoint, or Enumeration. It fills a leaf only when
exactly one valid candidate remains. Device-type filters,
endpoint protocol compatibility, and the full Unique ancestor-list scope rule
are applied before counting. The Unique value is the number of ancestor
List scopes to check, with 99 as the default. Zero or multiple candidates
leave the value uninitialized for explicit user choice. Unique never affects
leaves outside its deterministic ancestor-list scope. Each creation or
population event causes one whole-tree traversal so every uninitialized leaf is
evaluated against the values currently present in its own scope.
User selection follows User and group defaults
and is not inferred from the candidate count.
Manual Device selection applies that same filtered candidate set before it
opens a chooser. If no candidate remains, Android and iOS preserve existing
source membership, roll back an exact provisional List member created by the
same Add action, and report the localized no-available-device error. They do not
open an empty chooser or retain an unusable provisional row.
For a client Endpoint, mutual protocol-schema compatibility is always the first
filter. Default never changes the compatible candidate set. Fixed applies a
second filter requiring the server Endpoint's owning task process to equal its
developer-package::function value. Neither attribute makes the selected
Endpoint ID read-only: one task process can provide multiple compatible server
instances. When the final set is empty, both attributes identify the process
recommended for provisioning. Fixed additionally makes that App package a
deployment dependency, so Studio includes the package and its authoritative
version in finalized AppDependencies; Default does not create a deployed
App dependency. This is distinct from the task-process installation dependency
enforced by clients before task creation.
6.2 Task pre-creation static dependency check
This gate concerns configured service tasks. The separate schema availability requirement ensures that dependency schemas and documents are available for every editor choice; a listed App dependency alone does not require a service task.
Before transmitting a task-creation request, every Android or iOS control client runs the following ordered gate against the selected Hub. This is a client-side static check in addition to, never a replacement for, the Hub's authoritative transactional validation.
- Check the App-and-native-function `Singleton` identity first. On conflict, report the failure and stop immediately. Do not queue the request and do not start dependency installation.
- Snapshot the requested task configuration into an immutable client-local pending-creation record, then traverse the complete effective function-input schema tree. Follow materialized named types, inherited and compatible schema nodes, Tables, Lists, and every Union alternative. Traversal must be cycle-safe, but a globally visited type set must not suppress another occurrence reached by a different field path because ancestor optionality is path-dependent.
-
Inspect every client `Endpoint` carrying `Default` or `Fixed`. An occurrence
is absolutely required when neither that Endpoint nor any node on its path
to the function root has effective
Nullable, and no ancestor source array or schemaListpermits zero items. AListpermits zero when effectiveSizeMinis absent or zero; a strictly positiveSizeMindoes not. No other ancestor kind, including selection of a Union alternative, makes the occurrence optional for this static check. -
For every absolutely required occurrence, calculate the live eligible
server set from the Hub's current task/Endpoint snapshot. Apply complete
mutual protocol-schema compatibility first.
Defaultleaves every compatible existing server eligible.Fixedadditionally requires the server's owning task process to equal its exactdeveloper-package::functionvalue. The requirement is satisfied only when this final set contains at least one server. -
If any requirement is unsatisfied, present the required-task installation
UI for the process named by that Endpoint's
DefaultorFixedvalue. Requirements may be grouped by identical process identity for presentation, but every Endpoint path remains independently checked and all requirements must be satisfied simultaneously. Installing the package alone is not sufficient: the resulting task must expose a server that passes the same compatibility and, forFixed, identity filters. If the named App package is absent, clients use the current Libertas Plaza flow to install that exact package before opening its required-task editor. A legacy Hub without Plaza support can create the dependency task only from an already installed App.
The original creation record remains queued while required installations run; no task-create/add-update request is sent for it. Creating a dependency task runs this same gate and may therefore enqueue its own dependency chain. The client tracks the originating root request and rejects a cycle of unsatisfied required task-process dependencies instead of recursively opening installation UI forever.
After each successful installation, the client refreshes the authoritative Hub
task/Endpoint snapshot and reruns the entire gate beginning with Singleton.
It must not resume from cached candidate IDs or assume installation alone met a
requirement. Only one fresh reevaluation that simultaneously satisfies every
requirement may dequeue and transmit the original request. Cancelling any
dependency installation in that request's chain immediately cancels and
removes the originating creation record; a later installation or topology
change must not resurrect it. Other independent queued task creations are
unaffected.
6.3 Function data schemas
FunctionDataSchema is the required function attribute
key=KeyUnion,value=ValueUnion. Both references resolve in the materialized
graph and must name unions. Each local target uses its simple name; each external
target uses its canonical FQN and contributes a dependency. Validation adds
LibertasData=true to the value union. This binding is separate from the
function's ordered call parameters. See the
storage schema declaration contract.
The key union and its complete reachable layout are ordinary entries in
Types, including nested unions, ordered fields, discriminator values,
numeric representations, byte bounds and nullability. There is no separate
index catalog: fields in a key define its identity and ordered scan dimensions.
Stored records and concrete index values are runtime data, not schema content.
The generated default document includes the key union, its nested types and
their field/variant text under the ordinary Types document collection.
Translations change those labels, never encoded keys.
7. Path-based object and choice binding
The Libertas Schema Path specification defines syntax, context, and resolution. The rules below define the attributes that use it.
EnumSource must contain exactly one path, and the target must resolve to
List in the same schema-data tree. When the target List declares no key, the
host's effective type must be Number with unsigned NumberType (u8, u16,
u32, u64, or u128) and it stores the selected item's zero-based index.
The host may instead be a List of such values, to choose several members
(EnumSource List hosts).
EnumSource keys. A List whose member is a Table may mark one field of that
Table as its key with Key=true (@LibertasKey / #[libertas_key]). An
EnumSource host whose target List declares a key stores the chosen member's
key value instead of its index, and the host's effective type must equal the
key's: both String, or both Number with the same NumberType. Studio
requires that:
- `Key` is the exact marker `true` on a direct field of a Table, never on a package, function, function parameter, named type declaration, List item, or Union variant;
-
the key field's effective type is `String` or an integer `Number` (`u8`,
i8,u16,i16,u32,i32,u64,i64,u128, ori128), and it is notNullable; it may beHidden; - the key field also carries `Unique` at any level. Keys are kept distinct within their List by the ordinary scoped-uniqueness check of task-value validation, not by a separate key rule;
- a Table has at most one key field, counting inherited fields;
- every value occurrence of a keyed Table is the member of a List. Using it as a field, function parameter, or Union variant payload is rejected.
NumberMin, NumberMax, NumberStep, RangeMin, and RangeMax on a keyed integer host bound the stored key value, never the member's row position. A member whose key is unset (null) or an empty String is not offered and cannot be selected. ConstrainedBy constrains a host's stored value, which for a keyed host is the key. Allowed sets are integers and ConstrainedBy applies only to Enumeration and Union fields, so Studio's existing rule already rejects it on a String-keyed host.
A List without a key keeps index storage. Adding or removing a key never migrates or remaps stored host values; the App owns rewriting any it keeps.
EnumSource List hosts. A host that chooses several members is a List
carrying EnumSource, for example a Vec<i64> field naming the keyed forms a
record covers. Its element obeys the scalar host rule above: the target List's
key type, or an unsigned index when that List declares no key. Studio rejects
a Nullable element and an element that is a Table, List, or Union. The path
resolves from the List host exactly as from a scalar host, including ^
steps and typed ancestors; its elements carry no EnumSource of their own.
The stored value is the list of chosen keys (or indices), and the allowed set
is the same: each selectable member of the target List.
- Every element must name a selectable member. An element naming none, a stale key included, is unanswered: the host blocks submission with a field error, as a stale scalar host does, and is never remapped or silently dropped.
- Elements are distinct by this contract, so the host needs no `Unique`: a repeated key or index blocks submission with a field error.
-
The empty List is a complete answer: none chosen. `SizeMin` and `SizeMax`
(
Size(min, max)) bound the number of chosen keys, soSizeMin=1requires at least one. - Numeric bounds and step on the element type bound each stored key or index, as on a scalar host.
-
The [singleton rule](protocols.md#backend-constrained-choices) never fills a
List host, and a Display Template
when_single_choicerule cannot select one.ConstrainedBydoes not apply, because the host is not an Enumeration or Union field.
How clients present, select, and write the choice, including taking a sole selectable member under the singleton rule, is owned by common field metadata.
BaseObjects is a comma-delimited list of paths and is valid only on a built-in
Endpoint. At task-value validation time every path must resolve to set values,
and two endpoint instances may not resolve to exactly the same base-object set.
Base objects are Libertas objects rather than devices specifically; for example,
they may be devices, users, or other object types.
For an endpoint carrying BaseObjects, the Hub derives and caches its displayed
name and icon from the resolved base objects, in their declared order:
- The name is the comma-and-space-delimited concatenation of the available, non-empty base-object names. The Hub stops before the result would exceed the platform's maximum logical-device name length.
- The icon is the first available, non-empty base-object icon. If no base object has an icon, the endpoint has no derived icon.
When a base object's name or icon changes, dependent endpoint names and icons refresh, including transitive references through other endpoints. A cycle does not cause unbounded refresh.
Android and iOS task editors retain a server endpoint with BaseObjects but
omit its separate editable row. The same ordered naming and icon rules apply;
the endpoint remains eligible as a compatible server choice for client endpoints.
8. Types-only and interoperable packages
TypesOnly=true means:
- every public package-owned named type exported from
index.tsorsrc/lib.rsis automatically a schema root unless its source declaration carries the language's no-argumentIgnoreddirective; - no export/schema decorator is required to make those public types roots;
- a named type may publish
FormattedTextfields under the formatted-output contract without choosing a runtime direction; its package may publish one sharedStringResourcescatalog; Functionsmust be empty;- Studio skips executable compilation and deploys an empty code payload;
- the Hub must reject marker/code mismatches and must not create an executable artifact for the version.
See native root discovery.
Without TypesOnly, public non-ignored functions are the only roots and the
package publishes the minimum transitive closure of non-builtin types reachable
from their arguments and reachable schema-reference attributes. A decorator on
an otherwise unreachable type does not publish it. Ignored is valid on a
public function or public named type; reaching an ignored type from an included
root is an error rather than permission to emit a dangling reference.
A compatible type declares exactly one ForeignTypeSchema on that published
named type. Compatibility declarations are per type, so one App may contain
ordinary types and compatible types that refer to different official Apps.
Localized type-document sharing follows each declaration automatically.
At runtime the consumer resolves the official schema, validates structural wire compatibility, and creates a transient merged view. Official attributes provide the base at every corresponding node; attributes present on the corresponding compatible schema node override them by name. The persisted compatible schema is never rewritten. The final merged graph receives the normal contextual attribute validation. Document projection uses the same structural ID mapping while preserving language-native nodes with no official counterpart, such as a TypeScript-only discriminator field. Package-level display text remains owned by the compatible package.
9. Static and runtime validation
Schema generation performs static validation before deployment. The section that owns an attribute or mechanism states its checks; the shared layer enforces those and, among others, the following:
TypesOnlymultiplicity, marker value, and empty functions;- exact function-only
Singleton=trueplacement and multiplicity; - endpoint protocol existence, union shape, roles, transitions, cacheability, and role-less cycle rejection;
- optional
NextRequestsemantics: a present value has a non-empty valid target list, while omission on a Response remains a valid non-transition and never means every Request variant; - exact
Error=trueplacement on a variant reachable from an endpoint protocol Union and carrying the effectiveResponserole; the marker is classification metadata and does not define a response payload. The protocol error procedure presents a persistent dismissible dialog while preserving the current interaction, response data, and retry; - exact
ExcludeUI=trueplacement on an Endpoint server carrying a resolvedProtocolSchema, or aRequest/SubscriptionRequestvariant reachable from an endpoint protocol Union, plus equalExcludeUIvalues on all Endpoint servers sharing one resolvedProtocolSchemaroot; AccessPrivilegemultiplicity,Request/SubscriptionRequestplacement, and canonical nonzero bitmap containing only the currently definedRead/Writebits;- function data-schema existence and union shape;
- enumeration default validity and normalization;
- non-empty
BaseObjectson built-in endpoints only; Uniqueonly on non-composite hosts with integer level at least one;- exact
DateOnly=trueplacement on an integer Number, validyyyyMMddDefault/Fixedvalues, and the exclusions of numeric semantic attributes; - exact
Bitflags=trueEnumeration catalogs, explicit unique single-bit member values with the zero exception, integer host/reference/width checks, declared-bitDefault/Fixedmasks, and conflicting presentation metadata; Formatmultiplicity, effective Number/DateOnly/DateTime/TimeOnly placement, the host-specific portable Unicode LDML grammar, and ordinary-Number exclusion ofTimeInterval,EnumSource, andBitflags;EnumSourcehost type (an unsigned index, or the target List's key type, for a scalar host or each element of a List host), path syntax/resolution, and list target;Keymarker value, direct Table-field placement, String or integer host, non-Nullable, requiredUnique, one key per Table, and List-member-only use of a keyed Table;FormattedText's exact marker and Bytes host, mandatory static ReadOnly, and runtime-use restrictions under the formatted-output contract;- exact package-only placement plus syntax, non-empty unique keys, source-value,
and default-document membership checks for
StringResources, without requiring every formatted declaration to have a catalog or proving runtime resource-key membership; - chart protocol placement, unit/composition shape, channel multiplicity and scalar compatibility, mark geometry, scale grammar/configuration, and shared scale consistency;
- foreign identity consistency and top-level type deduplication.
Language parsers handle native syntax, resolution, source traversal, and extraction, including exact root exports and source representation. They do not provide parallel implementations of schema-attribute rules decidable from the emitted result and dependencies. Studio owns those shared semantic checks, reports their available source file and one-based line, and strips all transient source metadata before artifact sorting, hashing, comparison, or publication.
External references are resolved against the declared exact dependencies for validation and use. Resolution does not modify or republish persisted schemas or documents.
Task-value validation is a separate phase. It applies the generated model to actual values and checks nullability, scoped uniqueness, endpoint base objects, enum membership, numeric type/range/step constraints, string and byte lengths, list size, and table structure. Schema validity does not imply that every runtime value is valid.
10. Localized package documents
The presentation contract is:
interface PackageDocumentJson {
DocumentVersion: "0.0.2";
Locale: string;
DisplayName: string;
Description: string;
StringResources: Record<string, string>; // package-owned LMF1 templates
List: TypeDocumentJson[]; // functions; historical field name
Types: TypeDocumentJson[];
}
interface TypeDocumentJson {
Name: string; // stable function/type NativeName
DisplayName: string; // localized natural name
Description: string;
List: FieldDocumentJson[];
}
interface FieldDocumentJson {
Type: number; // corresponding schema-node ID
Name: string; // localized natural field/member name
Description: string;
Enum?: FieldDocumentJson[];
}
Document format 0.0.2 uses a single package-owned StringResources map and
removes the former per-function/per-type Strings members. It also supports
flattened custom string properties. A custom
property is encoded directly in the object it documents, beside DisplayName
and Description; there is no nested JSON Extensions member. Implementations
may expose those flattened values through an internal extensions map. For
example:
{
"Name": "configure",
"DisplayName": "Configure",
"Description": "Lets the user choose how this activity operates.",
"Hint": "Choose at least one item.",
"List": []
}
Parsers read custom properties from trailing comment sections. A marker must be
the only content on its line and has the form [PropertyName]. Its value starts
on the next line and continues through the line before the next marker or the
end of that comment section. Multiple markers are allowed. Values are strings;
leading and trailing blank space is removed while internal newlines are
preserved. With an array ---- divider, the text and custom properties above
the divider document the array container, and those below it document the
element.
In TypeScript JSDoc, ordinary label/description text and custom-property blocks
must precede the first @Libertas... tag because document extraction stops at
that first schema tag.
Items
Items shown to the user.
[Hint]
Select one or more items.
[Help]
The order is significant.
----
Item
One item.
[Hint]
Enter a single value.
The following case-sensitive custom-property names have standard Libertas semantics:
| Document property | Valid owner | Meaning |
|---|---|---|
[DefaultText] |
Function document, effective String field, or built-in Endpoint field carrying EndpointServer |
One non-empty localized line of initial task name or field text. Unlike schema Default, it may differ by locale. |
[TrueText], [FalseText] |
Field document for an effective Boolean field |
One non-empty localized line labelling true or false; either selects explicit choices. |
[NullText] |
Field document for an effectively nullable field | One non-empty localized line labelling explicit null: the null branch of the field's Nullable choice and its displayed null; required on an editable Nullable, with localized None only as a client fallback. |
[SetText] |
Field document for an effectively nullable field | One non-empty localized line labelling the value branch of the field's Nullable choice; required on an editable Nullable whose control draws a value answer (a Number, String, Bytes, date or time, Table or List), with localized Specify only as a client fallback. |
All standard properties are flattened into their owning document objects and translated like other custom properties. They are invalid on other owners; values contain no carriage return or newline after normal parser whitespace removal. Omit a property when its intended text exactly matches the ordinary localized fallback. See standard properties for authoring, initialization, and the distinction between null and other values.
Client initialization order is normative. Persisted or user-assigned text has
absolute priority and cannot be changed programmatically by default
application, locale/document/schema refresh, migration, or upgrade. A saved
empty string is also persisted text and must remain empty. Only when no
persisted value exists may a client initialize a new String or Endpoint-server
presentation value. Where the shared copy rules
leave that node to ordinary initialization, an applicable schema Fixed/Default value wins,
localized DefaultText is the next fallback, and the ordinary empty or
localized field-label fallback is last. For a new blank task name, localized
DefaultText wins, followed by the localized function DisplayName, then
the native function name.
For example, a function and one of its String parameters may produce:
{
"Name": "configure",
"DisplayName": "Configure watering",
"Description": "Lets the user choose a watering schedule and affected area.",
"DefaultText": "Back garden watering",
"List": [
{
"Type": 0,
"Name": "Schedule name",
"Description": "The name people use to recognize this watering schedule.",
"DefaultText": "Weekday schedule"
}
]
}
DisplayName, Description, List, StringResources, Strings, Enum, and
DocumentVersion are globally reserved custom-property names. A key that is
structural at the current level, including Locale, Types, Name, or Type,
also cannot be used as a custom property. Names are case-sensitive. Parsers
reject collisions; consumers may impose additional validation on properties
they recognize, but must preserve valid unknown string properties.
Strings is prohibited so it cannot become a misleading custom property. It is
not part of the 0.0.2 document shape.
Important distinctions:
TypeDocumentJson.Nameis the stable native function/type lookup key and is not translated.DisplayName,Description,FieldDocumentJson.Name, enum memberName, and packageStringResourcesvalues are localized. All custom-property string values are localized too, includingDefaultText,TrueText,FalseText, andNullText.FieldDocumentJson.Typeis named historically; it stores the schema node's numericID, not its runtimeTypestring.- Only enumeration field documents have
Enum. -
Package `StringResources` stores notification, data-name, permission-reason,
FormattedText, and chart templates by stable package-local key. Function and type documents have no resource table. A protocol peer already knows the task's App package and therefore needs no type-owner metadata or resource FQN. - Type documents contain the root type entry plus flattened inline built-in descendants. Function documents flatten parameters similarly. Traversal stops at named references because local types have their own local documents and FQN references resolve to the owning package's document.
- TypeScript discriminator fields receive an empty display name and description so compiler representation details do not leak into user-facing text.
-
`DisplayName` and `Description` are required string keys at package and
function/type levels.
NameandDescriptionare required on field and enum documents. Empty strings are valid values. - Every long
Descriptionuses HTML-style flow. One logical CR/LF break is canonicalized to exactly one space; a run of two or more logical breaks is canonicalized to one LF paragraph break. CRLF counts as one logical break. - Every document carries
DocumentVersion: "0.0.2". Missing or different versions and everyStringsproperty are rejected; there is no compatibility or migration interpretation.
Empty text is a structural representation allowance, not an authoring recommendation. A codebot must give every authored schema-visible document node a non-empty natural label and description, except a parser-suppressed compiler discriminator field. The complete package/function/type/field/member/variant/ List, accessibility, custom-property, and StringResources prose contract is in the schema documentation contract. The source annotation reference lists every currently supported public TypeScript and Rust spelling.
Document normalization sorts function and type documents by stable Name.
Document patching treats the newly generated default document as the structural
source and a translated document as the text target:
- obsolete functions, types, fields, enum entries, and string keys are removed;
- missing structures and strings are copied from the default locale;
- existing localized text is preserved whenever its key is present, including an intentionally empty string;
- custom-property keys follow the generated default document: missing keys are copied, translated values (including empty strings) are preserved, and obsolete keys are removed;
- field and enum IDs are refreshed from the new schema;
- when old and new signatures are supplied, same-named types are disambiguated by namespace, and fields and enum entries follow their complete native schema paths across insertions, deletion, reordering, and ID regeneration;
- without an old signature, localized field text is retained only for an exact numeric shape match. A changed shape receives default-locale field text rather than risking a positional mistranslation.
This is why schema NativeName values must remain stable while natural localized
names belong in the document.
For the VS Code export/import handoff and the rules a translation bot must follow, see the Libertas Schema Document Translation Guide.
11. Determinism, equality, and revisions
11.1 Deterministic sorting
Before hashing or deployment, Studio sorts:
TypesbyNativeName, thenNamespace, thenID;FunctionsbyNativeName;- every attribute array by
Name, thenValue; AppDependenciesby package identity, then exactVersion.
It intentionally does not sort Children or function parameters.
Key variants and key fields therefore retain their declared binary order;
sorting is idempotent and never rearranges a key layout.
11.2 Schema equality
Structural equality resolves named references recursively and compares effective
runtime Type, attributes when requested, and ordered children. IDs,
NativeType, and ordinary field/type names do not define non-strict structural
equivalence. Function names must match. Strict equality additionally requires the
resolved and reference-node NativeName values to match.
Two unresolved external reference nodes can still be compared by canonical FQN; their owned structures are compared when the corresponding package resources are available in a materialized graph.
For union variants, the discriminator field named by UnionTagName is filtered
before language-neutral structure comparison. Recursive references use a visited
pair map to terminate cycles.
11.3 Revision digest
Revision.ContentSha256 has the form sha256: followed by 64 lowercase
hexadecimal characters. It covers the final schema only, after presentation
attributes have moved into the Display Template, so a template change never
changes it. Studio computes it by:
- deep-copying the complete schema;
- recursively removing transient source-location metadata and removing
Revision; - normalizing and deterministically sorting the schema;
- serializing canonical JSON with object keys sorted, undefined object members
omitted, undefined array entries represented as
null, and array order kept; - hashing the canonical JSON bytes encoded as UTF-8.
Indentation, CRLF/LF choice, and original object-key order therefore do not affect the digest. String contents, attributes, IDs, and semantically ordered arrays do. Key discriminator, field-order, numeric-width, byte-capacity and nullability changes affect this digest. Document translations are separate artifacts and do not affect it. Structural equality is not a substitute for checking whether an already published key layout preserves every encoded byte.
Revision.SourceTimestampUtcMs is the latest relevant source or project-manifest
mtime discovered by Studio. It is diagnostic and is not used to order unequal
schemas. Direction is derived from the last synchronized digest:
| Condition | Parity |
|---|---|
| Local digest equals Hub digest | equal |
| Local equals last synchronized digest | hub-newer |
| Hub equals last synchronized digest | local-newer |
| Neither equals the synchronized digest | diverged |
| Digests differ and no valid synchronized digest exists | unknown |
Both timestamp and digest are validated before parity comparison. A missing, malformed, or stale digest requires regeneration.
12. Schematic example
The following selected metadata projection omits generated Revision and the
function's required persistence binding and key/value Unions, described under
persistent data schemas. Those declarations
are required when materializing a complete App schema.
{
"AppDependencies": [
{
"package": "libertas-shared_types",
"Version": "20260824.131415.016.3"
}
],
"Types": [
{
"ID": 0,
"NativeName": "Settings",
"Type": "Table",
"BuiltinType": true,
"Children": [
{
"ID": 1,
"NativeName": "choices",
"Type": "List",
"BuiltinType": true,
"Children": [
{
"ID": 2,
"NativeName": "",
"Type": "String",
"BuiltinType": true,
"Children": [],
"Attributes": [],
"NativeType": "",
"Namespace": ""
}
],
"Attributes": [],
"NativeType": "",
"Namespace": ""
},
{
"ID": 3,
"NativeName": "selectedChoice",
"Type": "Number",
"BuiltinType": true,
"Children": [],
"Attributes": [
{ "Name": "EnumSource", "Value": "$.choices" },
{ "Name": "NumberStep", "Value": "1" },
{ "Name": "NumberType", "Value": "u16" }
],
"NativeType": "",
"Namespace": ""
}
],
"Attributes": [],
"NativeType": "",
"Namespace": ""
}
],
"Functions": [
{
"NativeName": "configure",
"Parameters": [
{
"ID": 0,
"NativeName": "settings",
"Type": "Settings",
"BuiltinType": false,
"Children": [],
"Attributes": [],
"NativeType": "",
"Namespace": ""
}
],
"Attributes": [
{ "Name": "Permissions", "Value": "devices.read,devices.control" },
{ "Name": "AccessHost", "Value": "api.devices.example" }
]
}
],
"Attributes": [
{ "Name": "DefaultLocale", "Value": "en" },
{ "Name": "StringResources", "Value": "api.devices.example,devices.control,devices.read" }
]
}
Its English document can independently use natural text:
{
"DocumentVersion": "0.0.2",
"Locale": "en",
"DisplayName": "Device settings",
"Description": "Configures device behavior.",
"StringResources": {
"api.devices.example": "Synchronize device capabilities with the vendor service.",
"devices.read": "Read current device settings before showing or changing them.",
"devices.control": "Apply the device settings selected by the user."
},
"List": [
{
"Name": "configure",
"DisplayName": "Configure",
"Description": "Applies the selected settings.",
"List": [
{ "Type": 0, "Name": "Settings", "Description": "Settings to apply." }
]
}
],
"Types": [
{
"Name": "Settings",
"DisplayName": "Settings",
"Description": "Available configuration choices.",
"List": [
{ "Type": 0, "Name": "Settings", "Description": "" },
{ "Type": 1, "Name": "Choices", "Description": "Available choices." },
{ "Type": 2, "Name": "Choice", "Description": "One available choice." },
{ "Type": 3, "Name": "Selected choice", "Description": "Index of the selected choice." }
]
}
]
}