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 }
},
"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>.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.
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.
- 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.
- Capability coverage — a schema declares a lifecycle verb (e.g.
virtual_machine.destroy) 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 |
| 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. With
--strict-schemas, type and enum validation run against the literal text found inassets/*.csv. Values produced by Tera templates in models, compliance, or output files are not checked at validation time, because the validator performs static analysis without executing templates or action runners. Structural checks — whether the 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.
Merging Semantics
When multiple 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.
- Capabilities — union of action verbs.
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.