✓
Passing This code compiles and runs correctly.
Code
// TEST: MOUNTED SCOPE — a `.dylib` extends a live session's vocabulary,
// on the resources the session already holds, without reloading them.
//
// The claim this pins, end to end:
//
// 1. The bound scope `open` mints a `*Note<opened>` handle on the bridge.
// 2. `std/bridge:mount` dlopens module/zig-out/lib/libspike.dylib — a
// hand-built scope carrying the same ScopeRuntime vtable `register`
// emits — and installs it on the session.
// 3. The mounted verb `touch(handle: *Note<opened>)` borrows the
// base-minted handle THROUGH THE SAME POOL — the module resolves the
// token via the host services and reads note.text. Its result
// carries the note's own text: `touched:first-note` is proof the
// module read the resource, not a copy.
// 4. The mounted verb `seal` mints `*Stamp<sealed>` — booked against
// the same pool — discharged explicitly by `unseal`.
// 5. `vocabulary()` renders the mounted verbs beside the compiled ones
// — prompt and enforcement read one table.
// 6. A second stamp is left held: hang-up sweeps it through the
// MODULE's dispatcher — cross-scope discharge. Reaching the final
// print at all is the assertion: a stranded handle panics `close`.
//
// Minted tokens are never printed — they are random hex — only counts,
// bound names, and the module's own strings.
import std/io
import std/bridge
import app/session
tor step { br: *std/bridge:Bridge<session>, source: string<*>, tag: string }
step = std/bridge:run(br, source)
| result r |> std/io:print.ln("{{ tag:s }} ok (holds {{ r.handles:d }}, bound {{ r.bound:s }})")
| unhandled-branch ub |> std/io:print.ln("{{ tag:s }} FAIL unhandled {{ ub.branch:s }}")
| defined d |> std/io:print.ln("{{ tag:s }} FAIL defined {{ d:s }}")
| error runerr |> std/bridge:error-decode(e: runerr)
| dispatch-error e |> std/io:print.ln("{{ tag:s }} refused: {{ e.message:s }}")
| event-denied ev |> std/io:print.ln("{{ tag:s }} FAIL denied {{ ev:s }}")
| parse-error p |> std/io:print.ln("{{ tag:s }} FAIL parse {{ p.message:s }}")
| validation-error v |> std/io:print.ln("{{ tag:s }} FAIL invalid {{ v:s }}")
| shape-error sh |> std/io:print.ln("{{ tag:s }} FAIL shape {{ sh.message:s }}")
| exhausted _ |> std/io:print.ln("{{ tag:s }} FAIL exhausted")
| scope-not-found s2 |> std/io:print.ln("{{ tag:s }} FAIL noscope {{ s2:s }}")
| residue _ |> std/io:print.ln("{{ tag:s }} FAIL residue outcome")
| partial p |> std/io:print.ln("{{ tag:s }} FAIL partial {{ p.ran:d }}/{{ p.total:d }}")
// Same step, but the result VALUE is rendered — for `touch`, whose
// payload is the proof a mounted verb read a base-minted resource.
tor step-show { br: *std/bridge:Bridge<session>, source: string<*>, tag: string }
step-show = std/bridge:run(br, source)
| result r |> std/interpreter:value.stringify(r.value): j |> std/io:print.ln("{{ tag:s }} ok -> {{ j:s }}")
| unhandled-branch ub |> std/io:print.ln("{{ tag:s }} FAIL unhandled {{ ub.branch:s }}")
| defined d |> std/io:print.ln("{{ tag:s }} FAIL defined {{ d:s }}")
| error runerr |> std/bridge:error-decode(e: runerr)
| dispatch-error e |> std/io:print.ln("{{ tag:s }} refused: {{ e.message:s }}")
| event-denied ev |> std/io:print.ln("{{ tag:s }} FAIL denied {{ ev:s }}")
| parse-error p |> std/io:print.ln("{{ tag:s }} FAIL parse {{ p.message:s }}")
| validation-error v |> std/io:print.ln("{{ tag:s }} FAIL invalid {{ v:s }}")
| shape-error sh |> std/io:print.ln("{{ tag:s }} FAIL shape {{ sh.message:s }}")
| exhausted _ |> std/io:print.ln("{{ tag:s }} FAIL exhausted")
| scope-not-found s2 |> std/io:print.ln("{{ tag:s }} FAIL noscope {{ s2:s }}")
| residue _ |> std/io:print.ln("{{ tag:s }} FAIL residue outcome")
| partial p |> std/io:print.ln("{{ tag:s }} FAIL partial {{ p.ran:d }}/{{ p.total:d }}")
tor mount-spike { br: *std/bridge:Bridge<session>, path: string }
mount-spike = std/bridge:mount(br, path)
| mounted m |> std/io:print.ln("mounted scope: {{ m:s }}")
| failed f |> std/io:print.ln("MOUNT FAILED: {{ f:s }}")
tor show-vocab { br: *std/bridge:Bridge<session> }
show-vocab = std/bridge:vocabulary(br)
| ok v |> std/io:print.ln("vocabulary:\n{{ v:s }}")
| not-found _ |> std/io:print.ln("vocabulary: not-found")
tor show-grammar { br: *std/bridge:Bridge<session> }
show-grammar = std/bridge:grammar(br)
| ok g |> std/io:print.ln("grammar:\n{{ g:s }}")
| not-found _ |> std/io:print.ln("grammar: not-found")
tor show-held { br: *std/bridge:Bridge<session> }
show-held = std/bridge:get-handles(br): hc |> std/io:print.ln("held before hang-up: {{ hc:d }}")
[with]std/bridge:create(id: "mounted-1", scope: "notes", introspection: true): br
|> app/session:build-module(): lib
|> step(br, source: "open(path: \"first-note\")", tag: "open")
|> mount-spike(br, path: lib)
|> step-show(br, source: "touch(handle: r1)", tag: "touch")
|> step(br, source: "seal(label: \"wax\")", tag: "seal")
|> step(br, source: "unseal(stamp: r3)", tag: "unseal")
|> step(br, source: "unseal(stamp: r3)", tag: "reunseal")
|> show-vocab(br)
|> show-grammar(br)
|> step(br, source: "seal(label: \"held\")", tag: "seal2")
|> show-held(br)
|> close(br) |> std/io:print.ln("hang-up: clean")
Supporting Files
// spike — a hand-built Koru scope module (`.dylib`), the mounted-scope
// proof for 440_056. Three verbs:
//
// touch(handle: *Note<opened>) — borrows a handle the BOUND scope
// minted, reads through the pointer the
// pool resolves. Cross-scope param.
// seal(label: string) -> *Stamp<sealed!>
// — mints an obligation inside the dylib:
// the pool token comes back from the
// host, the custody record is booked by
// the interpreter from the spec table
// below.
// unseal(stamp: *Stamp<!sealed>) — consumes it. Called mid-session or
// swept at hang-up through THIS module's
// dispatcher — the cross-scope discharge
// proof.
//
// The shape mirrors what a `register` block emits for a compiled scope:
// one dispatcher over Invocation/DispatchResult, and the lookup tables
// the enforcement, gate, prompt, and sweep all read — nothing here is a
// second path.
const std = @import("std");
const abi = @import("module_abi.zig");
// The resource mirrors. `Note`'s layout is the bound scope's struct
// field-for-field — the pool hands back the very pointer `open` minted;
// `Stamp` exists only inside this module.
const Note = struct { text: []const u8 };
const Stamp = struct { label: []const u8 };
var host: abi.MountedHostServices = undefined;
var scratch = std.heap.page_allocator;
fn getArg(args: []const abi.Arg, name: []const u8) ?[]const u8 {
for (args) |a| {
if (std.mem.eql(u8, a.name, name)) return a.value;
}
return null;
}
fn dispatchTouch(inv: *const abi.Invocation, out: *abi.DispatchResult) !void {
const tok = getArg(inv.args, "handle") orelse return error.MissingArg;
const raw = host.resolve_dispatch_resource(tok) orelse {
host.set_dispatch_detail("touch: handle names nothing the session holds");
return error.DispatchFailed;
};
const note: *Note = @ptrCast(@alignCast(raw));
const S = struct { threadlocal var fields: [1]abi.NamedField = undefined; };
const text = std.fmt.allocPrint(scratch, "touched:{s}", .{note.text}) catch return error.OutOfMemory;
S.fields[0] = .{ .name = "__type_ref", .value = .{ .string_val = text } };
out.* = .{ .branch = "", .fields = S.fields[0..1] };
}
fn dispatchSeal(inv: *const abi.Invocation, out: *abi.DispatchResult) !void {
const label = getArg(inv.args, "label") orelse return error.MissingArg;
const stamp = scratch.create(Stamp) catch return error.OutOfMemory;
stamp.* = .{ .label = scratch.dupe(u8, label) catch return error.OutOfMemory };
const token = host.register_dispatch_resource("Stamp", stamp);
const S = struct { threadlocal var fields: [1]abi.NamedField = undefined; };
S.fields[0] = .{ .name = "__type_ref", .value = .{ .string_val = token } };
out.* = .{ .branch = "", .fields = S.fields[0..1] };
}
fn dispatchUnseal(inv: *const abi.Invocation, out: *abi.DispatchResult) !void {
const tok = getArg(inv.args, "stamp") orelse return error.MissingArg;
const raw = host.resolve_dispatch_resource(tok) orelse {
host.set_dispatch_detail("unseal: stamp names nothing the session holds");
return error.DispatchFailed;
};
const stamp: *Stamp = @ptrCast(@alignCast(raw));
scratch.free(stamp.label);
scratch.destroy(stamp);
out.* = .{ .branch = "", .fields = &.{} };
}
// -- the scope tables --
const event_names = [_][]const u8{ "touch", "seal", "unseal" };
const touch_input = [_]abi.InputFieldSpec{
.{ .name = "handle", .type_name = "*Note", .phantom = "opened" },
};
const seal_input = [_]abi.InputFieldSpec{
.{ .name = "label", .type_name = "string" },
};
const unseal_input = [_]abi.InputFieldSpec{
.{ .name = "stamp", .type_name = "*Stamp", .phantom = "!sealed" },
};
const seal_creates = [_]abi.CreateSpec{
.{ .obligation_module = "spike", .obligation_name = "sealed", .field_name = "__type_ref", .resource_type = "Stamp" },
};
const unseal_discharges = [_]abi.DischargeSpec{
.{ .obligation_module = "spike", .obligation_name = "sealed", .arg_name = "stamp" },
};
fn eventsFn() []const []const u8 {
return &event_names;
}
fn inputFn(name: []const u8) ?[]const abi.InputFieldSpec {
if (std.mem.eql(u8, name, "touch")) return &touch_input;
if (std.mem.eql(u8, name, "seal")) return &seal_input;
if (std.mem.eql(u8, name, "unseal")) return &unseal_input;
return null;
}
fn createsSpecFn(name: []const u8) []const abi.CreateSpec {
if (std.mem.eql(u8, name, "seal")) return &seal_creates;
return &.{};
}
fn dischargesSpecFn(name: []const u8) []const abi.DischargeSpec {
if (std.mem.eql(u8, name, "unseal")) return &unseal_discharges;
return &.{};
}
fn dischargeEventFn(obligation: []const u8) ?[]const u8 {
if (std.mem.eql(u8, obligation, "sealed")) return "unseal";
return null;
}
fn branchesFn(name: []const u8) []const abi.BranchSpec {
_ = name;
return &.{};
}
fn costFn(name: []const u8) u64 {
_ = name;
return 1;
}
fn docFn(name: []const u8) []const u8 {
if (std.mem.eql(u8, name, "touch")) return "read a held note (borrow, mounted)";
if (std.mem.eql(u8, name, "seal")) return "mint a sealed stamp (mounted)";
if (std.mem.eql(u8, name, "unseal")) return "discharge a sealed stamp (mounted)";
return "";
}
fn dispatcher(inv: *const abi.Invocation, out: *abi.DispatchResult) anyerror!void {
const name = if (inv.path.segments.len > 0) inv.path.segments[0] else "";
if (std.mem.eql(u8, name, "touch")) return dispatchTouch(inv, out);
if (std.mem.eql(u8, name, "seal")) return dispatchSeal(inv, out);
if (std.mem.eql(u8, name, "unseal")) return dispatchUnseal(inv, out);
return error.EventDenied;
}
const spike_scope = abi.ScopeRuntime{
.name = "spike",
.dispatcher = &dispatcher,
.cost_fn = &costFn,
.creates_obligations_fn = null,
.discharges_obligations_fn = null,
.discharge_event_fn = &dischargeEventFn,
.creates_spec_fn = &createsSpecFn,
.discharges_spec_fn = &dischargesSpecFn,
.branches_fn = &branchesFn,
.events_fn = &eventsFn,
.input_fn = &inputFn,
.doc_fn = &docFn,
.manifest = null,
};
// The one exported symbol mount looks up: hand the module the host's
// service table, get the scope's vtable back. The version check is the
// whole compat story — same toolchain or no mount.
export fn koru_module_init(services: *const abi.MountedHostServices) ?*const abi.ScopeRuntime {
if (services.abi_version != abi.KORU_MOUNTED_ABI_VERSION) return null;
host = services.*;
return &spike_scope;
}
// The Koru mounted-scope ABI — the module side of the boundary, mirrored
// field-for-field from koru_std (interpreter.contracts/resdispatch,
// runtime.lookups/specs, runtime.kz:ScopeRuntime). A module and its host
// must be built by the same toolchain: these layouts are compiled, not
// serialized, and `abi_version` is the handshake, not a compat story.
//
// `Invocation` is imported whole from the compiler's own ast.zig — it is
// the one type on this boundary too large to mirror honestly.
const std = @import("std");
const ast = @import("ast");
pub const Invocation = ast.Invocation;
pub const Arg = ast.Arg;
// -- dispatch protocol (koru_std/interpreter.kz) --
pub const FieldValue = union(enum) {
string_val: []const u8,
int_val: i64,
float_val: f64,
bool_val: bool,
string_array: []const []const u8,
};
pub const NamedField = struct {
name: []const u8,
value: FieldValue,
};
pub const DispatchResult = struct {
branch: []const u8,
fields: []const NamedField,
};
pub const DispatchFn = *const fn (*const Invocation, *DispatchResult) anyerror!void;
// -- metadata lookups (koru_std/runtime.lookups + specs) --
pub const CostFn = *const fn ([]const u8) u64;
pub const ObligationsArrayFn = *const fn ([]const u8) []const []const u8;
pub const DischargeEventFn = *const fn ([]const u8) ?[]const u8;
pub const CreateSpec = struct {
obligation_module: []const u8,
obligation_name: []const u8,
field_name: []const u8,
resource_type: []const u8,
branch: ?[]const u8 = null,
};
pub const DischargeSpec = struct {
obligation_module: []const u8,
obligation_name: []const u8,
arg_name: []const u8,
};
pub const CreatesSpecFn = *const fn ([]const u8) []const CreateSpec;
pub const DischargesSpecFn = *const fn ([]const u8) []const DischargeSpec;
pub const BranchField = struct {
name: []const u8,
type_name: []const u8,
is_optional: bool = false,
};
pub const BranchSpec = struct {
name: []const u8,
fields: []const BranchField,
is_optional: bool = false,
};
pub const BranchesLookupFn = *const fn ([]const u8) []const BranchSpec;
pub const EventsLookupFn = *const fn () []const []const u8;
pub const InputFieldSpec = struct {
name: []const u8,
type_name: []const u8,
is_optional: bool = false,
phantom: ?[]const u8 = null,
};
pub const InputLookupFn = *const fn ([]const u8) ?[]const InputFieldSpec;
pub const DocLookupFn = *const fn ([]const u8) []const u8;
// -- the vtable (koru_std/runtime.kz:858 — field order is load-bearing) --
pub const ScopeRuntime = struct {
name: []const u8,
dispatcher: DispatchFn,
cost_fn: ?CostFn,
creates_obligations_fn: ?ObligationsArrayFn,
discharges_obligations_fn: ?ObligationsArrayFn,
discharge_event_fn: ?DischargeEventFn,
creates_spec_fn: ?CreatesSpecFn,
discharges_spec_fn: ?DischargesSpecFn,
branches_fn: ?BranchesLookupFn,
events_fn: ?EventsLookupFn,
input_fn: ?InputLookupFn,
doc_fn: ?DocLookupFn,
manifest: ?*const anyopaque = null,
};
// -- host services (koru_std/interpreter.resdispatch.kz) --
//
// The three calls a mounted dispatcher may make back into the host. They
// are the module-boundary spelling of setDispatchDetail /
// registerDispatchResource / resolveDispatchResource: a mounted proc
// mints and resolves against the HOST'S pool — the session's handles —
// not a private table inside the .dylib.
pub const KORU_MOUNTED_ABI_VERSION: u32 = 1;
pub const MountedHostServices = struct {
abi_version: u32,
set_dispatch_detail: *const fn ([]const u8) void,
register_dispatch_resource: *const fn ([]const u8, *anyopaque) []const u8,
resolve_dispatch_resource: *const fn ([]const u8) ?*anyopaque,
};
// The resources a bridge session holds, the scope that names them, and
// the module-build helper for 440_056. The scope is declared here rather
// than in the entry file because a registered scope survives the module
// boundary (115_036).
//
// `Note` is the resource the bound scope mints — the mounted module
// mirrors this layout field-for-field and reads through the pointer the
// pool hands back.
~import std/runtime
const std = @import("std");
pub const Note = struct { text: []const u8 };
~pub tor open { path: string } -> *Note<opened!>
~proc open|zig {
const n = std.heap.page_allocator.create(Note) catch unreachable;
n.* = .{ .text = std.heap.page_allocator.dupe(u8, path) catch path };
return n;
}
~pub tor close { handle: *Note<!opened> }
~proc close|zig {
std.heap.page_allocator.free(handle.text);
std.heap.page_allocator.destroy(handle);
}
~std/runtime:register(scope: "notes") {
open(10)
close(1)
}
// zig build the module — the test compiles its own capability, which is
// also the honest artifact path: nothing outside the program had to know
// the lib existed. Paths resolve through selfExeDirPath: the harness runs
// the binary as "$test_dir/output" without cd, so neither `module/` nor
// `compiler_env.json` sits at cwd. Returns the dylib's absolute path.
~pub tor build-module {} -> string
~proc build-module|zig {
const allocator = std.heap.page_allocator;
var exe_buf: [4096]u8 = undefined;
const exe_dir = std.fs.selfExeDirPath(&exe_buf) catch
return "build-module: no self-exe path";
const env_path = std.fs.path.join(allocator, &.{ exe_dir, "compiler_env.json" }) catch
return "build-module: oom";
const env_text = std.fs.cwd().readFileAlloc(allocator, env_path, 64 * 1024) catch
return "build-module: no compiler_env.json beside the binary";
const parsed = std.json.parseFromSlice(
struct { koru_home: []const u8 },
allocator, env_text, .{ .ignore_unknown_fields = true },
) catch return "build-module: compiler_env.json did not parse";
defer parsed.deinit();
const root_arg = std.fmt.allocPrint(allocator, "-Dkoru_root={s}", .{parsed.value.koru_home}) catch
return "build-module: oom";
const mod_dir = std.fs.path.join(allocator, &.{ exe_dir, "module" }) catch
return "build-module: oom";
const result = std.process.Child.run(.{
.allocator = allocator,
.argv = &.{ "zig", "build", root_arg },
.cwd = mod_dir,
.max_output_bytes = 8 * 1024 * 1024,
}) catch |e|
return std.fmt.allocPrint(allocator, "build-module: spawn failed: {s}", .{@errorName(e)}) catch "spawn failed";
switch (result.term) {
.Exited => |code| {
if (code != 0) {
return std.fmt.allocPrint(allocator, "build-module: zig build failed ({d}): {s}",
.{ code, result.stderr[0..@min(result.stderr.len, 2000)] }) catch "build failed";
}
},
else => return "build-module: zig build died abnormally",
}
return std.fs.path.join(allocator,
&.{ mod_dir, "zig-out", "lib", "libspike.dylib" }) catch "build-module: oom";
}
Actual
open ok (holds 1, bound r1)
mounted scope: spike
touch ok -> {"branch":"","value":"touched:first-note"}
seal ok (holds 2, bound r3)
unseal ok (holds 1, bound r4)
reunseal refused: 'stamp' wants a live <sealed> to spend — 'h:Stamp:5fcd62' was already spent — an obligation discharges once. Mint one through a verb whose signature ends '-> <sealed!>'
vocabulary:
pub tor open { path: string } -> *Note<opened!>
pub tor close { handle: *Note<!opened> }
pub tor touch { handle: *Note<opened> } — read a held note (borrow, mounted)
pub tor seal { label: string } — mint a sealed stamp (mounted)
pub tor unseal { stamp: *Stamp<!sealed> } — discharge a sealed stamp (mounted)
vocabulary()
grammar()
bind(name: string, value: string)
note(text: string) — speaks to the observer, not the user: reasoning, plans, and "why I'm stuck" go here
abandon(reason: string) — ends the session with your reason: what you tried, what blocked you
r1
r3
r2
r4
grammar:
This surface is a little alien — a reply is a program, not a message. Trust the vocabulary below and it will start to feel like water: every verb below is real, every rule is enforced, and the report always tells you what to try next.
Your reply IS wire — invocations, never narration. One invocation per line, run in order. `say(text: "...")` is what reaches the user; prose binds as residue and reaches nobody.
VOCABULARY — each line below is the verb's declaration. `pub tor name { arg: type }` declares it; you INVOKE it with parens: `name(arg: "v")`. The `type<!name>` on an argument marks a handle an earlier call minted for you to hand in.
VALUES — a field takes a "quoted string" or a bare expression (`42`, `r3.total * 2`, `u.user_id`). `{{ name }}` inside a quoted string splices a bound value into prose; `{{ u.field }}` lifts one level of fields — a deeper path refuses, so read nested shapes out of the report instead.
OUTCOMES — `-> type` at the tail of a declaration names the verb's one outcome; arm it `| ok name |>`. A `|` list — `| ok string | missing string` — names the outcomes; arm words are exactly those names, nothing else. An invented arm is refused before the call runs, and the refusal names the legal words. When an outcome fires unarmed the report says `unhandled-branch` and names it: arm it and resend. Verbs that declare `| error string` issue failures on that branch — `call(...) | ok r |>` arms success only, so an unarmed error stops the reply at that step; to act on a failure arm it `| error e |>` and read `e` for the reason. Never assume a call succeeded — check the declaration's outcome list.
BINDINGS — `| ok n |>` binds the outcome to `n` for the rest of this reply; a call five items later still sees it, and the name dies with the reply. Every dispatched result also binds a durable `r1, r2, …` name that outlives the reply — `| ok n |>` for threading within a reply, `rN` across replies.
EXAMPLES
Arms on the call's own line:
fetch-account(id: "ada_7") | ok a |> say(text: "Welcome back, {{ a.name }}.")
Or each arm on its own indented line — never both; a `|` on the call's line commits to the single-line shape:
cancel-booking(booking_id: "BK402")
| ok c |> say(text: "Cancelled {{ c.booking_id }}.")
A verb with more than one outcome arms each by name:
read-doc(path: "n.txt")
| ok d |> say(text: "got {{ d }}")
| missing m |> say(text: "no such file: {{ m }}")
Steps thread bound names through the whole reply:
load-profile(user_id: "u_1") | ok p |>
check-tier(member: p.membership) | ok t |>
say(text: "{{ p.user_id }} is {{ t }}")
HOST VERBS — `bind(name: "k", value: "...")` persists a value under `k` across replies. `abandon(reason: "...")` ends the session with your reason — what you tried, what blocked you. `vocabulary()` and `grammar()` re-emit this surface.
The tool contract — every verb's full documentation and argument shapes — follows the vocabulary below; read it before composing a call whose payload shape is unclear.
- `<opened>` — minted by open(...) | ok; spent by close(handle:)
On this wall: `open(path: "1") | ok v |> close(handle: v)` — a real chain from your vocabulary.
Your vocabulary:
pub tor open { path: string } -> *Note<opened!>
pub tor close { handle: *Note<!opened> }
- `<sealed>` — minted by seal(...) | ok; spent by unseal(stamp:)
pub tor touch { handle: *Note<opened> } — read a held note (borrow, mounted)
pub tor seal { label: string } — mint a sealed stamp (mounted)
pub tor unseal { stamp: *Stamp<!sealed> } — discharge a sealed stamp (mounted)
vocabulary()
grammar()
bind(name: string, value: string)
note(text: string) — speaks to the observer, not the user: reasoning, plans, and "why I'm stuck" go here
abandon(reason: string) — ends the session with your reason: what you tried, what blocked you
seal2 ok (holds 2, bound r5)
held before hang-up: 2
[BRIDGE] Invoked 'unseal' for handle 'h:Stamp:e5f666' [spike:sealed]
[BRIDGE] Invoked 'close' for handle 'h:Note:01947a' [app.session:opened]
hang-up: clean
Expected patterns
Each line is a regex that must match the program output.
^mounted scope: spike$
^open ok \(holds 1, bound r1\)$
^touch ok -> \{"branch":"","value":"touched:first-note"\}$
^seal ok \(holds 2, bound r3\)$
^unseal ok \(holds 1, bound r4\)$
^reunseal refused: 'stamp' wants a live <sealed> to spend — 'h:Stamp:[0-9a-f]+' was already spent — an obligation discharges once\. Mint one through a verb whose signature ends '-> <sealed!>'$
^pub tor touch \{ handle: \*Note<opened> \} — read a held note \(borrow, mounted\)$
^pub tor seal \{ label: string \} — mint a sealed stamp \(mounted\)$
^pub tor unseal \{ stamp: \*Stamp<!sealed> \} — discharge a sealed stamp \(mounted\)$
^- `<sealed>` — minted by seal\(\.\.\.\) \| ok; spent by unseal\(stamp:\)$
^seal2 ok \(holds 2, bound r5\)$
^held before hang-up: 2$
^\[BRIDGE\] Invoked 'unseal' for handle 'h:Stamp:[0-9a-f]+' \[spike:sealed\]$
^\[BRIDGE\] Invoked 'close' for handle 'h:Note:[0-9a-f]+' \[app.session:opened\]$
^hang-up: clean$Flows
subflow ~step click a branch to expand · @labels scroll to their anchor
run (br, source)
subflow ~step-show click a branch to expand · @labels scroll to their anchor
run (br, source)
subflow ~mount-spike click a branch to expand · @labels scroll to their anchor
mount (br, path)
subflow ~show-vocab click a branch to expand · @labels scroll to their anchor
vocabulary (br)
subflow ~show-grammar click a branch to expand · @labels scroll to their anchor
grammar (br)
subflow ~show-held click a branch to expand · @labels scroll to their anchor
get-handles (br)
flow ~create click a branch to expand · @labels scroll to their anchor
create (id: "mounted-1", scope: "notes", introspection: true)
Test Configuration
MUST_RUN
koru.json:
{
"paths": {
"std": "../../../../../koru_std",
"app": "{{ ENTRY }}"
}
}