Module: Kobako::Outcome
- Defined in:
- lib/kobako/outcome.rb,
lib/kobako/outcome/panic.rb
Overview
Host-facing boundary for the OUTCOME_BUFFER produced by __kobako_eval. Takes raw outcome bytes — a one-byte tag followed by the msgpack-encoded body — and maps them to either the unwrapped mruby return value or a raised three-layer (docs/behavior.md Error Scenarios) exception.
Self-contained: this module owns the wire framing (tag bytes, body decoding), and the Panic wire record lives at Kobako::Outcome::Panic. The byte-level msgpack codec at Kobako::Codec is invoked for the body itself; otherwise nothing in RPC participates.
* tag 0x01, decode OK → return decoded value
* tag 0x01, decode fails → SandboxError (E-09)
* tag 0x02, origin="service" → ServiceError (E-13)
* tag 0x02, origin="sandbox"/missing → SandboxError (E-04..E-07)
* tag 0x02, decode fails → SandboxError (E-08)
* unknown tag → TrapError (E-03)
Defined Under Namespace
Classes: Panic
Constant Summary collapse
- TYPE_VALUE =
First byte of the OUTCOME_BUFFER for the success branch — body is the bare msgpack encoding of the returned value (docs/wire-contract.md Outcome Envelope).
0x01- TYPE_PANIC =
First byte of the OUTCOME_BUFFER for the failure branch — body is the msgpack Panic map.
0x02
Class Method Summary collapse
-
.build_panic_error(panic) ⇒ Object
Map a decoded Panic record into the corresponding three-layer Ruby exception.
-
.build_trap_error(tag) ⇒ Object
TrapError for unknown or absent tag (docs/wire-codec.md ABI Signatures: zero-length output and unrecognised first byte both walk the trap path).
-
.build_wire_violation_error(message, wire_error: nil) ⇒ Object
Lift the wire-violation fallback to the real
Kobako::RPC::WireErrorclass so callers canrescueit specifically instead of pattern-matching onerror.klass. - .decode(bytes) ⇒ Object
-
.decode_panic(body) ⇒ Object
Decode failure on the panic tag is a SandboxError (E-08).
-
.decode_value(body) ⇒ Object
Decode failure on the success tag is a SandboxError (E-09): the framing was fine, but the carried value is unrepresentable.
-
.panic_target_class(panic) ⇒ Object
docs/behavior.md Error Classes: map the panic
classfield to the matching Ruby exception subclass so callers can rescue specific failure paths. -
.parse_panic(body) ⇒ Object
Build a
Panicvalue object from the msgpack-decoded body. - .split_tag(bytes) ⇒ Object
Class Method Details
.build_panic_error(panic) ⇒ Object
Map a decoded Panic record into the corresponding three-layer Ruby exception. origin == “service” → ServiceError (with the ::Disconnected subclass selected when the panic carries the disconnected sentinel —docs/behavior.md Error Classes); everything else → SandboxError.
126 127 128 129 130 131 132 133 134 |
# File 'lib/kobako/outcome.rb', line 126 def build_panic_error(panic) panic_target_class(panic).new( panic., origin: panic.origin, klass: panic.klass, backtrace_lines: panic.backtrace, details: panic.details ) end |
.build_trap_error(tag) ⇒ Object
TrapError for unknown or absent tag (docs/wire-codec.md ABI Signatures: zero-length output and unrecognised first byte both walk the trap path). The user-facing message stays in caller vocabulary — the raw tag byte (or absence) belongs in details for operators, not in the message a caller sees.
54 55 56 57 58 59 60 61 62 63 |
# File 'lib/kobako/outcome.rb', line 54 def build_trap_error(tag) if tag.nil? TrapError.new("Sandbox exited without producing a result") else TrapError.new( "Sandbox produced an unrecognised result; the runtime is corrupted, " \ "discard this Sandbox before another invocation" ) end end |
.build_wire_violation_error(message, wire_error: nil) ⇒ Object
Lift the wire-violation fallback to the real Kobako::RPC::WireError class so callers can rescue it specifically instead of pattern-matching on error.klass. The klass field is still populated so existing operator-side tooling that greps on the string continues to work. wire_error carries the inner codec / framing message for operator diagnosis without polluting the user-facing #message.
161 162 163 164 165 166 167 168 |
# File 'lib/kobako/outcome.rb', line 161 def build_wire_violation_error(, wire_error: nil) Kobako::RPC::WireError.new( , origin: Panic::ORIGIN_SANDBOX, klass: "Kobako::RPC::WireError", details: wire_error.nil? ? nil : { wire_error: wire_error } ) end |
.decode(bytes) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/kobako/outcome.rb', line 36 def decode(bytes) tag, body = split_tag(bytes) case tag when TYPE_VALUE decode_value(body) when TYPE_PANIC decode_panic(body) else raise build_trap_error(tag) end end |
.decode_panic(body) ⇒ Object
Decode failure on the panic tag is a SandboxError (E-08). Either path raises — on success the decoded Panic is mapped to its three- layer exception via build_panic_error and raised; on wire-decode failure the rescue path raises the wire-violation SandboxError.
93 94 95 96 97 98 99 100 |
# File 'lib/kobako/outcome.rb', line 93 def decode_panic(body) raise build_panic_error(parse_panic(body)) rescue Kobako::Codec::Error => e raise build_wire_violation_error( "Sandbox produced an invalid panic record", wire_error: e. ) end |
.decode_value(body) ⇒ Object
Decode failure on the success tag is a SandboxError (E-09): the framing was fine, but the carried value is unrepresentable. The specific codec fault is stashed in details[:wire_error] rather than spliced into the message — callers cannot act on the inner “Symbol payload must be …” wording, but operators triaging a corrupted Sandbox runtime still need it.
80 81 82 83 84 85 86 87 |
# File 'lib/kobako/outcome.rb', line 80 def decode_value(body) Kobako::Codec::Decoder.decode(body) rescue Kobako::Codec::Error => e raise build_wire_violation_error( "Sandbox produced an invalid result value", wire_error: e. ) end |
.panic_target_class(panic) ⇒ Object
docs/behavior.md Error Classes: map the panic class field to the matching Ruby exception subclass so callers can rescue specific failure paths. origin=“service” plus class=“Kobako::ServiceError::Disconnected” selects the Disconnected subclass (E-14); origin=“sandbox” plus class=“Kobako::BytecodeError” selects the BytecodeError subclass (E-37 / E-38). Everything else falls back to the base class for the origin.
144 145 146 147 148 149 150 151 |
# File 'lib/kobako/outcome.rb', line 144 def panic_target_class(panic) case panic.origin when Panic::ORIGIN_SERVICE panic.klass == "Kobako::ServiceError::Disconnected" ? ServiceError::Disconnected : ServiceError else panic.klass == "Kobako::BytecodeError" ? BytecodeError : SandboxError end end |
.parse_panic(body) ⇒ Object
Build a Panic value object from the msgpack-decoded body. Raises Kobako::Codec::InvalidType when the body is not a map or when required keys are missing — both routed by decode_panic to build_wire_violation_error. The InvalidType message itself is never user-facing; it lands in details[:wire_error] via the rescue chain above.
108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/kobako/outcome.rb', line 108 def parse_panic(body) map = Kobako::Codec::Decoder.decode(body) raise Kobako::Codec::InvalidType, "panic body must be a map, got #{map.class}" unless map.is_a?(Hash) Kobako::Codec::Utils.wire_boundary do Panic.new( origin: map["origin"], klass: map["class"], message: map["message"], backtrace: map["backtrace"] || [], details: map["details"] ) end end |
.split_tag(bytes) ⇒ Object
65 66 67 68 69 70 71 72 |
# File 'lib/kobako/outcome.rb', line 65 def split_tag(bytes) bytes = bytes.b return [nil, "".b] if bytes.empty? tag = bytes.getbyte(0) # : Integer body = bytes.byteslice(1, bytes.bytesize - 1) # : String [tag, body] end |