← pluginabi · API reference · version 0.1.8
3 file(s), 574 lines. This is the source the published version ships, and it is loft — the library is written in the same language you call it from.
Public items
A worked example is a call site the author chose as the thing to read first. Call sites are every line naming the item, found mechanically over the source with comments excluded — so an item with no worked example is not an item nobody uses.
| Item | Defined | Worked example | Call sites |
|---|---|---|---|
OP_INITIAL_STATE const | 95 | — | 106 43 45 102 108 140 … (8 total) |
OP_APPLY_OP const | 96 | — | 106 115 35 37 78 102 … (14 total) |
OP_MAKE_OP const | 97 | — | 106 115 102 107 142 163 … (8 total) |
OP_RENDER const | 98 | — | 107 63 102 143 170 90 … (7 total) |
OP_SNAPSHOT const | 99 | — | 107 90 102 108 144 174 … (10 total) |
OP_LOAD_SNAPSHOT const | 100 | — | 107 102 145 175 180 129 … (7 total) |
valid_op fn | 105 | — | 216 103 106 151 154 |
takes_arg fn | 114 | PAB-003 | 107 108 136 |
request fn | 130 | PAB-001 | 35 43 63 78 90 97 … (18 total) |
req_op fn | 144 | — | 37 45 136 |
req_state_b64 fn | 148 | PAB-001 | 38 44 79 137 48 49 … (8 total) |
req_arg_b64 fn | 152 | — | 39 44 80 138 138 |
reply_ok fn | 162 | PAB-001 | 50 82 140 141 142 143 … (11 total) |
reply_err fn | 172 | — | 54 135 146 149 70 |
reply_is_ok fn | 182 | PAB-002 | 51 55 61 62 63 83 … (17 total) |
reply_out_b64 fn | 194 | PAB-002 | 52 84 159 163 164 165 … (13 total) |
reply_err_code fn | 198 | PAB-002 | 56 181 186 78 79 102 |
ERR_UNKNOWN_OP const | 204 | — | 216 98 149 186 123 144 |
ERR_MALFORMED const | 205 | — | 214 93 94 44 121 122 |
ERR_PLUGIN const | 206 | — | 54 56 146 181 70 79 |
check_request fn | 213 | PAB-003 | 91 93 94 98 134 44 … (14 total) |
src/pluginabi.loft
// pluginabi — the plugin call protocol: six operations as frames on one channel.
//
// WHAT THIS IS. A plugin implements six pure operations. The host does not call them as six
// named wasm exports, and there is no per-plugin `(ptr,len)` ABI: the host drives the plugin
// over the byte channel every loft wasm already exposes (`loft_io.loft_host_input_len` /
// `loft_host_input_copy` in, `loft_host_print` out, pumped by the host).
//
// So the six operations are FRAMES on one channel, and this file is that frame vocabulary. It is
// the only thing either side needs to agree on: no ABI, no shim, no runtime.
//
// WHY CBOR AND NOT A TEXT PROTOCOL. Plugin payloads are opaque BYTES (state, ops, snapshots —
// the plugin owns their meaning), and they must survive a round trip unchanged; canonical CBOR
// carries byte strings losslessly, and one canonical encoding is what makes a frame
// deterministic.
//
// Run: `loft test`.
//
// ---------- Using it ----------
//
// One vocabulary, used from BOTH ends — the host builds what the plugin reads,
// and the same file is the only thing either side agrees on:
//
// // host: build a request. Payloads are opaque bytes, base64 in this API.
// st = bytes_to_base64([1 as u8, 2 as u8, 255 as u8]);
// frame = pluginabi::request(pluginabi::OP_APPLY_OP, st,
// bytes_to_base64([0 as u8, 7 as u8]));
//
// // plugin: read it back
// pluginabi::req_op(frame) // -> "apply_op"
// pluginabi::req_state_b64(frame) // -> st, byte for byte
// pluginabi::check_request(frame) // -> "" — the empty string means VALID
//
// // plugin: answer
// ok = pluginabi::reply_ok(bytes_to_base64([9 as u8, 9 as u8]));
// bad = pluginabi::reply_err(pluginabi::ERR_UNKNOWN_OP);
// pluginabi::reply_is_ok(ok) // -> true
// pluginabi::reply_err_code(bad) // -> "unknown-op"
//
// The 255 and the 0 in those payloads are the point: they are not text, they
// survive the round trip unchanged, and canonical CBOR is what makes that true.
//
// Validate before dispatching. `check_request` and `reply_is_ok` are both
// FAIL-CLOSED — an undecodable frame is invalid rather than empty — so a host
// that skips them is guessing about a version skew instead of refusing it.
//
// Example: @PAB-004
use cbor::*; // the published canonical-CBOR codec
use crypto::*; // base64 <-> bytes
// ---------- CBOR helpers ----------
// Inlined rather than depending on a project-local CBOR wrapper: a published protocol library
// must stand on published deps alone.
//
// ALL PREFIXED `pa_`, and that is not cosmetic. A library's PRIVATE helpers still share a name
// space with every other library in a consumer's program, so a plain `decode_value` here collides
// with a consumer's own `decode_value` — which is exactly what happened: the collision was
// invisible in this package's own tests and in a minimal consumer, and only surfaced in a program
// that used both. A published library must not impose names a consumer cannot see coming.
fn pa_ctext(s: text) -> CborValue { return CText { value: s }; }
fn pa_cbool(b: boolean) -> CborValue { return CBool { value: b }; }
fn pa_cbytes(b64: text) -> CborValue { return CBytes { value: base64_to_bytes(b64) }; }
fn pa_te(k: text, v: CborValue) -> CborEntry { return CborEntry { key: CText { value: k }, value: v }; }
fn pa_decode(bytes: vector<u8>) -> CborValue { decode(bytes).value }
fn pa_decode_ok(bytes: vector<u8>) -> boolean { d = decode(bytes); return d.ok; }
// Is `b64` a payload `base64_to_bytes` reads? It answers the empty vector for text that is
// not base64, so only `""` may decode to nothing.
fn pa_is_base64(b64: text) -> boolean { b64 == "" || len(base64_to_bytes(b64)) > 0 }
fn pa_get(m: CborValue, key: text) -> CborValue {
match m {
CMap { entries } => {
for e in entries {
match e.key { CText { value } => { if value == key { return e.value; } }, _ => {} }
}
},
_ => {}
}
return CNull;
}
fn pa_text(m: CborValue, key: text) -> text {
match pa_get(m, key) { CText { value } => { return "{value}"; }, _ => {} }
return "";
}
fn pa_bytes(m: CborValue, key: text) -> text {
match pa_get(m, key) { CBytes { value } => { return bytes_to_base64(value); }, _ => {} }
return "";
}
// ---------- the operation vocabulary ----------
// The six, and only these six. `valid_op` is the gate: a host that receives an unknown op must
// reject rather than guess, because a plugin and host disagreeing about the vocabulary is a
// version skew, not a request to be interpreted.
pub const OP_INITIAL_STATE: text = "initial_state";
pub const OP_APPLY_OP: text = "apply_op";
pub const OP_MAKE_OP: text = "make_op";
pub const OP_RENDER: text = "render";
pub const OP_SNAPSHOT: text = "snapshot";
pub const OP_LOAD_SNAPSHOT: text = "load_snapshot";
// Is this one of the six? THE gate on the host side: an unknown op is a version
// skew between plugin and host, and the only safe answer is to refuse rather
// than to interpret.
pub fn valid_op(op: text) -> boolean {
return (op == OP_INITIAL_STATE) || (op == OP_APPLY_OP) || (op == OP_MAKE_OP)
|| (op == OP_RENDER) || (op == OP_SNAPSHOT) || (op == OP_LOAD_SNAPSHOT);
}
// Which operations read the `arg` slot. Recorded here rather than in each host so the two sides
// cannot drift: a host that sends `arg` for `snapshot` is confused about the contract, and a
// plugin that reads it would be reading whatever the host happened to leave there.
// Example: @PAB-003
pub fn takes_arg(op: text) -> boolean {
return (op == OP_APPLY_OP) || (op == OP_MAKE_OP);
}
// ---------- request frames ----------
// { "op": text, "state": bytes, "arg": bytes }
//
// `state` is always present (the empty byte string for `initial_state`), so the plugin never has
// to distinguish "absent" from "empty" — an absent-vs-empty ambiguity across a wire is exactly
// the kind of thing that produces a plausible wrong answer instead of an error.
//
// ⚠ A payload that is not base64 answers an EMPTY frame, which `check_request` refuses as
// `malformed-frame`. Handing over a raw string by mistake (`"hello"` for its base64) is
// then an error at the front door, not a state that arrives as nothing — only `""` decodes
// to the empty byte string.
// Example: @PAB-001
pub fn request(op: text, state_b64: text, arg_b64: text) -> vector<u8> {
if !pa_is_base64(state_b64) || !pa_is_base64(arg_b64) {
return [];
}
es: vector<CborEntry> = [];
es += [ pa_te("op", pa_ctext(op)) ];
es += [ pa_te("state", pa_cbytes(state_b64)) ];
es += [ pa_te("arg", pa_cbytes(arg_b64)) ];
return encode(CMap { entries: es });
}
// Read the operation out of a request frame. Answers "" for a frame that does
// not decode, which `valid_op` then rejects — so a corrupt frame and an unknown
// op take the same path.
pub fn req_op(frame: vector<u8>) -> text { return pa_text(pa_decode(frame), "op"); }
// The state the host is handing over, base64. Always present — the empty byte
// string for `initial_state` — so a plugin never has to tell absent from empty.
// Example: @PAB-001
pub fn req_state_b64(frame: vector<u8>) -> text { return pa_bytes(pa_decode(frame), "state"); }
// The operation's argument, base64. Only `apply_op` and `make_op` carry one;
// ask `takes_arg` first, because for the other four this is whatever the host
// left in the slot.
pub fn req_arg_b64(frame: vector<u8>) -> text { return pa_bytes(pa_decode(frame), "arg"); }
// ---------- reply frames ----------
// success: { "ok": true, "out": bytes }
// failure: { "ok": false, "err": text }
//
// A failure carries a CODE, not a message: the host shows it to a person, and a free-form string
// from the plugin would be the one channel out of the sandbox that is neither bytes-the-host-
// inspects (render) nor a value it asked for: render output stays the only rich channel.
// Example: @PAB-001
pub fn reply_ok(out_b64: text) -> vector<u8> {
es: vector<CborEntry> = [];
es += [ pa_te("ok", pa_cbool(true)) ];
es += [ pa_te("out", pa_cbytes(out_b64)) ];
return encode(CMap { entries: es });
}
// The failure reply. `code` is a CODE and not a message: a free-form string
// would be a channel out of the sandbox that nothing inspects, and the host is
// what turns a code into words a person reads.
pub fn reply_err(code: text) -> vector<u8> {
es: vector<CborEntry> = [];
es += [ pa_te("ok", pa_cbool(false)) ];
es += [ pa_te("err", pa_ctext(code)) ];
return encode(CMap { entries: es });
}
// true only for a well-formed success reply. A frame that does not decode, or whose `ok` is not
// the boolean true, reads as failure — the fail-closed direction.
// Example: @PAB-002
pub fn reply_is_ok(frame: vector<u8>) -> boolean {
if !pa_decode_ok(frame) { return false; }
match pa_get(pa_decode(frame), "ok") {
CBool { value } => { return value; },
_ => { return false; },
}
}
// The success payload, base64. Only meaningful once `reply_is_ok` says yes —
// on a failure reply it answers "", which is indistinguishable from an empty
// success.
// Example: @PAB-002
pub fn reply_out_b64(frame: vector<u8>) -> text { return pa_bytes(pa_decode(frame), "out"); }
// The failure code. The other half of the same pair: check `reply_is_ok`, then
// read exactly one of these two.
// Example: @PAB-002
pub fn reply_err_code(frame: vector<u8>) -> text { return pa_text(pa_decode(frame), "err"); }
// ---------- the error codes a host may produce ----------
// Closed set, so a host and a UI cannot drift. `ERR_UNKNOWN_OP` is the version-skew case above;
// `ERR_MALFORMED` is a frame that did not decode; `ERR_PLUGIN` is the plugin reporting it could
// not satisfy a well-formed request (which a plugin may — e.g. load_snapshot on foreign bytes).
pub const ERR_UNKNOWN_OP: text = "unknown-op";
pub const ERR_MALFORMED: text = "malformed-frame";
pub const ERR_PLUGIN: text = "plugin-error";
// Validate an inbound request frame the way a host must, BEFORE dispatching it: decode, then
// check the vocabulary. Returns "" when the frame is acceptable, else the error code to reply
// with. Centralised so every host does the same two checks in the same order rather than each
// re-deriving them — the contract has one front door.
// Example: @PAB-003
pub fn check_request(frame: vector<u8>) -> text {
if !pa_decode_ok(frame) { return ERR_MALFORMED; }
op = pa_text(pa_decode(frame), "op");
if !valid_op(op) { return ERR_UNKNOWN_OP; }
return "";
}
tests/pluginabi.loft
// Tests for pluginabi — the §9c.2 plugin call protocol. Run: `loft test`.
//
// The load-bearing test is the last one: a COMPLETE six-operation plugin driven end to end over
// nothing but frames. If the protocol were missing something a real plugin needs, that test could
// not be written — which is the point of writing it here rather than asserting field-by-field.
use pluginabi::*;
use cbor::*;
use crypto::*;
// Fixture helpers. The library keeps its CBOR shims PRIVATE — exporting them is not a protocol
// library's job — so a test that builds its own payloads brings its own, exactly as a real plugin
// author would.
fn t_te(k: text, v: CborValue) -> CborEntry { return CborEntry { key: CText { value: k }, value: v }; }
fn t_cuint(n: integer) -> CborValue { return CInt { value: n }; }
fn t_decode(bytes: vector<u8>) -> CborValue { d = decode(bytes); v = d.value; return v; }
fn t_decode_ok(bytes: vector<u8>) -> boolean { d = decode(bytes); return d.ok; }
fn t_uint(m: CborValue, key: text) -> integer {
match m {
CMap { entries } => {
for i in 0..len(entries) {
e = entries[i] ?? CborEntry { key: CNull, value: CNull };
match e.key { CText { value } => {
if value == key { match e.value { CInt { value } => { return value; }, _ => {} } }
}, _ => {} }
}
},
_ => {},
}
return 0;
}
fn test_request_round_trip() {
st = bytes_to_base64([1 as u8, 2 as u8, 255 as u8]); // binary, incl. a non-UTF-8 byte
ar = bytes_to_base64([0 as u8, 7 as u8]);
f = request(OP_APPLY_OP, st, ar);
assert(req_op(f) == OP_APPLY_OP, "op round-trips");
assert(req_state_b64(f) == st, "state round-trips byte-exactly");
assert(req_arg_b64(f) == ar, "arg round-trips byte-exactly");
// the empty-state case: `initial_state` sends "" rather than omitting the field, so a plugin
// never has to tell absent from empty
e = request(OP_INITIAL_STATE, "", "");
assert(req_state_b64(e) == "" && req_arg_b64(e) == "", "empty state/arg survive as empty");
assert(req_op(e) == OP_INITIAL_STATE, "op survives alongside empty payloads");
}
fn test_reply_round_trip_and_fails_closed() {
out = bytes_to_base64([9 as u8, 9 as u8]);
ok = reply_ok(out);
assert(reply_is_ok(ok), "a success reply reads as ok");
assert(reply_out_b64(ok) == out, "its payload round-trips");
er = reply_err(ERR_PLUGIN);
assert(!reply_is_ok(er), "a failure reply does NOT read as ok");
assert(reply_err_code(er) == ERR_PLUGIN, "its code round-trips");
// FAIL-CLOSED: anything that is not a well-formed success must read as failure, never as ok.
// This is the direction that matters — a garbled frame optimistically read as success would
// hand the host whatever bytes happened to decode.
assert(!reply_is_ok([]), "an empty frame is not ok");
assert(!reply_is_ok([1 as u8, 2 as u8, 3 as u8]), "garbage bytes are not ok");
assert(!reply_is_ok(request(OP_RENDER, "", "")), "a REQUEST frame is not a success reply");
}
// Large-payload coverage (loft#651). Every other test used 2-3 byte payloads; a real `merge3`
// frame carries a whole serialized document, so large fields are the COMMON case. This asserts the
// contract holds to 300 bytes. NOTE: loft#651 (the crash where `pa_get`'s old statement-form
// `match` corrupted a heap CborValue past ~54 bytes) is CONTEXT-SENSITIVE — it fires reliably as a
// standalone program but NOT inside this test harness, so this test does not by itself distinguish
// the buggy `pa_get` from the fixed one. The reliable repro is a program (see the zero-trust repo's
// upstream/match-stmt-return-heap-repro.loft); this test guards the contract and documents intent.
fn test_large_payload_round_trip() {
for sz in [1, 40, 53, 54, 100, 300] {
raw: vector<u8> = []; for i in 0..sz { raw += [ ((i * 7 + 3) & 255) as u8 ]; }
b64 = bytes_to_base64(raw);
// request: state AND arg both large; both must survive the read path (arg is the LAST field).
f = request(OP_APPLY_OP, b64, b64);
assert(req_state_b64(f) == b64, "large state round-trips byte-exactly (sz={sz})");
assert(req_arg_b64(f) == b64, "large arg round-trips byte-exactly (sz={sz})");
// reply: a large `out` must survive reply_out_b64, and reply_is_ok must not choke on it.
r = reply_ok(b64);
assert(reply_is_ok(r), "a large success reply reads as ok (sz={sz})");
assert(reply_out_b64(r) == b64, "large reply out round-trips byte-exactly (sz={sz})");
}
println("plugins: pluginabi — large frame payloads (to 300 bytes) round-trip byte-exactly");
}
fn test_check_request_is_the_front_door() {
good = request(OP_SNAPSHOT, "", "");
assert(check_request(good) == "", "a well-formed known op passes");
assert(check_request([7 as u8, 7 as u8]) == ERR_MALFORMED, "undecodable -> malformed");
assert(check_request([]) == ERR_MALFORMED, "empty -> malformed");
// version skew: an op this host does not know must be REJECTED, not guessed at
unknown = request("apply_ops", "", ""); // note the typo/newer name
assert(check_request(unknown) == ERR_UNKNOWN_OP, "an unknown op is rejected, not interpreted");
// the vocabulary is exactly six
n = 0;
for o in [OP_INITIAL_STATE, OP_APPLY_OP, OP_MAKE_OP, OP_RENDER, OP_SNAPSHOT, OP_LOAD_SNAPSHOT] {
if valid_op(o) { n = n + 1; }
}
assert(n == 6, "all six operations are in the vocabulary");
assert(!valid_op("") && !valid_op("eval") && !valid_op("exec"), "nothing else is");
assert(takes_arg(OP_APPLY_OP) && takes_arg(OP_MAKE_OP), "apply_op/make_op read arg");
assert(!takes_arg(OP_SNAPSHOT) && !takes_arg(OP_INITIAL_STATE), "the others do not");
}
// ---------- a complete plugin, driven only by frames ----------
// A counter: state is CBOR { "n": uint }; an op is CBOR { "d": uint } (a delta). The plugin owns
// that encoding — the protocol only carries bytes, which is the §9c.2 contract.
//
// ⚠ This is the package's best worked example, which is why it is TAGGED rather than duplicated:
// what a protocol library owes a reader is not a signature but the SHAPE OF A WHOLE EXCHANGE —
// front door, dispatch, the six operations, a plugin refusing a well-formed request, and a host
// rejecting an op the plugin body never sees.
fn plug_state(n: integer) -> text {
es: vector<CborEntry> = []; es += [ t_te("n", t_cuint(n)) ];
return bytes_to_base64(encode(CMap { entries: es }));
}
fn plug_n(state_b64: text) -> integer {
if state_b64 == "" { return 0; }
return t_uint(t_decode(base64_to_bytes(state_b64)), "n");
}
fn plug_delta(arg_b64: text) -> integer {
if arg_b64 == "" { return 0; }
return t_uint(t_decode(base64_to_bytes(arg_b64)), "d");
}
// THE PLUGIN: one entry point, frame in -> frame out. This is what the wasm pump calls.
fn plugin_dispatch(frame: vector<u8>) -> vector<u8> {
bad = check_request(frame);
if bad != "" { return reply_err(bad); }
op = req_op(frame);
st = req_state_b64(frame);
ar = req_arg_b64(frame);
if op == OP_INITIAL_STATE { return reply_ok(plug_state(0)); }
if op == OP_APPLY_OP { return reply_ok(plug_state(plug_n(st) + plug_delta(ar))); }
if op == OP_MAKE_OP { return reply_ok(ar); }
if op == OP_RENDER { return reply_ok(base64_encode("count={plug_n(st)}")); }
if op == OP_SNAPSHOT { return reply_ok(st); }
if op == OP_LOAD_SNAPSHOT {
if !t_decode_ok(base64_to_bytes(st)) { return reply_err(ERR_PLUGIN); } // foreign bytes
return reply_ok(st);
}
return reply_err(ERR_UNKNOWN_OP);
}
fn call(op: text, st: text, ar: text) -> vector<u8> { return plugin_dispatch(request(op, st, ar)); }
// @PAB-004 — a whole plugin driven end to end over nothing but frames
fn test_a_whole_plugin_over_frames() {
// initial_state
r = call(OP_INITIAL_STATE, "", "");
assert(reply_is_ok(r), "initial_state succeeds");
st = reply_out_b64(r);
assert(plug_n(st) == 0, "a fresh state counts zero");
// make_op then apply_op, twice — the host never touches the plugin's encoding
d5 = reply_out_b64(call(OP_MAKE_OP, st, plug_delta_arg(5)));
st = reply_out_b64(call(OP_APPLY_OP, st, d5));
d7 = reply_out_b64(call(OP_MAKE_OP, st, plug_delta_arg(7)));
st = reply_out_b64(call(OP_APPLY_OP, st, d7));
assert(plug_n(st) == 12, "5 + 7 folded through frames = 12");
// render — the host-visible channel
rr = call(OP_RENDER, st, "");
assert(reply_is_ok(rr) && base64_decode(reply_out_b64(rr)) == "count=12", "render reflects state");
// snapshot -> load_snapshot round-trips to the SAME state (the §9c.1 rollover contract)
snap = reply_out_b64(call(OP_SNAPSHOT, st, ""));
back = call(OP_LOAD_SNAPSHOT, snap, "");
assert(reply_is_ok(back), "a snapshot loads");
assert(plug_n(reply_out_b64(back)) == 12, "and restores the same count");
// a plugin may refuse a well-formed request — foreign snapshot bytes
bad = call(OP_LOAD_SNAPSHOT, bytes_to_base64([200 as u8, 201 as u8]), "");
assert(!reply_is_ok(bad) && reply_err_code(bad) == ERR_PLUGIN,
"load_snapshot on foreign bytes is a plugin error, not a protocol error");
// and the host rejects an op the plugin never sees
sk = plugin_dispatch(request("teleport", st, ""));
assert(!reply_is_ok(sk) && reply_err_code(sk) == ERR_UNKNOWN_OP, "an unknown op never reaches the plugin body");
println("plugins: pluginabi — §9c.2 frame protocol + a whole plugin driven over frames OK");
}
fn plug_delta_arg(d: integer) -> text {
es: vector<CborEntry> = []; es += [ t_te("d", t_cuint(d)) ];
return bytes_to_base64(encode(CMap { entries: es }));
}
tests/worked-examples.loft
// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// One test per non-obvious contract, and each one is that contract's
// worked EXAMPLE.
//
// ⚠⚠ **The pair is the deliverable.** A snippet in a doc comment rots
// silently — nothing compiles it, so it goes stale at the first
// signature change and the reader finds out. A test nobody links to is
// correct and invisible. So every function below carries an
// `// Example: @PAB-0NN` line pointing here, and every tag here is
// reachable from the functions it demonstrates.
//
// ⚠ These are examples FIRST and assertions second: each is written the
// way a host or a plugin author would actually write the call.
// `tests/pluginabi.loft` already pins that the frames round-trip; what
// these pin is the shape of a CORRECT CALL — and `@PAB-004` is that
// file's whole-plugin test, which is the best example this package has.
//
// Why this library owes them: **a frame is `vector<u8>` and every
// payload is `text`.** The signatures say bytes-in / bytes-out and
// text-in / text-out, and never that the text must be BASE64, that the
// `text` a reply hands back is `""` for two opposite outcomes, or that
// the front door checks the envelope and not the letter. Each is a
// well-typed call that answers something other than it appears to.
use pluginabi::*;
use cbor::*;
use crypto::*;
// Sending a payload.
//
// ⚠⚠ **Every payload crossing this API is BASE64 TEXT, not the bytes and
// not the string.** The frame is `vector<u8>`, so the base64 layer is
// invisible at the call site — so `request` checks it: a raw string that is
// not base64 answers an empty frame, which the front door refuses.
// @PAB-001 — a payload is base64 text, and a raw string is refused at the front door
fn test_a_payload_is_base64_text() {
// ⚠ The mistake, spelled the way it types. "hello" is five characters and
// not base64 — `base64_to_bytes` answers nothing for it — so `request`
// answers an empty frame, and the front door refuses it as malformed.
pb_wrong = request(OP_APPLY_OP, "hello", "");
assert(len(base64_to_bytes("hello")) == 0, "a raw string is not base64");
assert(len(pb_wrong) == 0, "so there is no frame to send");
assert(check_request(pb_wrong) == ERR_MALFORMED, "and the front door says so");
// The correct spelling — encode at the boundary, decode at the far end:
pb_right = request(OP_APPLY_OP, base64_encode("hello"), "");
assert(req_state_b64(pb_right) == "aGVsbG8=", "base64 on the wire");
assert(base64_decode(req_state_b64(pb_right)) == "hello", "and back out again");
// For real binary state — which is the common case, since a plugin owns
// its own encoding — `bytes_to_base64` is the door, and it survives a
// byte no UTF-8 reader could carry.
pb_raw: vector<u8> = [0 as u8, 200 as u8, 255 as u8];
pb_bin = request(OP_APPLY_OP, bytes_to_base64(pb_raw), "");
assert(len(base64_to_bytes(req_state_b64(pb_bin))) == 3, "three bytes in, three out");
assert(bytes_to_base64(pb_raw) == req_state_b64(pb_bin), "byte-exact");
}
// Reading a reply.
//
// ⚠⚠ **`reply_is_ok` is the only function that classifies a reply.**
// `reply_out_b64` answers `""` for a failure — the same `""` a success
// carrying an empty payload answers — and `reply_err_code` answers `""`
// for a success, the same `""` an error code would have to be to be
// absent. So neither accessor can be read as a verdict, in either
// direction.
// @PAB-002 — ask reply_is_ok first; "" is both outcomes' answer
fn test_only_reply_is_ok_classifies_a_reply() {
rp_fail = reply_err(ERR_PLUGIN);
rp_empty = reply_ok(""); // a genuine success with nothing to return
// ⚠ The two payload reads are indistinguishable:
assert(reply_out_b64(rp_fail) == "", "a failure has no out");
assert(reply_out_b64(rp_empty) == "", "and neither has an empty success");
// ⚠ ...and so are the two code reads, the other way round:
assert(reply_err_code(rp_empty) == "", "a success has no code");
assert(reply_err_code(rp_fail) == ERR_PLUGIN, "the failure's code is there");
// The verdict, and the only one:
assert(!reply_is_ok(rp_fail), "the failure is a failure");
assert(reply_is_ok(rp_empty), "the empty success is a success");
// It fails CLOSED, which is the direction that matters: a truncated or
// garbled frame, or one sent in the wrong direction, is never read as
// a success carrying whatever bytes happened to decode.
assert(!reply_is_ok([]), "an empty frame is not a success");
assert(!reply_is_ok([1 as u8, 2 as u8, 3 as u8]), "garbage is not a success");
assert(!reply_is_ok(request(OP_RENDER, "", "")), "a REQUEST is not a success reply");
// So the host's read order is fixed: verdict, then payload.
assert(host_reads(rp_empty) == "ok:", "an empty success reads {host_reads(rp_empty)}");
assert(host_reads(rp_fail) == "err:plugin-error", "a failure reads {host_reads(rp_fail)}");
assert(host_reads([9 as u8]) == "err:", "garbage reads {host_reads([9 as u8])}");
}
// The read order the example above recommends: classify, then read the
// one field that outcome has.
fn host_reads(hr_frame: vector<u8>) -> text {
if reply_is_ok(hr_frame) { return "ok:{reply_out_b64(hr_frame)}"; }
return "err:{reply_err_code(hr_frame)}";
}
// Admitting an inbound request.
//
// ⚠⚠ **`check_request` checks the ENVELOPE, and `""` is its pass.** It
// decodes the frame and looks the op up in the vocabulary — nothing
// else. A known op carrying payload bytes the plugin cannot use passes;
// an `arg` present for an operation that does not take one passes; and a
// REPLY frame handed to it comes back `unknown-op`, because a reply
// decodes perfectly and simply has no `op`.
// @PAB-003 — the front door validates the frame, never the payload
fn test_the_front_door_checks_the_envelope() {
// ⚠ `""` means ACCEPTABLE. The return is the code to reply WITH, so
// the check reads backwards from every boolean guard around it.
fd_good = request(OP_SNAPSHOT, base64_encode("state"), "");
assert(check_request(fd_good) == "", "a well-formed known op passes");
// What it does catch, and the two codes it distinguishes:
assert(check_request([7 as u8, 7 as u8]) == ERR_MALFORMED, "undecodable is malformed");
assert(check_request([]) == ERR_MALFORMED, "so is empty");
assert(check_request(request("apply_ops", "", "")) == ERR_UNKNOWN_OP,
"a near-miss op is version skew, not a request to interpret");
// ⚠ What it does NOT catch — a known op whose state is bytes no plugin
// could load. That is the PLUGIN's answer to give, and `ERR_PLUGIN`
// is how it says so; the front door has no opinion.
fd_foreign = request(OP_LOAD_SNAPSHOT, bytes_to_base64([200 as u8, 201 as u8]), "");
assert(check_request(fd_foreign) == "", "foreign payload bytes are admitted");
// ⚠ Nor an `arg` sent for an operation that reads none. `takes_arg`
// records which four do not, so the two sides cannot drift — but it is
// a contract both must consult, not a gate either one enforces.
fd_extra = request(OP_SNAPSHOT, base64_encode("s"), base64_encode("a"));
assert(!takes_arg(OP_SNAPSHOT), "snapshot reads no arg");
assert(check_request(fd_extra) == "", "yet a frame carrying one is admitted");
assert(req_arg_b64(fd_extra) == base64_encode("a"), "and the arg is sitting there, readable");
// ⚠ And the honest limit: a REPLY frame is well-formed CBOR with no
// `op`, so the missing key reports as an unknown operation. A host
// chasing `unknown-op` is being pointed at version skew when the real
// fault is a frame going the wrong way.
assert(check_request(reply_ok(base64_encode("x"))) == ERR_UNKNOWN_OP,
"a reply frame at the request door reports {check_request(reply_ok(base64_encode(\"x\")))}");
// The vocabulary is exactly six, and nothing else is in it.
fd_n = 0;
for fd_o in [OP_INITIAL_STATE, OP_APPLY_OP, OP_MAKE_OP,
OP_RENDER, OP_SNAPSHOT, OP_LOAD_SNAPSHOT] {
if valid_op(fd_o) { fd_n = fd_n + 1; }
}
assert(fd_n == 6, "all six are in the vocabulary");
assert(!valid_op("") && !valid_op("eval"), "and nothing else is");
}
fn main() {
test_a_payload_is_base64_text();
test_only_reply_is_ok_classifies_a_reply();
test_the_front_door_checks_the_envelope();
println("pluginabi worked examples passed");
}