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_run. 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 (SPEC.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 (SPEC.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

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 —SPEC “Error Classes”); everything else → SandboxError.



105
106
107
108
109
110
111
112
113
# File 'lib/kobako/outcome.rb', line 105

def build_panic_error(panic)
  panic_target_class(panic).new(
    panic.message,
    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 (SPEC.md ABI Signatures: len=0 and unknown-tag both walk the trap path).



50
51
52
53
54
# File 'lib/kobako/outcome.rb', line 50

def build_trap_error(tag)
  return TrapError.new("guest exited without writing an outcome (len=0)") if tag.nil?

  TrapError.new(format("unknown outcome tag 0x%<tag>02x", tag: tag))
end

.build_wire_violation_error(message) ⇒ Object



125
126
127
128
129
130
131
# File 'lib/kobako/outcome.rb', line 125

def build_wire_violation_error(message)
  SandboxError.new(
    message,
    origin: Panic::ORIGIN_SANDBOX,
    klass: "Kobako::RPC::WireError"
  )
end

.decode(bytes) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/kobako/outcome.rb', line 35

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.



77
78
79
80
81
# File 'lib/kobako/outcome.rb', line 77

def decode_panic(body)
  raise build_panic_error(parse_panic(body))
rescue Kobako::Codec::Error => e
  raise build_wire_violation_error("panic envelope decode failed: #{e.message}")
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.



67
68
69
70
71
# File 'lib/kobako/outcome.rb', line 67

def decode_value(body)
  Kobako::Codec::Decoder.decode(body)
rescue Kobako::Codec::Error => e
  raise build_wire_violation_error("result envelope decode failed: #{e.message}")
end

.panic_target_class(panic) ⇒ Object

SPEC “Error Classes”: when origin=“service” and the panic class field names ServiceError::Disconnected, surface that subclass so callers can rescue the disconnected path specifically (E-14).



119
120
121
122
123
# File 'lib/kobako/outcome.rb', line 119

def panic_target_class(panic)
  return SandboxError unless panic.origin == Panic::ORIGIN_SERVICE

  panic.klass == "Kobako::ServiceError::Disconnected" ? ServiceError::Disconnected : ServiceError
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.



87
88
89
90
91
92
93
94
95
96
97
# File 'lib/kobako/outcome.rb', line 87

def parse_panic(body)
  map = Kobako::Codec::Decoder.decode(body)
  raise Kobako::Codec::InvalidType, "Panic envelope 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



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