Schema load validation and the write contract
Infrahub now validates every schema submitted to POST /api/schema/load against a
user-facing write contract. The contract defines which fields a user may set, which
values those fields accept, and which fields are required. Invalid values are reported
as field-level errors naming the field and the value that was rejected.
Fields a user may not set are reported and dropped. A read-only field — one Infrahub returns and computes itself — is reported as a warning, so reading a schema back from Infrahub, editing it, and loading it again keeps working without stripping anything first.
This guide explains what changed, who is affected, and how to check a payload before submitting it.
Two categories of payload that earlier versions accepted are now rejected: a payload whose
settable values are invalid (out of the allowed set, out of range, or the wrong type), and
attribute parameters that belong to a different attribute kind. Read-only fields are not
one of those categories — they are accepted with a warning.
What changed​
Schema fields are now classified by who may see or set them:
- write — fields a user may submit, for example
name,namespace,attributes,relationships, and each attribute's or relationship's settable properties. - read — fields Infrahub computes and returns on
GET /api/schema, but that a user may not set. These includeinherited,used_by,hierarchy, and the derivedkindon a node or generic. - internal — fields used only inside the backend and never exposed.
POST /api/schema/load validates each submitted node, generic, and extension against the
write contract.
Read-only fields are reported as a warning​
A read-only field is dropped before the schema is applied and reported on the response, so the submitted value never takes effect but you are told it was ignored. This happens at every level of the payload: on nodes, generics, extensions, and on their nested attributes, relationships, parameters and choices.
That means the read-back, edit, re-load round trip works as it always did. A schema fetched
with GET /api/schema carries inherited, used_by, and the derived kind; submitting it
back unchanged is accepted, Infrahub re-derives those fields itself, and the response lists
one warning per read-only field naming the kinds that carried it:
'inherited' is a read-only field, the submitted value is ignored [InfraDevice.name, InfraDevice.interfaces]
Alongside the read-only fields listed above, this also covers a field that belongs somewhere
else in the contract rather than at the place it was used: the id and state bookkeeping
that a schema dumped from Infrahub's own models carries on nested blocks, and a field
belonging to another variant of the same block — for example transform on a computed
attribute whose kind is Jinja2.
infrahubctl schema load and infrahubctl schema check print these warnings after loading;
infrahubctl validate schema prints them without contacting a server.
Unrecognized fields are rejected​
A field the contract does not know about at all — a typo, or a field removed in a newer version — is an error naming the field path and the value received:
nodes[0].attributes[0].optionl: Unknown field, it is not part of the schema (received: True)
Earlier versions rejected these too, so this is not a change in whether the payload loads — only in how the problem is reported. The path is now field-level, and the same message is available offline before you submit.
Attribute parameters that belong to a different attribute kind are a change: they
configure nothing on the kind they were set on, and earlier versions accepted them and then
discarded the values, so the setting silently had no effect.
nodes[0].attributes[0].parameters.start_range: Unknown field, it is not part of the schema (received: 1)
Invalid values are rejected​
What the contract does reject is a field a user may set carrying a value it cannot hold. These two are new:
- A constrained field set outside its allowed values, for example a relationship
cardinalityor an attributekind. - A value out of range or of the wrong type for its field, for example an attribute
namethat is shorter than the minimum length or does not match the allowed pattern.
These two were already rejected and are listed only so the contract reads completely:
- A missing required field, for example a node without a
name, or an attribute without akind. - A missing
versionat the root of the payload.
Each rejection names the field path and, where applicable, the value received:
nodes[0].relationships[0].cardinality: Input should be 'one' or 'many' (received: 'several')
nodes[0].attributes[0].Text.name: String should match pattern '^[a-z0-9\_]+$' (received: 'MyName')
nodes[0].attributes[0].Text.order_weight: Input should be a valid integer, unable to parse string as an integer (received: 'high')
nodes[0].name: Field required
version: Field required
An invalid attribute kind is reported on the attribute itself rather than on the kind
field, because kind selects which attribute contract applies. The message lists every
valid kind:
nodes[0].attributes[0]: Input tag 'NotAKind' found using 'kind' does not match any of the expected tags: <AttributeKind.TEXT: 'Text'>, <AttributeKind.TEXTAREA: 'TextArea'>, <AttributeKind.NUMBER: 'Number'> (…) (received: {'name': 'name', 'kind': 'NotAKind'})
GET /api/schema is unchanged: it still returns the read-only fields such as inherited
and used_by.
Who is affected​
You are affected if you submit schemas to POST /api/schema/load — directly, through
infrahubctl schema load, through a repository import, or through the Python SDK — and your
payload sets a value the write contract does not accept, or sets attribute parameters that
belong to a different attribute kind. Values that a previous version stored, or accepted and
discarded, without complaint now produce an error instead.
You are not affected by read-only fields. A payload that echoes back derived or read-only fields still loads, with a warning for each.
Check a payload offline​
The write contract is published as a committed model inside the Python SDK, in
infrahub_sdk.schema.generated.write. The SDK exposes an offline validator that reproduces
the server's verdict without a running server, so you can check and correct a payload before
submitting it.
from infrahub_sdk.schema.validate import validate_schema
# schema is your schema-root payload: {"version": "1.0", "nodes": [...], "generics": [...]}
result = validate_schema(schema=schema)
for message in result.warning_messages:
print(message)
if not result.valid:
for message in result.messages:
print(message)
For the payload {"version": "1.0", "nodes": [{"name": "Widget", "namespace": "Testing", "relationships": [{"name": "others", "peer": "TestingOther", "cardinality": "several"}]}]}
that prints:
nodes[0].relationships[0].cardinality: Input should be 'one' or 'many' (received: 'several')
Every entry in result.errors carries the field path in .field and the full message in
.message. result.raise_for_status() raises a ValueError joining all the messages when
the payload is invalid. Each entry in result.warnings additionally carries the schema kind
and element that set the field in .kind and .element, and in .name the field named
relative to them — so a nested block is unambiguous (parameters.id rather than id, which
is settable on the attribute itself).
To make a rejected payload submittable:
- Run
validate_schema()and read the field path in each message to locate the offending value. - Correct the value so it satisfies the contract — pick an allowed value for a constrained field, bring an out-of-range value into range, or supply a missing required field.
- Remove or fix any field reported as unknown. A typo is the usual cause; check the spelling against the node schema reference.
- Add
versionat the root of the payload if it is absent. - Re-run
validate_schema()until it returnsvalid = True, then load.
You do not need to remove read-only fields. Leaving them in place is harmless, and the warnings tell you which values had no effect.
Extra fields are only reported once the payload is otherwise valid, because the contract that applies at a given place in the payload is resolved from the validated document. A payload rejected for another reason reports that reason first, and names its extra fields on the next run.
Because the write model is versioned and shipped inside the SDK package, installing the SDK that matches your Infrahub version gives you the exact contract the server enforces.
Related resources​
- Create and load a schema — the schema loading guide
- Node schema reference — the schema field reference