Failure Semantics¶
Every assertion in a Lace script produces one of three outcomes: passed, failed, or indeterminate. Failures are either hard (the rest of the script is skipped) or soft (recorded, execution continues).
Hard Fail¶
A hard fail skips .store() and .wait() on the current call and all subsequent calls in the script. The current call's .check() and .assert() still run and are recorded. The result records every call that was skipped with outcome "skipped".
Sources of hard fail:
| Source | Description |
|---|---|
.expect() |
Any scope fails (after all scopes are evaluated) |
.assert({ expect: [...] }) |
Any expect condition fails (after all are evaluated) |
| Redirect limit exceeded | The call needs more than redirects.max hops --- not overridable |
TLS error with rejectInvalidCerts: true |
Any TLS error, e.g. an invalid certificate --- not overridable |
schema($var) where $var is null |
Missing schema variable |
timeout.action: "fail" |
Request timed out |
| All retries exhausted | timeout.action: "retry" with all retries failed |
A redirects.max or timeout.ms above the system maximum never gets this far: it is a validation error (REDIRECTS_MAX_LIMIT / TIMEOUT_MS_LIMIT) and the script does not run.
Timeouts¶
A call that times out always gets the call outcome "timeout" (not "failure"), whatever its timeout.action. The action decides what happens next:
"fail", or"retry"once the retries are exhausted --- a hard fail. Later calls are skipped and the run outcome is"timeout"."warn"--- a soft fail. The run continues, and the timeout does not change the run outcome.
The run outcome "timeout" therefore means that a call timed out and stopped the run; there is no separate run-level timeout.
Soft Fail¶
A soft fail is recorded but execution continues normally through remaining chain methods and subsequent calls.
Sources of soft fail:
| Source | Description |
|---|---|
.check() |
Any scope fails (after all scopes are evaluated) |
.assert({ check: [...] }) |
Any check condition fails (after all are evaluated) |
timeout.action: "warn" |
Request timed out but configured to warn only |
TLS error with rejectInvalidCerts: false |
The TLS error is recorded as a warning in the call record; the executor does not otherwise interpret the certificate |
Indeterminate¶
An indeterminate outcome is neither pass nor fail. It occurs when a comparison cannot produce a meaningful result.
Source: a null operand in an ordered comparison (lt, lte, gt, gte) or in arithmetic (+, -, *, /, %).
// If $$prev_count is null (never stored), this is indeterminate
get("$BASE_URL/api/metrics")
.expect(status: 200)
.assert({
check: [
$$prev_count lt this.body.count
]
})
Indeterminate outcomes are recorded as "indeterminate" in the assertion record. Execution continues.
Equality is not indeterminate
null eq null is true. null eq 42 is false. null neq 42 is true. Only ordered comparisons and arithmetic with null produce indeterminate results.
Complete Evaluation Before Cascade¶
This is a key design principle: all scopes/conditions are evaluated before any failure takes effect.
.expect() evaluates every scope. .assert({ expect: [...] }) evaluates every condition. Every assertion block on the call --- .expect(), .check(), .assert() --- is evaluated in full before the hard-fail cascade acts. This ensures all failures on a call are visible simultaneously in the result.
.expect(
status: 200, // fails
totalDelayMs: 500, // also fails
body: schema($s) // also evaluated
)
// All three results are recorded, THEN the hard fail cascade begins
Cascade Rules¶
When a hard fail occurs:
.store()and.wait()on the current call are skipped. If.expect()fails, the call's.check()and.assert()are still evaluated and recorded;.store()and.wait()are not run.- All subsequent calls are skipped. They appear in the result with outcome
"skipped".
.store() is specifically skipped whenever a preceding chain method on the same call produced a hard fail. This prevents storing values from a response that failed validation.
Summary Table¶
| Outcome | Sources | Effect on execution |
|---|---|---|
| Hard fail | .expect(), .assert({ expect }), redirect limit, TLS error (strict), schema(null), timeout (fail/retry exhausted) |
Skip .store()/.wait() on the call + all subsequent calls |
| Soft fail | .check(), .assert({ check }), timeout (warn), TLS error (lenient) |
Record failure, continue |
| Indeterminate | null in ordered comparison or arithmetic |
Record as indeterminate, continue |
| Passed | Assertion condition met | Continue |