Intent vs. Reality
Every cloud orchestration task has two sides:
- Intent — the generic architectural role a resource plays (“I need a compute instance called
web-01with 4 vCPUs and 16 GiB RAM”). - Reality — the concrete, provider-specific object that satisfies that intent (“an Exoscale medium instance with template
ubuntu-22.04and provider idi-8f7a…”).
Rescile supports two complementary ways to model that relationship in the graph.
Approach A: Specific resources (entity-centric)
The physical cloud object is a first-class resource. The generic intent resource links to a dedicated provider-specific resource through a relationship such as DEPLOYED_AS.
[compute: web-01] --(DEPLOYED_AS)--> [exoscale_instance: web-01-exo]
This is the right choice when:
- You want a clear boundary between intent and physical state.
- You need to attach provider-specific properties, lifecycle actions, and compliance rules to the concrete resource.
- You want one intent fanning out to multiple concrete targets (multi-cloud HA, canary deployments).
Schema inheritance makes this pattern maintainable. A generic compute schema declares shared properties; provider schemas such as exoscale_instance or aws_instance declare implements = ["compute"] and inherit them.
{
"resources": {
"compute": {
"properties": { "vcpus": { "type": "integer" }, "ram_gb": { "type": "integer" } }
},
"exoscale_instance": {
"implements": ["compute"],
"properties": { "instance_type": { "type": "string" } }
}
}
}
Approach B: Relation properties (relation-centric)
The graph keeps only the generic intent resource. Provider-specific configuration is stored directly on the relation that connects the intent to the provider target.
[compute: web-01] --(DEPLOYED_AS { instance_type: "medium", encrypted: true })--> [provider: exoscale]
This works best when:
- The architecture itself is the source of truth.
- You want a compact graph without a resource per provider object.
- Provider modules can inject configuration without declaring new resource schemas.
Central schemas can describe relation properties through property_schema, and validation will enforce required relation properties as well as type-check their values under --strict-schemas.
{
"resources": {
"compute": {
"relations": {
"DEPLOYED_AS": {
"target": "provider",
"property_schema": {
"properties": {
"instance_type": { "type": "string" },
"encrypted": { "type": "boolean" }
},
"required": ["instance_type"]
}
}
}
}
}
}
Choosing between the approaches
The approaches are not mutually exclusive. A workspace can use resources for long-lived cloud objects and relation properties for lightweight provider bindings.
| Concern | Approach A | Approach B |
|---|---|---|
| Graph growth | Adds a resource per concrete object | Minimal graph |
| Schema surface | One schema per provider/object type | Schemas describe only generic types |
| Multi-target deployments | One intent → many concrete resources | One intent → many relations |
| Physical state history | Replace or historicize concrete resources | Couples intent and deployment state |
| Query ergonomics | Union across provider types | Read generic resource, inspect relations |
The schema engine supports both: inheritance and merging for Approach A, typed relation property schemas and relation-targeted compliance for Approach B.