✓
Passing This code compiles and runs correctly.
Code
// TEST: an event that RE-ISSUES the obligation it consumes is not a discharger.
//
// The registry's discharge lookup returned the FIRST event whose declaration
// consumed the obligation. With two consumers — `use` (`<!open> -> <open!>`) and
// `shut` (`<!open>`, void) — first meant `use`, so a session's hang-up called
// the one that hands the obligation straight back and the resource survived its
// own release. The counter still went to zero, because a re-issue discharges the
// old handle before minting the new one.
//
// The compiled path has excluded this shape for as long as auto-discharge has
// existed (`eventReIssuesObligation`); only the interpreted path picked it up.
//
// Found by kopium-headless in koru-examples — an agent harness whose only tool
// is a Koru interpreter over a bridge. Its `append` verb is exactly `use`, and
// the session's hang-up wrote to the file it was closing.
//
// The bridge holds one resource; nothing in this flow discharges it, so the
// compiler inserts the hang-up. `shut` must be what runs.
import std/bridge
import std/io
import app/session
[with]std/bridge:create(id: "s1", scope: "res"): br |> run(br, source: "open(path: \"x\")")
| result r |> run(br, source: "use(handle: \"res_1\")")
| result r2 |> std/io:print.ln("held {{ r.handles:d }} then {{ r2.handles:d }}")
| defined d2 |> std/io:print.ln("FAIL turn2 defined {{ d2:s }}")
| exhausted _ |> std/io:print.ln("FAIL exhausted")
| parse-error _ |> std/io:print.ln("FAIL parse")
| validation-error _ |> std/io:print.ln("FAIL validation")
| shape-error _ |> std/io:print.ln("FAIL shape")
| event-denied _ |> std/io:print.ln("FAIL denied")
| dispatch-error e |> std/io:print.ln("FAIL {{ e.message:s }}")
| scope-not-found _ |> std/io:print.ln("FAIL scope")
| defined d1 |> std/io:print.ln("FAIL turn1 defined {{ d1:s }}")
| exhausted _ |> std/io:print.ln("FAIL exhausted")
| parse-error _ |> std/io:print.ln("FAIL parse")
| validation-error _ |> std/io:print.ln("FAIL validation")
| shape-error _ |> std/io:print.ln("FAIL shape")
| event-denied _ |> std/io:print.ln("FAIL denied")
| dispatch-error e2 |> std/io:print.ln("FAIL {{ e2.message:s }}")
| scope-not-found _ |> std/io:print.ln("FAIL scope")
Actual
use() ran — obligation re-issued, nothing released
held 1 then 1
shut() ran — released 'res_1'
[BRIDGE] Invoked 'shut' for handle 'res_1' [app.session:open]
Expected output
use() ran — obligation re-issued, nothing released
held 1 then 1
shut() ran — released 'res_1'
[BRIDGE] Invoked 'shut' for handle 'res_1' [app.session:open]
Flows
flow ~create click a branch to expand · @labels scroll to their anchor
create (id: "s1", scope: "res")
Imported Files
// Two events consume `<!open>`. Only one of them RELEASES anything.
//
// `use` is the transaction shape (`tx.exec`): it consumes the obligation and
// hands back another, so calling it lets go of nothing. `shut` consumes and
// creates nothing, so it is the only real discharger.
~import std/runtime
const std = @import("std");
~pub tor open { path: string } -> string<open!>
~proc open|zig {
_ = path;
return "res_1";
}
~pub tor use { handle: string<!open> } -> string<open!>
~proc use|zig {
std.debug.print("use() ran — obligation re-issued, nothing released\n", .{});
return handle;
}
~pub tor shut { handle: string<!open> }
~proc shut|zig {
std.debug.print("shut() ran — released '{s}'\n", .{handle});
}
~std/runtime:register(scope: "res") {
open(10)
use(1)
shut(1)
}
Test Configuration
MUST_RUN