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 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 Transport participates.
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_panic_record(body) ⇒ Object
Build a
Panicvalue object from the msgpack-decoded body. -
.build_transport_error(message, detail: nil) ⇒ Object
Lift the wire-violation fallback to the real
Kobako::Transport::Errorclass so callers canrescueit specifically instead of pattern-matching onerror.klass. -
.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).
- .decode(bytes) ⇒ Object
-
.decode_panic(body) ⇒ Object
Decode failure on the panic tag is a SandboxError.
-
.decode_value(body) ⇒ Object
Decode failure on the success tag is a SandboxError: the framing was fine, but the carried value is unrepresentable.
-
.panic_target_class(panic) ⇒ Object
Map the panic
classfield to the matching Ruby exception subclass so callers can rescue specific failure paths. - .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; everything else → SandboxError.
114 115 116 117 118 119 120 121 122 |
# File 'lib/kobako/outcome.rb', line 114 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_panic_record(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_transport_error. The decode runs in block form so Panic.new‘s ArgumentError invariants surface as InvalidType through the decoder boundary; the message itself is never user- facing — it lands in details via the rescue chain above.
100 101 102 103 104 105 106 107 108 109 |
# File 'lib/kobako/outcome.rb', line 100 def build_panic_record(body) Kobako::Codec::Decoder.decode(body) do |map| raise Kobako::Codec::InvalidType, "panic body must be a map, got #{map.class}" unless map.is_a?(Hash) Panic.new( origin: map["origin"], klass: map["class"], message: map["message"], backtrace: map["backtrace"] || [], details: map["details"] ) end end |
.build_transport_error(message, detail: nil) ⇒ Object
Lift the wire-violation fallback to the real Kobako::Transport::Error 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. detail carries the inner codec / framing message, stashed directly into details for operator diagnosis without polluting the user-facing #message.
147 148 149 150 151 152 153 154 |
# File 'lib/kobako/outcome.rb', line 147 def build_transport_error(, detail: nil) Kobako::Transport::Error.new( , origin: Panic::ORIGIN_SANDBOX, klass: "Kobako::Transport::Error", details: detail ) 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 absent-vs-present distinction selects the message; the raw byte is not actionable to a caller and is not surfaced.
45 46 47 48 49 50 51 52 53 54 |
# File 'lib/kobako/outcome.rb', line 45 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 |
.decode(bytes) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/kobako/outcome.rb', line 28 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. 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.
84 85 86 87 88 89 90 91 |
# File 'lib/kobako/outcome.rb', line 84 def decode_panic(body) raise build_panic_error(build_panic_record(body)) rescue Kobako::Codec::Error => e raise build_transport_error( "Sandbox produced an invalid panic record", detail: e. ) end |
.decode_value(body) ⇒ Object
Decode failure on the success tag is a SandboxError: the framing was fine, but the carried value is unrepresentable. The specific codec fault is stashed in details 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.
71 72 73 74 75 76 77 78 |
# File 'lib/kobako/outcome.rb', line 71 def decode_value(body) Kobako::Codec::Decoder.decode(body) rescue Kobako::Codec::Error => e raise build_transport_error( "Sandbox produced an invalid result value", detail: e. ) end |
.panic_target_class(panic) ⇒ Object
Map the panic class field to the matching Ruby exception subclass so callers can rescue specific failure paths. origin=“service” →ServiceError; origin=“sandbox” plus class=“Kobako::BytecodeError” selects the BytecodeError subclass. Everything else falls back to the base class for the origin.
130 131 132 133 134 135 136 137 |
# File 'lib/kobako/outcome.rb', line 130 def panic_target_class(panic) case panic.origin when Panic::ORIGIN_SERVICE ServiceError else panic.klass == "Kobako::BytecodeError" ? BytecodeError : SandboxError end end |
.split_tag(bytes) ⇒ Object
56 57 58 59 60 61 62 63 |
# File 'lib/kobako/outcome.rb', line 56 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 |