From 2b47b2925c0c5e9b2616c976927917eb70332e7b Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 6 Jun 2026 18:17:40 +0000 Subject: [PATCH] flow: end-to-end integration suite + 10 tests (Phase 7) Realistic flows composing every phase: an order pipeline (validate via attempt -> payment suspend -> branch -> ledger federation via remote-node) and an onboarding flow, each run through the full lifecycle including a simulated crash (export/wipe/ import) and a peer handoff mid-flow, with flow/pending|status|result introspection. 142/142 across 9 suites. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/flow/conformance.sh | 1 + lib/flow/scoreboard.json | 9 +-- lib/flow/scoreboard.md | 3 +- lib/flow/tests/integration.sx | 115 ++++++++++++++++++++++++++++++++++ plans/flow-on-sx.md | 12 +++- 5 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 lib/flow/tests/integration.sx diff --git a/lib/flow/conformance.sh b/lib/flow/conformance.sh index bcdc52dd..5d0e7b6b 100755 --- a/lib/flow/conformance.sh +++ b/lib/flow/conformance.sh @@ -29,6 +29,7 @@ SUITES=( "api flow-api-tests-run! lib/flow/tests/api.sx" "combinators flow-cmb-tests-run! lib/flow/tests/combinators.sx" "railway flow-rail-tests-run! lib/flow/tests/railway.sx" + "integration flow-int-tests-run! lib/flow/tests/integration.sx" ) TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT diff --git a/lib/flow/scoreboard.json b/lib/flow/scoreboard.json index cc1f1082..b8cff6b9 100644 --- a/lib/flow/scoreboard.json +++ b/lib/flow/scoreboard.json @@ -1,6 +1,6 @@ { - "total": 132, - "passed": 132, + "total": 142, + "passed": 142, "failed": 0, "suites": { "basic": { "passed": 18, "total": 18 }, @@ -10,7 +10,8 @@ "distributed": { "passed": 19, "total": 19 }, "api": { "passed": 12, "total": 12 }, "combinators": { "passed": 17, "total": 17 }, - "railway": { "passed": 10, "total": 10 } + "railway": { "passed": 10, "total": 10 }, + "integration": { "passed": 10, "total": 10 } }, - "phases": { "phase1": "done", "phase2": "done", "phase3": "done", "phase4": "done", "phase5": "done", "phase6": "done" } + "phases": { "phase1": "done", "phase2": "done", "phase3": "done", "phase4": "done", "phase5": "done", "phase6": "done", "phase7": "done" } } diff --git a/lib/flow/scoreboard.md b/lib/flow/scoreboard.md index 90e9f6d8..0f539a36 100644 --- a/lib/flow/scoreboard.md +++ b/lib/flow/scoreboard.md @@ -1,6 +1,6 @@ # flow-on-sx Scoreboard -**All tests pass: 132 / 132 across 8 suites. Phases 1-6 complete.** +**All tests pass: 142 / 142 across 9 suites. Phases 1-7 complete.** `bash lib/flow/conformance.sh` @@ -16,6 +16,7 @@ | api | 12 | Phase 5: introspection — `flow/status`, `flow/result`, `flow/list`, `flow/pending` | | combinators | 17 | Phase 5: `tap`, `recover` (fail-value), `map-flow` fan-over-list, `flow-while`/`flow-until` bounded iteration | | railway | 10 | Phase 6: `attempt` — fail-value short-circuiting sequence + recover rejoin | +| integration | 10 | Phase 7: end-to-end order + onboarding flows composing every phase (suspend, branch, federation, crash recovery, handoff, introspection) | ## Architecture diff --git a/lib/flow/tests/integration.sx b/lib/flow/tests/integration.sx new file mode 100644 index 00000000..46352682 --- /dev/null +++ b/lib/flow/tests/integration.sx @@ -0,0 +1,115 @@ +;; lib/flow/tests/integration.sx — Phase 7: end-to-end flows composing every phase. + +(define flow-int-pass 0) +(define flow-int-fail 0) +(define flow-int-fails (list)) + +(define + flow-int-test + (fn + (name actual expected) + (if + (= actual expected) + (set! flow-int-pass (+ flow-int-pass 1)) + (begin + (set! flow-int-fail (+ flow-int-fail 1)) + (append! flow-int-fails {:name name :expected expected :actual actual}))))) + +(define flow-i (fn (src) (flow-run src))) + +;; The order-processing flow, defined once per program via this prelude string: +;; validate amount (attempt: fail if <= 0) +;; -> suspend for payment confirmation (resume value = confirmed amount) +;; -> branch: confirmed>0 ? record on the ledger peer : declined failure +(define + order-prelude + "(flow-peer-register! (quote ledger) (list (list (quote record) (lambda (amt) (list (quote recorded) amt)))))\n (defflow order\n (attempt\n (lambda (amt) (if (> amt 0) amt (fail (quote invalid-amount))))\n (lambda (amt) (suspend (quote await-payment)))\n (branch (lambda (amt) (> amt 0))\n (remote-node (quote ledger) (quote record))\n (flow-const (fail (quote declined))))))") + +;; ── happy path through every phase ────────────────────────────── +(flow-int-test + "order: validate -> suspend -> resume -> branch -> federate" + (flow-i + (str + order-prelude + "(define id (car (cdr (flow/start order 100)))) (flow/resume id 250)")) + (list "recorded" 250)) +(flow-int-test + "order: starting suspends awaiting payment" + (flow-i + (str + order-prelude + "(define s (flow/start order 100)) (list (car s) (car (cdr (cdr s))))")) + (list "flow-suspended" "await-payment")) +(flow-int-test + "order: invalid amount fails up front and never suspends" + (flow-i + (str + order-prelude + "(define r (flow/start order -5)) (list (failed? r) (fail-reason r))")) + (list true "invalid-amount")) +(flow-int-test + "order: a declined payment yields a failure value" + (flow-i + (str + order-prelude + "(define id (car (cdr (flow/start order 100)))) (failed? (flow/resume id 0))")) + true) + +;; ── crash recovery mid-flow ───────────────────────────────────── +(flow-int-test + "order: survives a simulated crash between suspend and resume" + (flow-i + (str + order-prelude + "(define id (car (cdr (flow/start order 100)))) (define saved (flow-store-export)) (set! flow-store (list)) (flow-store-import! saved) (flow/resume id 250)")) + (list "recorded" 250)) + +;; ── handoff to a peer mid-flow ────────────────────────────────── +(flow-int-test + "order: hands off to a peer that resumes and completes" + (flow-i + (str + order-prelude + "(define id (car (cdr (flow/start order 100)))) (flow-replicate-to (quote nodeB)) (set! flow-store (list)) (flow-restore-from (quote nodeB)) (flow/resume id 250)")) + (list "recorded" 250)) + +;; ── introspection during the flow's life ──────────────────────── +(flow-int-test + "order: pending shows what the flow awaits, then result after resume" + (flow-i + (str + order-prelude + "(define id (car (cdr (flow/start order 100)))) (define p (flow/pending)) (flow/resume id 250) (list p (flow/status id) (flow/result id))")) + (list + (list (list 1 "await-payment")) + "done" + (list "recorded" 250))) + +;; ── onboarding: two human steps + cancellation ────────────────── +(define + onboard-prelude + "(defflow onboard\n (sequence\n (lambda (user) (+ user 1))\n (lambda (x) (suspend (quote confirm-email)))\n (lambda (x) (suspend (quote complete-profile)))\n (lambda (x) (list (quote onboarded) x))))") + +(flow-int-test + "onboard: two suspends resume in order to completion" + (flow-i + (str + onboard-prelude + "(define id (car (cdr (flow/start onboard 0)))) (flow/resume id 7) (flow/resume id 9)")) + (list "onboarded" 9)) +(flow-int-test + "onboard: the second pending tag appears after the first resume" + (flow-i + (str + onboard-prelude + "(define id (car (cdr (flow/start onboard 0)))) (flow/resume id 7) (car (cdr (car (flow/pending))))")) + "complete-profile") +(flow-int-test + "onboard: cancelling abandons the flow" + (flow-i + (str + onboard-prelude + "(define id (car (cdr (flow/start onboard 0)))) (flow/cancel id) (list (flow/status id) (car (flow/resume id 7)))")) + (list "cancelled" "flow-error")) + +(define flow-int-tests-run! (fn () {:total (+ flow-int-pass flow-int-fail) :passed flow-int-pass :failed flow-int-fail :fails flow-int-fails})) diff --git a/plans/flow-on-sx.md b/plans/flow-on-sx.md index 278937ad..95a8a57b 100644 --- a/plans/flow-on-sx.md +++ b/plans/flow-on-sx.md @@ -16,7 +16,7 @@ federation extension via fed-sx for remote-node execution. ## Status (rolling) -`bash lib/flow/conformance.sh` → **132/132** (Phases 1-6 complete) +`bash lib/flow/conformance.sh` → **142/142** (Phases 1-7 complete) ## Ground rules @@ -159,6 +159,16 @@ Make the `(fail reason)` value channel compose into real validation/ETL pipeline - [x] `lib/flow/tests/railway.sx` — 10 cases: fail short-circuiting, no-run-after- failure, recover rejoin, validation pipeline reporting the failing stage +## Phase 7 — End-to-end integration + +Prove the phases compose: realistic flows exercising attempt + suspend + branch + +remote-node + crash-recovery + handoff + introspection together. + +- [x] `lib/flow/tests/integration.sx` — 10 cases: an order-processing flow (validate + → payment suspend → branch → ledger federation) and an onboarding flow, run through + the full lifecycle including a simulated crash and a peer handoff mid-flow, plus + introspection (`flow/pending`/`status`/`result`) during the flow's life + ## Progress log - **Phase 1 (combinators + sequential runtime).** Flow built as a Scheme prelude