flow: error model — fail/failed?/fail-reason failure values + 6 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s

Explicit (fail reason) values flow downstream as data and are inspected with
failed?/fail-reason — distinct from raised exceptions (retry/try-catch territory).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 16:35:40 +00:00
parent 65cbdb8387
commit 1731476dc6
5 changed files with 43 additions and 13 deletions

View File

@@ -1,13 +1,10 @@
{
"total": 24,
"passed": 24,
"total": 30,
"passed": 30,
"failed": 0,
"suites": {
"basic": { "passed": 18, "total": 18 },
"control": { "passed": 6, "total": 6 }
"control": { "passed": 12, "total": 12 }
},
"phases": {
"phase1": "done",
"phase2": "in-progress"
}
"phases": { "phase1": "done", "phase2": "in-progress" }
}

View File

@@ -1,6 +1,6 @@
# flow-on-sx Scoreboard
**All tests pass: 24 / 24 across 2 suites.**
**All tests pass: 30 / 30 across 2 suites.**
`bash lib/flow/conformance.sh`
@@ -9,7 +9,7 @@
| Suite | Passing | Covers |
|-------|--------:|--------|
| basic | 18 | Phase 1: single nodes, linear sequence, data-flow threading, defflow, parallel fan/join, nested composition, publish-shaped flow |
| control | 6 | Phase 2: `branch` conditional — true/false select, threaded predicate, full-node branches, nested 3-way, approval gate |
| control | 12 | Phase 2: `branch` conditional (6); error model `fail`/`failed?`/`fail-reason` failure values that flow as data (6) |
## Architecture
@@ -36,6 +36,6 @@ capture the flow continuation directly.
## Phases
- [x] Phase 1 — Declarative DAG + sequential execution (combinators + 18 tests, `flow/start`)
- [~] Phase 2 — Control flow + error handling (`branch` done)
- [~] Phase 2 — Control flow + error handling (`branch`, error model done)
- [ ] Phase 3 — Suspend / resume (the showcase)
- [ ] Phase 4 — Distributed nodes via fed-sx

View File

@@ -19,6 +19,9 @@
;; Phase 2 combinators (flow-control-src):
;; (branch pred then else) — pred on input selects then/else node
;; (`cond` is a Scheme special form, so the combinator is named `branch`)
;; (fail reason) — make an explicit failure value (data, not an exception)
;; (failed? x) — is x a failure value?
;; (fail-reason x) — the reason carried by a failure value
(define
flow-combinators-src
@@ -26,7 +29,7 @@
(define
flow-control-src
"(define (branch pred then else)\n (lambda (input) (if (pred input) (then input) (else input))))")
"(define (branch pred then else)\n (lambda (input) (if (pred input) (then input) (else input))))\n (define (fail reason) (list (quote flow-fail) reason))\n (define (failed? x) (and (pair? x) (eq? (car x) (quote flow-fail))))\n (define (fail-reason x) (car (cdr x)))")
(define
flow-load-combinators!

View File

@@ -50,4 +50,32 @@
"(defflow publish (branch (lambda (post) (>= (string-length post) 3)) (lambda (post) (string-append post \" [published]\")) (lambda (post) (string-append post \" [rejected]\")))) (flow/start publish \"ok\")")
"ok [rejected]")
;; ── error model — explicit (fail reason) values ─────────────────
(flow-ctl-test
"fail: failed? is true for a failure value"
(flow-c "(failed? (fail 404))")
true)
(flow-ctl-test
"fail: fail-reason extracts the reason"
(flow-c "(fail-reason (fail 404))")
404)
(flow-ctl-test
"fail: failed? is false for a plain value"
(flow-c "(failed? 7)")
false)
(flow-ctl-test
"fail: failed? is false for an ordinary list"
(flow-c "(failed? (list 1 2 3))")
false)
(flow-ctl-test
"fail: a node may emit a failure as data"
(flow-c
"(defflow validate (lambda (s) (if (>= (string-length s) 3) s (fail (quote too-short))))) (failed? (flow/start validate \"hi\"))")
true)
(flow-ctl-test
"fail: failure flows downstream, branch recovers"
(flow-c
"(defflow guarded (sequence (lambda (s) (if (>= (string-length s) 3) (string-length s) (fail (quote too-short)))) (branch failed? (lambda (f) (list (quote recovered) (fail-reason f))) (lambda (n) (list (quote ok) n))))) (flow/start guarded \"hi\")")
(list "recovered" "too-short"))
(define flow-ctl-tests-run! (fn () {:total (+ flow-ctl-pass flow-ctl-fail) :passed flow-ctl-pass :failed flow-ctl-fail :fails flow-ctl-fails}))

View File

@@ -16,7 +16,7 @@ federation extension via fed-sx for remote-node execution.
## Status (rolling)
`bash lib/flow/conformance.sh`**24/24** (Phase 1 done; Phase 2 in progress)
`bash lib/flow/conformance.sh`**30/30** (Phase 1 done; Phase 2 in progress)
## Ground rules
@@ -80,7 +80,9 @@ lib/flow/spec.sx lib/flow/runtime.sx lib/flow/store.sx
- [ ] `retry n [backoff]` — re-runs node up to n times on exception
- [ ] `timeout ms` — bounds node execution
- [ ] `try-catch` — exception handler with reified error
- [ ] error model — exceptions vs explicit `(fail :reason ...)` results
- [x] error model — exceptions vs explicit `(fail reason)` results: `fail`/`failed?`/
`fail-reason` produce/inspect failure values that flow downstream as data
(distinct from raised exceptions caught by retry/try-catch). 6 tests.
- [ ] `lib/flow/tests/control.sx` — 25+ cases: each combinator + composition
## Phase 3 — Suspend / resume (the showcase)