Skip to content
Ironflow is pre-1.0: APIs can change between releases. Pin a version. What is ready →

Push Conformance

ironflow conformance push checks a push endpoint against the push protocol. It sends the requests the engine sends, and it checks each answer. It needs no Ironflow server. Use it when you write a push handler in a language that has no Ironflow SDK.

A language is supported for push mode when its handler passes every check at level 3 (ADR 0079).

The suite cannot know what your own functions return. Your endpoint must therefore host four fixture functions with the exact behavior below. Register them next to your real functions, or in a test build of your service.

Each step returns a fixed JSON object. The suite plants different outputs in steps, so it can see whether your handler replayed a step or ran it again.

No steps. Return the run fields you received:

return { run_id, function_id, attempt, data: event.data }

Return event.data unchanged. It can contain HTML characters such as <, > and &. Sign and verify the raw request bytes, not a re-serialized body.

No steps. Fail without retry. Answer HTTP 200 with status: "failed", error.message set to event.data.message, and error.retryable: false.

o1 = step.run("plain") -> {"s":"plain#1"}
o2 = step.run("plain") -> {"s":"plain#2"}
o3 = step.run("a:b\c") -> {"s":"escaped"}
[b0, b1] = parallel("fan:out", [
branch 0: step.run("leaf") -> {"s":"leaf-0-0"}, then step.run("leaf") -> {"s":"leaf-0-1"}
branch 1: step.run("leaf") -> {"s":"leaf-1-0"}
])
return [o1, o2, o3, b0, b1]

A branch returns the output of its last step. For run R, the step IDs must be:

R:plain:0
R:plain:1
R:a\:b\\c:0
R:fan\:out:0:leaf:0
R:fan\:out:0:leaf:1
R:fan\:out:1:leaf:0
before = step.run("before") -> {"s":"before"}
step.sleep("nap", 1 hour)
after = step.run("after") -> {"s":"after"}
return [before, after]
Terminal window
ironflow conformance push --url http://localhost:8080/ironflow --signing-key $KEY

Build in levels, as the any-language guide recommends:

LevelChecksFixtures needed
1405 for GET, 404 for an unknown function, unknown fields ignored, echo, failure encoding, signaturesconformance-echo, conformance-fail
2Step IDs and escaping, parallel scopes, memo replay, delta-only steps, failed memo entries, stable IDs across attemptsadds conformance-steps
3Sleep yield and resumeadds conformance-sleep

Pass --level 1 until level 1 passes. Without --signing-key, the signature checks are skipped. The flag reads IRONFLOW_SIGNING_KEY when you do not set it. Each request has a 10-second limit. Change it with --timeout, for example --timeout 30s.

PASS L1 transport.method-not-allowed
FAIL L2 steps.full-replay
delta step IDs
want: (none)
got: run_conf_x:plain:0 run_conf_x:plain:1
contract: https://docs.ironflow.run/reference/api/push-protocol/#memoization-rules

Each failure shows what the suite expected, what your endpoint sent, and the rule on the push protocol page. Add --json for a report that a CI job can read. Exit code 0 means that no check failed. The CLI reference lists the other exit codes.

The suite is stricter than the engine in a few places. The engine accepts these step results, and the suite fails them:

  • A step result with no id
  • A step result with an empty name
  • A status other than completed or failed
  • A step result in steps that is not completed, or that has a wrong output
  • A type other than invoke for step.run

The engine makes up an ID for a step result that has none. Your handler cannot compute that ID, so the next replay runs the step again.

  • wait_for_event, invoke_function and invoke_function_async yields
  • The compensate: and publish: step-name prefixes
  • The capacity fencing headers (Ironflow-Execution-Seq, Ironflow-Lease-*)
  • secrets and trace headers
  • started_at and ended_at on step results
  • Constant-time signature comparison. Timing is not visible from outside your handler. Use a constant-time compare anyway.