Writing Heuristics¶
Heuristics are CEL expressions evaluated against the live graph. Write one and CloudSlash flags every matching resource on every scan.
The Evaluation Model¶
The heuristics engine accepts a pointer to the Devi Graph and evaluates registered CEL rules against the property maps of all vertices. Rules execute sequentially; a node can match multiple heuristics.
type HeuristicRule struct {
Name string
Expression *cel.Program
Severity int // 1–100; >= 10 triggers waste flagging
}
Severity levels:
| Severity | Effect |
|---|---|
< 10 |
Informational: logged, not flagged |
10–49 |
FLAG verdict: waste detected, below action threshold |
50–79 |
DELETE verdict: engine recommends removal |
≥ 80 |
BLOCK / CRITICAL: immediate review required |
CEL Expression Variables¶
The node variable is populated with the full vertex property map during evaluation:
| Variable | Type | Description |
|---|---|---|
node.type |
string |
Resource type (e.g., aws_instance, aws_ebs_volume) |
node.properties |
map[string]any |
All provider-specific resource attributes |
node.metrics.cpu_utilization_p99_7d |
float64 |
99th percentile CPU over 7 days |
node.metrics.network_bytes_7d |
float64 |
Total network I/O over 7 days |
node.metrics.invocations_90d |
int64 |
Lambda/function invocations over 90 days |
node.tags |
map[string]string |
Resource tags |
node.cost |
float64 |
Monthly cost estimate in USD |
node.region |
string |
Cloud region |
Writing Heuristic Rules¶
Declare rules in a YAML file with the rules: key:
# rules.yaml
rules:
# Orphaned EBS volume: not attached to any instance
- name: "orphaned_ebs_volume"
severity: 10
expression: "node.type == 'aws_ebs_volume' && node.properties['attachment.instance_id'] == null"
# Idle EC2: consistently low CPU for 7 days
- name: "idle_ec2_instance"
severity: 50
expression: "node.type == 'aws_instance' && node.metrics.cpu_utilization_p99_7d < 2.0"
# Dead Lambda: zero invocations in 90 days
- name: "stale_lambda"
severity: 50
expression: "node.type == 'aws_lambda_function' && node.metrics.invocations_90d == 0"
# Oversized RDS: less than 5% CPU, costs more than $500/mo
- name: "oversized_rds"
severity: 30
expression: "node.type == 'aws_db_instance' && node.metrics.cpu_utilization_p99_7d < 5.0 && node.cost > 500.0"
# Untagged resource: missing required 'env' tag
- name: "missing_env_tag"
severity: 5
expression: "!('env' in node.tags)"
# gp2 volume: should migrate to gp3 for cost savings
- name: "legacy_gp2_volume"
severity: 10
expression: "node.type == 'aws_ebs_volume' && node.properties['volume_type'] == 'gp2'"
Rule Field Reference¶
| Field | Required | Type | Description |
|---|---|---|---|
name |
✓ | string |
Unique rule identifier (snake_case) |
expression |
✓ | string |
CEL expression: must evaluate to bool |
severity |
✓ | int |
Severity score (1–100) |
description |
: | string |
Human-readable rule description |
target_type |
: | string |
Restrict to a specific node type (optimization) |
Validation¶
Rules Before Deployment
Parse and compile CEL expressions without running a scan:
This catches: - YAML syntax errors - Missing required fields - CEL compile-time type errors - Unbalanced parentheses and unknown variable references
Run cs policy lint rules.yaml for additional checks:
- Duplicate rule names
- Very short expressions that likely indicate a typo
- Missing description fields
Testing¶
Against Live Data
Dry-run your rules against the last scan snapshot without modifying any state:
Output shows which nodes would match each rule:
Testing rules.yaml against snapshot (10,420 nodes)...
orphaned_ebs_volume → 23 matches (23 new waste nodes flagged)
idle_ec2_instance → 7 matches (7 resources would get DELETE verdict)
stale_lambda → 41 matches
missing_env_tag → 312 matches (informational, severity < 10)
Total: 71 waste nodes flagged across 4 rules.
Deploying Heuristics¶
Via CLI Flag¶
# Run a one-off scan with custom rules
cs scan --rules rules.yaml --region us-east-1
# Start the daemon with rules loaded
cs daemon --rules rules.yaml
Configuration¶
Persistent
Copy your rules file to ~/.cloudslash/policies/ and reference it in config.yaml:
# ~/.cloudslash/config.yaml
policies:
active:
- my-custom-rules # loads ~/.cloudslash/policies/my-custom-rules/rules.yaml
REST API¶
Runtime Injection
Add rules to a running daemon without restarting:
curl -X POST http://localhost:8080/api/v1/policies \
-H "Content-Type: application/json" \
-d '{
"rules": [{
"name": "idle_ec2_instance",
"expression": "node.type == \"aws_instance\" && node.metrics.cpu_utilization_p99_7d < 2.0",
"severity": 50
}]
}'
CEL Expression Tips¶
Null safety: use has() to check for property existence before access:
String matching: CEL supports contains(), startsWith(), endsWith(), and regex:
Multiple conditions: use && and || with explicit parentheses:
Tag map access: check tag existence before reading value:
Compilation Behavior¶
The engine compiles all CEL ASTs at boot. If any expression fails to compile, the daemon logs a fatal error and exits before accepting connections: ensuring no malformed rules silently skip evaluation. Fix the expression and restart.