✓
Passing This code compiles and runs correctly.
Code
// The pump slice: `! step` on a one-owned-column store + `std/pump`.
//
// `! step s |> lib/srclib:step(s)` declares how a row ADVANCES; the step tor's
// `|` verdicts are the store's to read — `| complete` retires the row in place
// (the `! discharge` arm runs it out), every other verdict keeps it. The
// generated pass steps each row once and never steps a retired row — the
// hand-written version of this (the sweep + handle-slot + deferred `take`
// scheduler of the r3 probe) is the code this slice replaces.
//
// `std/pump:create(name)` declares the pump; `std/pump(name)` is the join
// form — a participant attaches its verbs (`! step`/`! live`) as arms on the
// reference, and joins land in document order = pass order. The store's
// `! step` arm exposes those verbs as the ordinary units `<store>-step` /
// `<store>-live`; the pump composes names, not stores.
// `std/pump:run(name)` loops passes until every participant's `live` answers
// 0, then fires the declaration's `| drained` arm.
//
// `close -> i64` is not unattended-callable, so `! discharge` is required and
// IS the retirement path — the pinned output shows it running from inside the
// pass, not at teardown.
import std/io
import std/store
import std/pump
import lib/srclib
std/store:new(fast, capacity: 2) { *lib/srclib:Src<live!> }
! step s |> lib/srclib:step(s)
! moved i |> std/io:print.ln("fast moved {{ i:d }}")
! discharge s |> lib/srclib:close(s): id |> std/io:print.ln("retired {{ id:d }}")
std/store:new(slow, capacity: 2) { *lib/srclib:Src<live!> }
! step s |> lib/srclib:step(s)
! moved i |> std/io:print.ln("slow moved {{ i:d }}")
! discharge s |> lib/srclib:close(s): id |> std/io:print.ln("retired {{ id:d }}")
lib/srclib:open(id: 1, work: 2): a |> std/store:insert(fast) { a }
| row _ |> _
| full f |> lib/srclib:close(s: f): id |> std/io:print.ln("dropped {{ id:d }}")
lib/srclib:open(id: 2, work: 3): b |> std/store:insert(slow) { b }
| row _ |> _
| full f |> lib/srclib:close(s: f): id |> std/io:print.ln("dropped {{ id:d }}")
std/pump:create(main)
| drained |> std/io:print.ln("all participants retired")
std/pump(main)
! step |> fast-step()
! live |> fast-live()
std/pump(main)
! step |> slow-step()
! live |> slow-live()
std/pump:run(main)
Supporting Files
// A synthetic PARTICIPANT with curl's exact contract, so the pump shape can be
// probed without libcurl or a terminal.
//
// open → mints *Src<live!> (curl: multi.start | started)
// step → BORROWS <live>, `| running` / `| complete` (curl: poll)
// close → CONSUMES <!live>, hands a value back (curl: finish/cancel)
//
// close returning a value is the load-bearing detail: it leaves the column with
// ZERO void dischargers, so a store holding one must be drained by an explicit
// `! discharge` arm rather than auto-discharged (690_109's shape).
const std = @import("std");
pub const Src = struct { id: i64, left: i64 };
~pub tor open { id: i64, work: i64 } -> *Src<live!>
~proc open|zig {
const s = std.heap.page_allocator.create($mod.Src) catch unreachable;
s.* = .{ .id = id, .left = work };
return s;
}
~pub tor step { s: *Src<live> }
! ?moved i64
| running
| complete
~proc step|zig {
if (s.left <= 0) return .{ .complete = .{} };
s.left -= 1;
moved(s.id);
if (s.left == 0) return .{ .complete = .{} };
return .{ .running = .{} };
}
~pub tor close { s: *Src<!live> } -> i64
~proc close|zig {
const id = s.id;
std.heap.page_allocator.destroy(s);
return id;
}
Actual
fast moved 1
slow moved 2
fast moved 1
retired 1
slow moved 2
slow moved 2
retired 2
all participants retired
Expected output
fast moved 1
slow moved 2
fast moved 1
retired 1
slow moved 2
slow moved 2
retired 2
all participants retired
Flows
flow ~new click a branch to expand · @labels scroll to their anchor
new (fast, capacity: 2, source: *lib/srclib:Src<live!>)
flow ~new click a branch to expand · @labels scroll to their anchor
new (slow, capacity: 2, source: *lib/srclib:Src<live!>)
flow ~open click a branch to expand · @labels scroll to their anchor
open (id: 1, work: 2)
flow ~open click a branch to expand · @labels scroll to their anchor
open (id: 2, work: 3)
flow ~create click a branch to expand · @labels scroll to their anchor
create (expr: main)
flow ~std/pump click a branch to expand · @labels scroll to their anchor
std/pump (main)
flow ~std/pump click a branch to expand · @labels scroll to their anchor
std/pump (main)
flow ~run click a branch to expand · @labels scroll to their anchor
run (expr: main)
Test Configuration
MUST_RUN
koru.json:
{
"name": "probe",
"version": "0.1.0"
}