Simulate

4 min read

simulate.yaml is unit testing for your reconciler — without writing Go. It runs the same reconcile loop that executes in production against a fake in-memory cluster, asserts which resources were created and in which cycle, and produces a pass or fail in under a second. No cluster, no Helm, no Docker.

When simulate.yaml is present in a pattern directory, ork registry push runs it automatically before the E2E gate and blocks publication if any assertion fails.

For the complete field reference — spec, expect, ops, crds — see Simulate schema.


How it gates publication

# Simulate runs automatically if simulate.yaml is present — before E2E
ork registry push postgres:v14 ./patterns/postgres/

# Skip the gate
ork registry push postgres:v14 ./patterns/postgres/ --no-simulate

# Skip both gates
ork registry push postgres:v14 ./patterns/postgres/ --force

The result is baked into the OCI artifact as annotations:

io.orkestra.simulate.status     passed | no-assertion | skipped
io.orkestra.simulate.duration   4ms
io.orkestra.simulate.tested_at  2026-05-24T10:00:00Z

ork registry info shows this alongside the E2E status. Consumers can see at a glance what guarantees a pattern carries before importing it.


Simulate status in ork registry info

ork registry info postgres:v14

postgres:v14
  Registry:    ghcr.io/orkspace/orkestra-registry/patterns/katalogs
  Kind:        Katalog
  Digest:      sha256:abc123def456...
  Pushed:      2026-05-24T10:00:00Z
  Size:        14.2 KB

  Description: A declarative PostgreSQL deployment operator

  Simulate:    ✓ Verified · 4ms · tested 2h ago
  E2E:         ✓ Verified · 45s · tested 2h ago

Three possible simulate statuses:

StatusMeaning
✓ Verifiedexpect: blocks present and all assertions passed
⚠ No assertionssimulate.yaml present but no expect: — ran clean, nothing asserted
~ Skipped--no-simulate or --force used at push time

Simulate status is not shown in ork registry list — it appears in ork registry info only, alongside E2E.


simulate.yaml — pattern shape

A minimal simulate spec alongside a Katalog pattern:

apiVersion: orkestra.orkspace.io/v1
kind: Simulate
metadata:
  name: postgres-sim
  description: Verify StatefulSet and headless Service created in cycle 1

spec:
  katalog: ./katalog.yaml
  cr: ./cr.yaml
  cycles: 5

  expect:
    steady: true
    noErrors: true
    ops:
      - cycle: 1
        verb: create
        resource: statefulsets
        name: my-postgres
      - cycle: 1
        verb: create
        resource: services
        name: my-postgres-headless

expect: is optional. Without it, simulate.yaml runs in op-print mode — confirms the reconciler exits clean, records no-assertion in the annotation, and never blocks a push. Add expect: when you know the resource sequence and want it enforced.


Running simulate locally

# Auto-detects simulate.yaml in the current directory
ork simulate

# Explicit path
ork simulate -f ./patterns/postgres/simulate.yaml

# See every recorded op with its cycle number — useful for writing expect: rules
ork simulate --debug-ops

Use --debug-ops to discover what a new or unfamiliar operator creates, then copy those ops into expect: blocks.


Simulate vs E2E

Simulate and E2E answer different questions. Together they give you two quality layers with complementary failure modes.

SimulateE2E
Cluster requirednoyes (kind)
Speed<1s2–5 min
Runs in CI without Dockeryesno
What is assertedreconciler ops by cycleKubernetes resources in a live cluster
Catchestemplate errors, wrong resource names, wrong cycle orderwebhook failures, RBAC gaps, real scheduling issues

The push order reflects this: simulate runs first (instant feedback), E2E runs after (integration truth). A failing simulate blocks E2E — no point spending minutes on a cluster when the reconciler is already broken.


Simulate in CI

Because simulate requires no cluster or Docker, it runs anywhere — including CI runners without Docker-in-Docker access. Use --no-e2e to skip the cluster gate in environments that can’t provision kind, while still enforcing reconciler correctness via simulate:

ork registry push postgres:v14 ./patterns/postgres/ --no-e2e

The artifact carries io.orkestra.simulate.status=passed — consumers know the reconciler was verified even if the full cluster test was deferred.


Testing a multi-operator pattern

When a pattern contains several Katalogs, a simulate aggregator at the root runs all sub-simulations together:

multi-tenancy/
  komposer.yaml
  simulate.yaml                    ← aggregator
  01-basic-namespacing/
    katalog.yaml  crd.yaml  cr.yaml  simulate.yaml
  02-cross-access-control/
    katalog.yaml  crd.yaml  cr.yaml  simulate.yaml

The aggregator:

apiVersion: orkestra.orkspace.io/v1
kind: Simulate
metadata:
  name: multi-tenancy-sim

imports:
  files:
    - ./01-basic-namespacing/simulate.yaml
    - ./02-cross-access-control/simulate.yaml

ork registry push discovers simulate.yaml at the pattern root and runs it. Each sub-simulation runs independently; one failure stops the push.

ork validate -f simulate.yaml checks the spec and confirms all imported files exist on disk before any simulation runs:

Validating Simulate...

  ✓ metadata.name: multi-tenancy-sim
  ✓ imports.files: ./01-basic-namespacing/simulate.yaml (found)
  ✓ imports.files: ./02-cross-access-control/simulate.yaml (found)

2 import(s) valid

→ Full field reference: simulate.yaml schema