Intent Contracts
Intent contracts define typed, validated write operations that modify workspace data while strictly enforcing schema invariants and business rules.
Instead of allowing human customizers or AI agents to make arbitrary, unvalidated edits to raw CSV files or JSON lookup tables, Rescile exposes declarative contracts in contracts/*.toml. Contracts are accessible via:
- REST API:
POST /api/contracts/:name - GraphQL: Schema-generated mutation resolvers
- MCP Tools:
invokeContract(discovered vialistContracts)
Why Contracts Over Raw Asset Edits?
- Intent-Based Boundary: Callers specify what business intent they want to achieve (e.g.
ProvisionApplication,AssignFeeGlAccount,AdjustInterestTier), rather than knowing physical file structures or database column mappings. - Strict Input Validation: Fields are verified against regular expressions (
pattern), numeric bounds (minimum/maximum), semantic formats (format = "date"), and live graph lookups (enum_lookup). - Dual Mutable Targets: Contracts can mutate either graph entity ground truth (
target_asset = "fee_schedules.csv") or reference lookup tables (target_input = "rules.json"). - Atomic Rollback: If a contract performs multiple writes across multiple files, all writes are staged in memory. If any write fails, the entire transaction rolls back cleanly without leaving corrupted files.
- Deterministic Upsert: Explicit
action = "upsert"semantics with composite key matching (key_fields = [...]).
Contract Definition File Structure
Contracts are placed in the contracts/ directory of your data directory or module:
my-project/data/contracts/
├── assign_gl_account.toml
├── update_fee_schedule.toml
└── mutate_determination_rule.toml
Complete Example
name = "UpdateFeeSchedule"
description = "Updates fee schedule parameters or creates a new tier if none exists"
# --- Input Parameter Validation Schema ---
[[input]]
name = "servicing_model"
type = "string"
required = true
# Dynamic validation: input must match an existing code entry in the graph or asset file
enum_lookup = 'code_table_entries[code_table="CODE_SERVICING_MODEL"]:intl_id'
[[input]]
name = "currency"
type = "string"
required = true
pattern = "^[A-Z]{3}$" # Strict 3-letter ISO currency code
[[input]]
name = "rate"
type = "float"
required = true
minimum = 0.0001
maximum = 0.1000
[[input]]
name = "valid_from"
type = "string"
required = true
format = "date" # Validates YYYY-MM-DD date representation
[[input]]
name = "gl_account"
type = "string"
required = false
pattern = "^GL_[0-9]{6}$"
# --- Write Operations ---
[[writes]]
target_asset = "fee_schedules.csv"
action = "upsert"
key_fields = ["servicing_model", "currency", "valid_from"]
record = {
servicing_model = "{{ input.servicing_model }}",
currency = "{{ input.currency }}",
valid_from = "{{ input.valid_from }}",
rate = "{{ input.rate }}",
gl_account = "{{ input.gl_account | default(value='GL_DEFAULT_SUSPENSE') }}"
}
Input Validation Directives
Each [[input]] block supports the following constraints:
| Field | Type | Description |
|---|---|---|
name |
String | Parameter name accessed in templates via {{ input.<name> }}. |
type |
String | Primitive type: "string", "integer", "float", "boolean", "array", "object". |
required |
Boolean | Whether the parameter must be provided. |
pattern |
String | Regex pattern used to validate string values (e.g. ^[A-Z]{3}$). |
minimum |
Float / Int | Minimum allowable value for numeric inputs (inclusive). |
maximum |
Float / Int | Maximum allowable value for numeric inputs (inclusive). |
format |
String | Semantic format validation: "date" (YYYY-MM-DD), "timestamp" (RFC3339), "uuid", "email". |
allowed_values |
Array | Static enumeration of permitted string or number values. |
enum_lookup |
String | Dynamic enumeration DSL query checked against graph nodes or workspace files. |
Dynamic enum_lookup
enum_lookup validates that an input value exists dynamically in the active graph or in workspace assets/inputs. It uses a string query expression:
target[filter_property="filter_value"]:property_path
target: Vertex label in the in-memory graph (e.g.,code_table_entry) or asset filename without extension (e.g.,code_table_entriesforassets/code_table_entries.csvorinput/code_table_entries.json).[filter_property="filter_value"](optional): Filters records/vertices so only entities wherefilter_property == filter_valueare considered.:property_path(optional, defaults to"name"): Property or column whose values form the allowed enumeration set. Supports dotted path traversal into arrays of objects (e.g.:entries.intl_id).
Examples
1. Filtered CSV Asset / Table Lookup: When a single table or asset contains entries across multiple domains or types (e.g., code table entries), use a filter to restrict allowed values to the relevant group:
[[input]]
name = "servicing_model"
type = "string"
required = true
description = "Internal ID of the servicing model"
enum_lookup = 'code_table_entries[code_table="CODE_SERVICING_MODEL"]:intl_id'
Without the [code_table="..."] filter, code_table_entries:intl_id would collect intl_id from every code table in the file (e.g., client segments, currency codes), allowing unintended values.
2. Direct Graph Entity Lookup:
Extracts unique values of code from all vertices of type currency:
[[input]]
name = "currency"
type = "string"
required = true
enum_lookup = "currency:code"
3. Nested Property Array Traversal: If graph vertices or JSON inputs hold nested arrays of objects:
[[input]]
name = "tier_id"
type = "string"
required = true
enum_lookup = "pricing_model[status=active]:tiers.tier_id"
4. Static JSON Lookup Table:
Against input/cost_centers.json:
[[input]]
name = "cost_center"
type = "string"
required = true
enum_lookup = "cost_centers:code"
Resolution Order & Best Practices
- In-Memory Graph First: Rescile checks loaded graph vertices where
vertex.label == target. - Data Directory Fallback: If no vertices match, Rescile looks for
assets/<target>.csvorinput/<target>.jsonin the active data directory. - Resource Type vs Asset Name: If an asset is remapped in
module.tomlviaresource_type = "code_table_entry", graph vertices receive the singular label (code_table_entry), while the CSV file on disk remains plural (assets/code_table_entries.csv). Using the asset target name with a filter (e.g.,code_table_entries[code_table="..."]:intl_id) ensures correct fallback resolution even when the relation column has been converted into graph edges.
Dual Mutation Targets
Rescile maintains a strict distinction between graph entities and reference lookups:
1. Asset Ground Truth (target_asset)
Modifies CSV files in assets/*.csv. Rows become graph vertices during ingestion.
[[writes]]
target_asset = "applications.csv"
action = "add"
record = { name = "{{ input.name }}", tier = "{{ input.tier }}" }
2. JSON Reference Tables (target_input)
Modifies JSON files in input/*.json. These files serve as reference dictionaries for models via read_input_json or json! without creating graph nodes.
Supports dictionary update (merging keys) and array element updates:
[[writes]]
target_input = "determination_matrix.json"
action = "change"
lookup_by = { rule_code = "{{ input.code }}" }
record = { fallback_rate = "{{ input.new_rate }}" }
Write Actions & Composite Keys
Contracts support four write actions:
add: Appends a new record to the target. Fails if a record with the same primary key already exists.change: Modifies an existing record matchinglookup_byorkey_fields.delete: Removes matching records.upsert: Replaces the matching record if it exists, or appends a new record if it does not.
Composite Keys
For tables where records are identified by multiple fields (e.g., banking determination rules or multi-tenant configurations), pass key_fields:
[[writes]]
target_asset = "fee_determination.csv"
action = "upsert"
key_fields = ["servicing_model", "currency", "valid_from"]
record = {
servicing_model = "{{ input.servicing_model }}",
currency = "{{ input.currency }}",
valid_from = "{{ input.valid_from }}",
rate = "{{ input.rate }}"
}
Invocation Examples
Via MCP (invokeContract)
{
"contract_name": "UpdateFeeSchedule",
"inputs": {
"servicing_model": "FAMILY_OFFICE",
"currency": "EUR",
"rate": 0.015,
"valid_from": "2026-04-01",
"gl_account": "GL_400100"
}
}
Via HTTP REST
curl -X POST http://localhost:8080/api/contracts/UpdateFeeSchedule \
-H "Content-Type: application/json" \
-d '{
"servicing_model": "FAMILY_OFFICE",
"currency": "EUR",
"rate": 0.015,
"valid_from": "2026-04-01"
}'