Schema Validation
Central schemas describe the shape of your graph: which resource types exist, what properties and relations they must have, and what lifecycle capabilities they expose. Rescile validates every module footprint against these schemas before the graph is built or served.
Schema files use the .schema.json extension and are loaded from local paths, Git URLs, or declared directly in a module’s module.toml.
Schema File Format
A minimal schema file looks like this:
{
"schema_id": "rescile/infrastructure",
"version": "20260729",
"resources": {
"virtual_machine": {
"required": ["name"],
"properties": {
"name": { "type": "string" },
"cpu_cores": { "type": "integer" },
"ram_gb": { "type": "integer" }
},
"relations": {
"network": { "target": "network", "required": true },
"DEPLOYED_AS": {
"target": "provider",
"cardinality": "one",
"property_schema": {
"properties": {
"instance_type": { "type": "string" },
"encrypted": { "type": "boolean" }
},
"required": ["instance_type"]
},
"capabilities": {
"actions": {
"provision": { "description": "Provision this deployment target" }
}
}
}
},
"capabilities": {
"actions": {
"provision": { "description": "Create a new instance" },
"destroy": { "description": "Remove an instance" }
}
}
}
}
}
schema_id/version— uniquely identify the schema.resources.<name>.properties— JSON-Schema-like property definitions (type,enum,required).resources.<name>.relations— relations to other resource types and whether they are required.resources.<name>.relations.<name>.property_schema— optional JSON Schema object describing properties that may be stored on edges with this relation label. Itspropertieskeyword declares per-edge fields; itsrequiredkeyword lists required edge property names.resources.<name>.relations.<name>.capabilities.actions— lifecycle verbs offered on the relation itself (e.g.DEPLOYED_AS.provision).resources.<name>.capabilities.actions— lifecycle verbs the resource supports (e.g.provision,destroy,start,stop).
Declaring Schemas in module.toml
The simplest declaration loads a single schema for the whole module:
schemas = ["./schemas/infrastructure.schema.json"]
[module]
name = "my-module"
version = "1.0.0"
url = "https://github.com/example/my-module"
You can also use explicit declarations to bind a schema to specific resources:
[[schema]]
schema = "./schemas/infrastructure.schema.json"
resources = ["virtual_machine", "network"]
[[implements]]
schema = "rescile/infrastructure"
version = "20260729"
resources = ["virtual_machine"]
Per-asset overrides take precedence:
[assets."virtual_machines.csv"]
schema = "./schemas/custom-vm.schema.json"
If a resource is created by a module but no schema is declared explicitly, the engine falls back to automatic binding: when exactly one loaded schema defines that resource, it is bound automatically. If more than one schema defines it, the resource remains ambiguous.
Model parsing before validation
Model files can contain several column-zero origin_resource declarations. Rescile splits the raw file at each declaration before TOML deserialization, so every origin chunk contributes a separate model definition to the static footprint.
Do not use a generic whole-file TOML parse tree to decide which origin owns a rule. Standard TOML table context can make a later declaration appear nested beneath the preceding table even though rescile recognizes it as a new chunk. Ensure every intended origin_resource begins at column zero, then use rescile-ce validate and saved-graph checks to verify exact rule behavior.
CLI Validation
rescile-ce validate
# Use schemas declared in module.toml
rescile-ce --data-dir ./data validate
# Validate against a specific schema only
rescile-ce --data-dir ./data validate --schema ./schemas/20260729.schema.json
# Validate strictly (CSV cell values must satisfy schema types)
rescile-ce --data-dir ./data validate --schema ./schemas/20260729.schema.json --strict-schemas
rescile-ee validate
The service-oriented edition provides the same command with syslog-style output:
rescile-ee --data-dir ./data validate
rescile-ee --data-dir ./data validate --schema ./schemas/20260729.schema.json --strict-schemas
mod check-schema
You can also trigger validation from the module management subcommand:
rescile-ce mod check-schema --schema ./schemas/20260729.schema.json --strict-schemas
rescile-ee mod check-schema --schema ./schemas/20260729.schema.json --strict-schemas
--schema override behavior
When --schema is passed, only the supplied schema source is used. Schemas declared in any module.toml (module-level declarations and per-asset schema = "..." overrides) are ignored for that run.
What is Validated
The engine walks every module footprint and reports:
- Missing required properties — a required property is not created by any implementing module.
- Missing required relations — a required relation is not present in the graph.
- Missing required edge properties — a relation’s
property_schema.requiredproperty is not provided by any edge created by the module set. - Property type conflicts — when multiple schemas define the same property with incompatible types.
- Relation conflicts — when multiple schemas define the same relation with different targets or cardinality.
- Relation property type mismatches (only with
--strict-schemas) — a provided edge property value does not satisfy the declaredproperty_schematype. - Capability coverage — a resource or relation schema declares a lifecycle verb but no action offers it.
- CSV value conformance (only with
--strict-schemas) — every non-empty CSV cell must satisfy the property’s schema type.
Validation spans all processing phases
Schema validation does not require every resource to have a backing CSV file. The validator builds a static footprint from every phase of the pipeline:
| Phase | Properties come from |
|---|---|
| Asset loading | CSV headers in assets/*.csv |
| Model application | create_resource, copy_property, and link_resources rules in models/*.toml |
| Model application (relations) | relation_properties on create_resource and create_relation on link_resources |
| Compliance application | target.resource and target.policy blocks in compliance/*.toml |
| Contracts | [[contract.write]] record definitions |
A resource created entirely inside a model file — for example, a compute resource produced by a [[create_resource]] rule with no compute.csv — is still checked against the bound schema. A property added to an existing resource during the compliance phase is merged into the same footprint and satisfies the schema requirement.
Properties are accumulated per resource type across every loaded module and every phase, so a missing-property warning is only emitted when no phase in any loaded module provides the required key.
Value checks are limited to raw CSV cells and relation property samples. With
--strict-schemas, type and enum validation run against literal text found inassets/*.csvand against the staticrelation_propertiesmaps declared in models. Values produced by Tera templates outside of these maps are not checked at validation time, because the validator performs static analysis without executing templates or action runners. Structural checks — whether the property or edge property key exists — are always applied across all phases.
Multiple capability offers are expected
It is perfectly fine for several actions to offer the same schema capability. A typical setup has aws, exoscale, and lxd actions all implementing virtual_machine.start. The validator does not warn about multiple offers; it only warns when a declared capability has no implementation.
Ambiguous schemas only warn on conflict
When a resource is defined by more than one schema and the module does not choose one, the schemas are merged. An ambiguity warning is not emitted unless the merge produces a real conflict, such as incompatible property types or relation definitions. If the schemas complement each other, validation proceeds silently.
Schema Inheritance
A resource schema can declare that it implements another resource type. This lets you model a generic intent (e.g. compute) and concrete provider-specific variants (e.g. exoscale_instance, aws_instance) without duplicating every shared property.
{
"schema_id": "rescile/cloud",
"version": "20260729",
"resources": {
"compute": {
"required": ["name"],
"properties": {
"name": { "type": "string" },
"vcpus": { "type": "integer" },
"ram_gb": { "type": "integer" }
}
},
"exoscale_instance": {
"implements": ["compute"],
"properties": {
"instance_type": { "type": "string" },
"template_id": { "type": "string" }
}
}
}
}
The validator expands exoscale_instance with the base compute definition before merging. Inherited properties, relations, and capabilities participate in validation exactly as if they were declared directly on the derived resource. Conflicts between inherited and derived definitions are reported the same way as conflicts across multiple schemas.
Merging Semantics
When multiple schemas (including inherited base schemas) are bound to the same resource, their definitions are merged:
- Required properties — union (a property is required if any schema requires it).
- Property types — intersection of allowed types.
- Enum values — intersection of allowed values.
- Relations — last defined target/cardinality wins; conflicting definitions emit a warning.
- Relation property schemas — union of declared edge properties.
- Capabilities — union of action verbs at both the resource and relation levels.
Generating a Schema from a Module
rescile-ce can generate a starter .schema.json from the local data dir module. It inspects CSV assets, models, relations, and action files to infer resource types, properties, relations, and capabilities.
rescile-ce generate-schema --output ./data/my-module.schema.json
It can also generate a schema for specific resources only:
rescile-ce generate-schema "compute,os,dns"
Options:
--output <FILE>: write the generated schema to a file. If omitted, prints JSON to stdout.--schema-id <ID>: override the schema id (defaults to the module name).--schema-version <VERSION>: override the schema version (defaults to the module version).
rescile-ee does not provide this command.
What gets inferred
| Schema part | Source |
|---|---|
| Resource types | Every resource created by local assets or models. |
| Required properties | The primary key name and properties whose CSV column has no empty cells. |
| Property types | Declared [assets.columns] type when available (deprecated); otherwise inferred from CSV values. |
| Relations | Relations created by models. |
| Capabilities | Prefix-matched [capabilities] entries in action files (e.g. virtual_machine.start). |
Review the generated schema before publishing it: type inference and required-property heuristics are a starting point, not a guarantee.
Note on
[assets.columns]inference: the schema generator still reads legacy[assets.columns]declarations for type hints, but new modules should use central schemas for validation instead.
REST Conformance Endpoint
While rescile-ce or rescile-ee is running, you can request a live conformance report via the REST API:
# Default, non-strict validation
GET /api/validate-workspace
# Include CSV cell value checks
GET /api/validate-workspace?strict=true
The response matches the summary produced by rescile-ce validate:
{
"error_count": 0,
"warning_count": 0,
"info_count": 0,
"issues": []
}
Each issue has severity, code, and message. Because schema validation performs static analysis of the workspace, it is exposed as an operational REST endpoint rather than through the GraphQL resource API.
Exit Codes
A validation run exits with code 0 if no errors are found. Warnings and info notes do not cause a non-zero exit unless --strict-schemas promotes a warning to an error. A run that encounters one or more errors exits non-zero.