Failure Semantics¶
Every assertion in a Lace script produces one of three outcomes: passed, failed, or indeterminate. Failures are either hard (execution stops) or soft (recorded, execution continues).
Hard Fail¶
A hard fail skips all remaining chain methods on the current call and all subsequent calls in the script. 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 | redirects.max reached --- not overridable |
TLS error with rejectInvalidCerts: true |
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 |
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 |
Invalid certificate, configured to allow |
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
.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. Only after the full evaluation does the hard-fail cascade trigger. This ensures all failures 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:
- Remaining chain methods on the current call are skipped. If
.expect()fails, then.check(),.assert(),.store(), and.wait()on that call are all skipped. - 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 remaining chain methods + 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 |