Skip to content

Execution Model ​

Understand how EnsuraScript orders guarantees and enforces them continuously.

The Execution Pipeline ​

When you run ensura run config.ens, this happens:

1. Lexer       → Tokenize source code
2. Parser      → Build AST
3. Binder      → Resolve implicit subjects, expand policies
4. Imply       → Add implied guarantees
5. Graph       → Build dependency graph
6. Topo Sort   → Determine execution order
7. Planner     → Create sequential execution plan
8. Runtime     → Execute plan in continuous loop

Dependency Graph ​

Graph Construction ​

The graph builder collects all guarantees and creates edges for:

  1. Implication dependencies - Implied conditions → original
  2. Explicit requires - ensure A requires B creates edge B → A
  3. Temporal ordering - ensure A after B creates edge B → A
  4. Priorities - Invariants get +1000 priority

Each guarantee gets a unique ID:

<condition>:<subject>@<position>

Example:

exists:file("/var/app/config.yaml")@line12
encrypted:file("/var/app/secrets.db")@line15

Graph Edges ​

Given:

ens
ensure encrypted on file "secrets.db"  # Line 5
ensure backed_up on file "secrets.db" requires encrypted  # Line 6

Graph edges:

exists:file("secrets.db")@5 → encrypted:file("secrets.db")@5
readable:file("secrets.db")@5 → encrypted:file("secrets.db")@5
writable:file("secrets.db")@5 → encrypted:file("secrets.db")@5
encrypted:file("secrets.db")@5 → backed_up:file("secrets.db")@6

Topological Sort ​

Kahn's Algorithm ​

EnsuraScript uses Kahn's algorithm for topological sorting:

  1. Start with all nodes that have no incoming edges
  2. Remove a node and add to output
  3. Remove all edges from that node
  4. Repeat until graph is empty
  5. If graph is not empty → cycle detected → ERROR

Priority-Based Tie-Breaking ​

When multiple nodes have no incoming edges, choose by priority:

  • Invariants: priority 1000
  • Regular guarantees: priority 0

Higher priority = executed first.

Cycle Detection ​

If dependencies create a cycle:

ens
ensure A requires B
ensure B requires C
ensure C requires A

Compilation fails:

Error: Dependency cycle detected: A → B → C → A

Execution Plan ​

The planner takes the topologically sorted guarantees and creates a sequential plan:

bash
ensura plan config.ens

Output:

Execution Plan (6 steps):

1. [fs.native] ensure exists on file "secrets.db"
2. [fs.native] ensure readable on file "secrets.db"
3. [fs.native] ensure writable on file "secrets.db"
4. [AES:256] ensure encrypted on file "secrets.db" with AES:256 key "env:KEY"
5. [posix] ensure permissions on file "secrets.db" with posix mode "0600"
6. [fs.native] ensure backed_up on file "secrets.db"

This is the exact order the runtime executes.

Runtime Loop ​

Continuous Enforcement ​

loop forever:
  for each guarantee in plan:
    check()
    if not satisfied:
      if dry_run:
        mark VIOLATED
      else:
        enforce()
        recheck()
        if still not satisfied:
          retry up to max_retries
          if still failed:
            trigger violation handler
            mark FAILED
        else:
          mark REPAIRED
    else:
      mark SATISFIED
  
  sleep(interval)

Configuration ​

  • --interval - Time between loops (default: 30s)
  • --retries - Max retry attempts (default: 3)
  • --dry-run - Check only, no enforcement

Check vs. Enforce ​

Check:

  • Read-only operation
  • Returns true if guarantee is satisfied
  • Example: Does file exist? Is it encrypted?

Enforce:

  • Makes the guarantee true
  • Can modify the system
  • Example: Create file, encrypt it

Re-check:

  • After enforcement, verify it worked
  • If not, retry or trigger violation handler

Example Execution ​

Given:

ens
on file "secrets.db" {
  ensure exists
  ensure encrypted with AES:256 key "env:SECRET_KEY"
  ensure permissions with posix mode "0600"
}

First Loop (file doesn't exist) ​

Step 1: ensure exists
  Check: os.Stat("secrets.db") → NOT FOUND
  Enforce: os.Create("secrets.db")
  Re-check: os.Stat("secrets.db") → FOUND
  Status: REPAIRED

Step 2: ensure encrypted
  Check: Read file, look for magic header → NOT FOUND
  Enforce: Read plaintext, encrypt with AES-256-GCM, write with magic header
  Re-check: Read file, look for magic header → FOUND
  Status: REPAIRED

Step 3: ensure permissions
  Check: os.Stat().Mode() → 0644 (default from create)
  Enforce: os.Chmod("secrets.db", 0600)
  Re-check: os.Stat().Mode() → 0600
  Status: REPAIRED

Sleep 30 seconds...

Second Loop (file exists, all satisfied) ​

Step 1: ensure exists
  Check: os.Stat("secrets.db") → FOUND
  Status: SATISFIED

Step 2: ensure encrypted
  Check: Read file, look for magic header → FOUND
  Status: SATISFIED

Step 3: ensure permissions
  Check: os.Stat().Mode() → 0600
  Status: SATISFIED

Sleep 30 seconds...

Third Loop (user changed permissions) ​

Step 1: ensure exists
  Check: SATISFIED

Step 2: ensure encrypted
  Check: SATISFIED

Step 3: ensure permissions
  Check: os.Stat().Mode() → 0777 (user changed it!)
  Enforce: os.Chmod("secrets.db", 0600)
  Re-check: os.Stat().Mode() → 0600
  Status: REPAIRED

Sleep 30 seconds...

This is drift detection and automatic remediation.

Parallel Execution (Future) ​

Currently, guarantees execute sequentially in topological order.

The parallel block is parsed but not yet executed in parallel:

ens
parallel {
  ensure reachable on http "https://api1.example.com"
  ensure reachable on http "https://api2.example.com"
  ensure reachable on http "https://api3.example.com"
}

Future versions will execute independent guarantees concurrently.

Performance Considerations ​

  • Graph building - O(V + E) where V = guarantees, E = dependencies
  • Topological sort - O(V + E)
  • Runtime loop - O(V) per iteration
  • Check operations - Typically O(1) (file stat, HTTP GET, etc.)
  • Enforce operations - Varies (file creation is fast, encryption is slower)

For 100 guarantees with 200 dependencies:

  • Compilation: < 100ms
  • Runtime loop: ~1-5s depending on handlers

Observability ​

View what's happening:

bash
# See execution order
ensura plan config.ens

# See expanded guarantees
ensura explain config.ens

# See dependency graph as DOT
ensura compile config.ens --graph

# Run with verbose output
ensura run config.ens --verbose

Next Steps ​

You've completed the Learn section! Continue to the Reference section for complete syntax documentation.

Released under the MIT License.