✓
Passing Passing: the compiler rejects this program as expected.
Code
import std/io
import std/store
import app/lib/post
import app/lib/stamps
// MINIMAL REPRO: an obligation consumed by BOTH arms of an if inside a
// for-body, followed by a |> continuation, is still seen as live at the
// merge — KORU030 says <held!> was never discharged.
//
// Expected: both arms consume p (sign/stamp-express take <!held>), so the
// merge has nothing to discharge — the program should compile and run.
// Actual: error[KORU030]: Resource 'p' obligation <held!> was not
// discharged, blamed on the |> line after the if.
std/store:new(board) { n: 0[i64] }
tor go {}
go = for(0..@as(usize, 2))
! each i |> app/lib/post:took-pouch(seal: @as(i64, @intCast(i))): p
|> if(@mod(@as(i64, @intCast(i)), 2) == 0)
| then |> app/lib/stamps:sign(p): p1
|> app/lib/stamps:wax(p: p1): p2
|> app/lib/stamps:carry(p: p2): p3
|> app/lib/stamps:arrive(p: p3): p4
|> app/lib/post:file-dispatch(p: p4)
| else |> app/lib/stamps:stamp-express(p): p1
|> app/lib/stamps:arrive(p: p1): p2
|> app/lib/post:file-dispatch(p: p2)
|> std/store:stored { board.n: board.n + 1 }
go()
|> std/io:print.ln("done")
Supporting Files
// The pouch — a proc-minted resource whose stamps are someone else's job.
// took-pouch issues <held!>; file-dispatch is the ONLY discharge and it
// demands <!delivered> — so a pouch cannot leave the world unfiled.
// peek-seal is a borrow: it reads the seal off a <carried> pouch and hands
// it back untouched. Every stamp between held and delivered lives in
// lib/stamps.k — pure Koru retagging a pointer this module owns.
const std = @import("std");
const Pouch = struct { seal: i64 };
~pub tor took-pouch { seal: i64 } -> *Pouch<held!>
~proc took-pouch|zig {
const p = std.heap.page_allocator.create(Pouch) catch unreachable;
p.* = .{ .seal = seal };
return p;
}
~pub tor peek-seal { p: *Pouch<carried> } -> i64
~proc peek-seal|zig {
return p.seal;
}
~pub tor file-dispatch { p: *Pouch<!delivered> }
~proc file-dispatch|zig {
// Silent on purpose: std.debug.print is stderr, and the harness merges
// streams into actual.txt — interleaving would make the oracle flaky.
std.heap.page_allocator.destroy(p);
}
import app/lib/post
pub tor sign { p: *app/lib/post:Pouch<!held> } -> *app/lib/post:Pouch<signed!>
sign -> p
pub tor wax { p: *app/lib/post:Pouch<!signed> } -> *app/lib/post:Pouch<sealed!>
wax -> p
pub tor carry { p: *app/lib/post:Pouch<!sealed> } -> *app/lib/post:Pouch<carried!>
carry -> p
pub tor stamp-express { p: *app/lib/post:Pouch<!held> } -> *app/lib/post:Pouch<carried!>
stamp-express -> p
pub tor arrive { p: *app/lib/post:Pouch<!carried> } -> *app/lib/post:Pouch<delivered!>
arrive -> p
Actual compiler output
error[KORU030]: Resource 'p' obligation <held!> was not discharged. No tor accepts <!held>.
--> tests/regression/800_CHALLENGES/800_009_if_arm_obligation_merge/input.k:29:8
❌ Compiler coordination error: Auto-discharge failed (multiple disposal options or no disposal event)
(set KORU_BACKEND_TRACE=1 for the backend return trace)Must fail at runtime:
Program must error when executed.
Error output must contain
obligation <held!> was not dischargedFlows
flow ~new click a branch to expand · @labels scroll to their anchor
new (board, source: n: 0[i64])
subflow ~go click a branch to expand · @labels scroll to their anchor
for (0..@as(usize, 2))
flow ~go click a branch to expand · @labels scroll to their anchor
go
Test Configuration
MUST_ERROR