kobako 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Cargo.lock +2347 -0
- data/Cargo.toml +11 -0
- data/LICENSE +201 -0
- data/README.md +228 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +36 -0
- data/ext/kobako/extconf.rb +6 -0
- data/ext/kobako/src/lib.rs +10 -0
- data/ext/kobako/src/wasm/cache.rs +92 -0
- data/ext/kobako/src/wasm/dispatch.rs +110 -0
- data/ext/kobako/src/wasm/host_state.rs +59 -0
- data/ext/kobako/src/wasm/instance.rs +361 -0
- data/ext/kobako/src/wasm.rs +80 -0
- data/lib/kobako/errors.rb +88 -0
- data/lib/kobako/registry/dispatcher.rb +168 -0
- data/lib/kobako/registry/handle_table.rb +107 -0
- data/lib/kobako/registry/service_group.rb +65 -0
- data/lib/kobako/registry.rb +160 -0
- data/lib/kobako/sandbox/outcome_decoder.rb +100 -0
- data/lib/kobako/sandbox/output_buffer.rb +79 -0
- data/lib/kobako/sandbox.rb +148 -0
- data/lib/kobako/version.rb +5 -0
- data/lib/kobako/wasm.rb +35 -0
- data/lib/kobako/wire/codec/decoder.rb +87 -0
- data/lib/kobako/wire/codec/encoder.rb +41 -0
- data/lib/kobako/wire/codec/error.rb +35 -0
- data/lib/kobako/wire/codec/factory.rb +136 -0
- data/lib/kobako/wire/codec.rb +44 -0
- data/lib/kobako/wire/envelope/payloads.rb +145 -0
- data/lib/kobako/wire/envelope.rb +147 -0
- data/lib/kobako/wire/exception.rb +38 -0
- data/lib/kobako/wire/handle.rb +36 -0
- data/lib/kobako/wire.rb +40 -0
- data/lib/kobako.rb +7 -0
- data/sig/kobako.rbs +4 -0
- metadata +112 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../codec"
|
|
4
|
+
|
|
5
|
+
module Kobako
|
|
6
|
+
module Wire
|
|
7
|
+
# Outcome-path envelopes (SPEC.md Outcome Envelope): Result and Panic
|
|
8
|
+
# value objects plus the tagged Outcome wrapper that frames them on
|
|
9
|
+
# the wire. The RPC-path counterparts (Request / Response) live in
|
|
10
|
+
# the parent +envelope.rb+ file.
|
|
11
|
+
module Envelope
|
|
12
|
+
# ============================================================
|
|
13
|
+
# Result (SPEC.md Outcome Envelope → Result)
|
|
14
|
+
# ============================================================
|
|
15
|
+
#
|
|
16
|
+
# Success Outcome payload. SPEC pins the Result envelope as a
|
|
17
|
+
# 1-element msgpack array carrying the value, keeping framing
|
|
18
|
+
# symmetric with the Panic envelope so the value position is never
|
|
19
|
+
# ambiguous.
|
|
20
|
+
Result = Data.define(:value)
|
|
21
|
+
|
|
22
|
+
def self.encode_result(value)
|
|
23
|
+
Codec::Encoder.encode([value])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.decode_result(bytes)
|
|
27
|
+
arr = Codec::Decoder.decode(bytes)
|
|
28
|
+
unless arr.is_a?(Array) && arr.length == 1
|
|
29
|
+
raise Codec::InvalidType, "Result envelope must be a 1-element array, got #{arr.inspect}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Result.new(arr[0])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# ============================================================
|
|
36
|
+
# Panic (SPEC.md Outcome Envelope → Panic)
|
|
37
|
+
# ============================================================
|
|
38
|
+
#
|
|
39
|
+
# Failure Outcome payload. Encoded as a msgpack **map** keyed by
|
|
40
|
+
# name (forward-compatibility — unknown keys are silently ignored).
|
|
41
|
+
# Required: "origin" / "class" / "message". Optional: "backtrace"
|
|
42
|
+
# (array of str), "details" (any wire-legal value).
|
|
43
|
+
Panic = Data.define(:origin, :klass, :message, :backtrace, :details) do
|
|
44
|
+
def initialize(origin:, klass:, message:, backtrace: [], details: nil)
|
|
45
|
+
raise ArgumentError, "Panic origin must be String" unless origin.is_a?(String)
|
|
46
|
+
raise ArgumentError, "Panic class must be String" unless klass.is_a?(String)
|
|
47
|
+
raise ArgumentError, "Panic message must be String" unless message.is_a?(String)
|
|
48
|
+
unless backtrace.is_a?(Array) && backtrace.all?(String)
|
|
49
|
+
raise ArgumentError, "Panic backtrace must be Array of String"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Panic::ORIGIN_SANDBOX = "sandbox"
|
|
57
|
+
Panic::ORIGIN_SERVICE = "service"
|
|
58
|
+
|
|
59
|
+
def self.encode_panic(panic)
|
|
60
|
+
Codec::Encoder.encode(panic_map(panic))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# SPEC: Panic is a msgpack MAP keyed by name. Required keys always
|
|
64
|
+
# emitted; "backtrace" emitted only when non-empty (keep the wire
|
|
65
|
+
# compact); "details" only when non-nil. Ruby Hash preserves
|
|
66
|
+
# insertion order so the resulting msgpack map carries the keys in
|
|
67
|
+
# the order added below.
|
|
68
|
+
def self.panic_map(panic)
|
|
69
|
+
map = { "origin" => panic.origin, "class" => panic.klass, "message" => panic.message }
|
|
70
|
+
map["backtrace"] = panic.backtrace unless panic.backtrace.empty?
|
|
71
|
+
map["details"] = panic.details unless panic.details.nil?
|
|
72
|
+
map
|
|
73
|
+
end
|
|
74
|
+
private_class_method :panic_map
|
|
75
|
+
|
|
76
|
+
def self.decode_panic(bytes)
|
|
77
|
+
map = Codec::Decoder.decode(bytes)
|
|
78
|
+
raise Codec::InvalidType, "Panic envelope must be a map, got #{map.class}" unless map.is_a?(Hash)
|
|
79
|
+
|
|
80
|
+
Codec.translate_value_object_error do
|
|
81
|
+
Panic.new(
|
|
82
|
+
origin: map["origin"], klass: map["class"], message: map["message"],
|
|
83
|
+
backtrace: map["backtrace"] || [], details: map["details"]
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# ============================================================
|
|
89
|
+
# Outcome (SPEC.md Outcome Envelope)
|
|
90
|
+
# ============================================================
|
|
91
|
+
#
|
|
92
|
+
# OUTCOME_BUFFER wrapper: one-byte tag (+0x01+ Result, +0x02+ Panic)
|
|
93
|
+
# followed by the msgpack payload of the corresponding envelope.
|
|
94
|
+
# Callers construct an +Outcome+ by wrapping the payload directly —
|
|
95
|
+
# +Outcome.new(Result.new(value))+ or +Outcome.new(panic)+ — so the
|
|
96
|
+
# contract reads symmetrically across both variants.
|
|
97
|
+
Outcome = Data.define(:payload) do
|
|
98
|
+
def initialize(payload:)
|
|
99
|
+
unless payload.is_a?(Result) || payload.is_a?(Panic)
|
|
100
|
+
raise ArgumentError, "Outcome payload must be Result or Panic, got #{payload.class}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
super
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def result? = payload.is_a?(Result)
|
|
107
|
+
def panic? = payload.is_a?(Panic)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.encode_outcome(outcome)
|
|
111
|
+
tag, body = encode_outcome_payload(outcome.payload)
|
|
112
|
+
out = String.new(encoding: Encoding::ASCII_8BIT)
|
|
113
|
+
out << [tag].pack("C")
|
|
114
|
+
out << body
|
|
115
|
+
out
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.encode_outcome_payload(payload)
|
|
119
|
+
case payload
|
|
120
|
+
when Result then [OUTCOME_TAG_RESULT, encode_result(payload.value)]
|
|
121
|
+
when Panic then [OUTCOME_TAG_PANIC, encode_panic(payload)]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
private_class_method :encode_outcome_payload
|
|
125
|
+
|
|
126
|
+
def self.decode_outcome(bytes)
|
|
127
|
+
bytes = bytes.b
|
|
128
|
+
raise Codec::InvalidType, "Outcome bytes must not be empty" if bytes.empty?
|
|
129
|
+
|
|
130
|
+
tag = bytes.getbyte(0)
|
|
131
|
+
body = bytes.byteslice(1, bytes.bytesize - 1)
|
|
132
|
+
Outcome.new(decode_outcome_payload(tag, body))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.decode_outcome_payload(tag, body)
|
|
136
|
+
case tag
|
|
137
|
+
when OUTCOME_TAG_RESULT then decode_result(body)
|
|
138
|
+
when OUTCOME_TAG_PANIC then decode_panic(body)
|
|
139
|
+
else raise Codec::InvalidType, format("unknown outcome tag 0x%<tag>02x", tag: tag)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
private_class_method :decode_outcome_payload
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "handle"
|
|
4
|
+
require_relative "exception"
|
|
5
|
+
require_relative "codec"
|
|
6
|
+
|
|
7
|
+
module Kobako
|
|
8
|
+
module Wire
|
|
9
|
+
# Envelope-layer encoders/decoders for the kobako wire contract.
|
|
10
|
+
#
|
|
11
|
+
# SPEC.md → Wire Contract pins the logical shape of every host↔guest
|
|
12
|
+
# message and SPEC.md → Wire Codec → Envelope Frame Layout pins the
|
|
13
|
+
# binary framing. This module assembles the four envelope kinds
|
|
14
|
+
# (Request, Response, Result, Panic) and the outer Outcome wrapper on
|
|
15
|
+
# top of the lower-level {Codec::Encoder} / {Codec::Decoder} primitives.
|
|
16
|
+
#
|
|
17
|
+
# The contract collapses into two wire paths:
|
|
18
|
+
#
|
|
19
|
+
# - **RPC path** (lives in this file): Request / Response — guest
|
|
20
|
+
# calls a Service, host returns a value or an Exception.
|
|
21
|
+
# - **Outcome path** (lives in +envelope/payloads.rb+): Result /
|
|
22
|
+
# Panic wrapped in an Outcome envelope — the host reads this
|
|
23
|
+
# after +__kobako_run+ to surface either the script's last
|
|
24
|
+
# expression or a Sandbox/Service panic.
|
|
25
|
+
#
|
|
26
|
+
# The envelope objects are plain Value Objects; they own the field
|
|
27
|
+
# invariants (raising +ArgumentError+ on violation). The encode/decode
|
|
28
|
+
# helpers around them own the msgpack framing and translate value-
|
|
29
|
+
# object faults into the wire-layer +Codec::InvalidType+ taxonomy.
|
|
30
|
+
module Envelope
|
|
31
|
+
# ---------------- Outcome tag bytes (SPEC.md Outcome Envelope) -----
|
|
32
|
+
|
|
33
|
+
# First byte of the OUTCOME_BUFFER for a Result envelope.
|
|
34
|
+
OUTCOME_TAG_RESULT = 0x01
|
|
35
|
+
# First byte of the OUTCOME_BUFFER for a Panic envelope.
|
|
36
|
+
OUTCOME_TAG_PANIC = 0x02
|
|
37
|
+
|
|
38
|
+
# ---------------- Response status bytes (SPEC.md Response Shape) ---
|
|
39
|
+
|
|
40
|
+
# Response variant marker for the success branch.
|
|
41
|
+
STATUS_OK = 0
|
|
42
|
+
# Response variant marker for the error branch.
|
|
43
|
+
STATUS_ERROR = 1
|
|
44
|
+
|
|
45
|
+
# ============================================================
|
|
46
|
+
# Request (SPEC.md Wire Codec → Request)
|
|
47
|
+
# ============================================================
|
|
48
|
+
#
|
|
49
|
+
# 4-element msgpack array: [target, method, args, kwargs]. +target+
|
|
50
|
+
# is either a String ("Group::Member") or a {Handle}. SPEC pins
|
|
51
|
+
# +kwargs+ map keys to ext 0x00 Symbol (→ Wire Codec → Ext Types);
|
|
52
|
+
# enforced at construction so the Value Object is the single source
|
|
53
|
+
# of truth.
|
|
54
|
+
Request = Data.define(:target, :method_name, :args, :kwargs) do
|
|
55
|
+
def initialize(target:, method:, args: [], kwargs: {})
|
|
56
|
+
unless target.is_a?(String) || target.is_a?(Handle)
|
|
57
|
+
raise ArgumentError, "Request target must be String or Handle, got #{target.class}"
|
|
58
|
+
end
|
|
59
|
+
raise ArgumentError, "Request method must be String" unless method.is_a?(String)
|
|
60
|
+
raise ArgumentError, "Request args must be Array" unless args.is_a?(Array)
|
|
61
|
+
|
|
62
|
+
validate_kwargs!(kwargs)
|
|
63
|
+
super(target: target, method_name: method, args: args, kwargs: kwargs)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def validate_kwargs!(kwargs)
|
|
69
|
+
raise ArgumentError, "Request kwargs must be Hash" unless kwargs.is_a?(Hash)
|
|
70
|
+
|
|
71
|
+
kwargs.each_key do |k|
|
|
72
|
+
raise ArgumentError, "Request kwargs keys must be Symbol, got #{k.class}" unless k.is_a?(Symbol)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Encode a {Request} to bytes. The Value Object's own invariants
|
|
78
|
+
# are the contract; this method does not re-check the shape.
|
|
79
|
+
def self.encode_request(request)
|
|
80
|
+
Codec::Encoder.encode([request.target, request.method_name, request.args, request.kwargs])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.decode_request(bytes)
|
|
84
|
+
arr = Codec::Decoder.decode(bytes)
|
|
85
|
+
unless arr.is_a?(Array) && arr.length == 4
|
|
86
|
+
raise Codec::InvalidType, "Request must be a 4-element array, got #{arr.inspect}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
target, method_name, args, kwargs = arr
|
|
90
|
+
Codec.translate_value_object_error do
|
|
91
|
+
Request.new(target: target, method: method_name, args: args, kwargs: kwargs)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# ============================================================
|
|
96
|
+
# Response (SPEC.md Wire Codec → Response)
|
|
97
|
+
# ============================================================
|
|
98
|
+
#
|
|
99
|
+
# 2-element msgpack array: [status, value-or-error]. +status+ is 0
|
|
100
|
+
# (success) or 1 (error). For success the second element is the
|
|
101
|
+
# return value; for error it is an {Exception} (ext 0x02 envelope).
|
|
102
|
+
Response = Data.define(:status, :payload) do
|
|
103
|
+
def self.ok(value)
|
|
104
|
+
new(status: STATUS_OK, payload: value)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.err(exception)
|
|
108
|
+
unless exception.is_a?(Exception)
|
|
109
|
+
raise ArgumentError, "Response.err requires Kobako::Wire::Exception, got #{exception.class}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
new(status: STATUS_ERROR, payload: exception)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def initialize(status:, payload:)
|
|
116
|
+
unless [STATUS_OK, STATUS_ERROR].include?(status)
|
|
117
|
+
raise ArgumentError, "Response status must be 0 or 1, got #{status.inspect}"
|
|
118
|
+
end
|
|
119
|
+
if status == STATUS_ERROR && !payload.is_a?(Exception)
|
|
120
|
+
raise ArgumentError, "Response status=1 payload must be Kobako::Wire::Exception"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
super
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def ok? = status == STATUS_OK
|
|
127
|
+
def err? = status == STATUS_ERROR
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def self.encode_response(response)
|
|
131
|
+
Codec::Encoder.encode([response.status, response.payload])
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def self.decode_response(bytes)
|
|
135
|
+
arr = Codec::Decoder.decode(bytes)
|
|
136
|
+
unless arr.is_a?(Array) && arr.length == 2
|
|
137
|
+
raise Codec::InvalidType, "Response must be a 2-element array, got #{arr.inspect}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
status, payload = arr
|
|
141
|
+
Codec.translate_value_object_error { Response.new(status: status, payload: payload) }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
require_relative "envelope/payloads"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
module Wire
|
|
5
|
+
# Wire-level value object for an ext-0x02 Exception envelope.
|
|
6
|
+
#
|
|
7
|
+
# SPEC pins the payload (Wire Codec → Ext Types → ext 0x02) to a
|
|
8
|
+
# msgpack map with exactly three keys:
|
|
9
|
+
# * "type" — one of "runtime", "argument", "disconnected", "undefined"
|
|
10
|
+
# * "message" — human-readable string
|
|
11
|
+
# * "details" — any wire-legal value, or nil when absent
|
|
12
|
+
#
|
|
13
|
+
# This object holds the *encoded* form. Reifying the corresponding Ruby
|
|
14
|
+
# exception class (RuntimeError, ArgumentError, Kobako::ServiceError, ...)
|
|
15
|
+
# is the responsibility of the dispatch layer, not the codec.
|
|
16
|
+
#
|
|
17
|
+
# Built on +Data.define+ so equality, hash, and immutability are
|
|
18
|
+
# inherited from the value-object machinery; only the field invariants
|
|
19
|
+
# ride on top.
|
|
20
|
+
Exception = Data.define(:type, :message, :details) do
|
|
21
|
+
# +VALID_TYPES+ is attached to the Exception class below this block.
|
|
22
|
+
# Reach it through +self.class::VALID_TYPES+ — Data.define's block
|
|
23
|
+
# scope resolves bare constants against the enclosing +Wire+ module,
|
|
24
|
+
# so a bare +VALID_TYPES+ would raise +NameError+. Same pattern as
|
|
25
|
+
# +Wire::Handle+.
|
|
26
|
+
def initialize(type:, message:, details: nil)
|
|
27
|
+
valid_types = self.class::VALID_TYPES
|
|
28
|
+
raise ArgumentError, "type must be String" unless type.is_a?(String)
|
|
29
|
+
raise ArgumentError, "message must be String" unless message.is_a?(String)
|
|
30
|
+
raise ArgumentError, "type=#{type.inspect} not one of #{valid_types.inspect}" unless valid_types.include?(type)
|
|
31
|
+
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Exception::VALID_TYPES = %w[runtime argument disconnected undefined].freeze
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
module Wire
|
|
5
|
+
# Wire-level value object for an ext-0x01 Capability Handle.
|
|
6
|
+
#
|
|
7
|
+
# SPEC pins the binary layout to fixext 4 with a 4-byte big-endian u32
|
|
8
|
+
# payload (Wire Codec → Ext Types → ext 0x01). ID 0 is reserved as the
|
|
9
|
+
# invalid sentinel; the maximum valid ID is 0x7fff_ffff (2^31 - 1).
|
|
10
|
+
#
|
|
11
|
+
# This is intentionally a thin value object built on +Data.define+ so
|
|
12
|
+
# equality, hash, and immutability are inherited. The runtime-facing
|
|
13
|
+
# +Kobako::Handle+ class lives at a higher layer and may add behaviour
|
|
14
|
+
# (HandleTable bookkeeping, reset semantics). The codec only needs to
|
|
15
|
+
# carry the opaque integer ID across the wire.
|
|
16
|
+
Handle = Data.define(:id) do
|
|
17
|
+
# +MIN_ID+ / +MAX_ID+ live on the Handle class (defined below this
|
|
18
|
+
# block), not in this block's binding — Data.define's block scope
|
|
19
|
+
# resolves bare constants against the enclosing +Wire+ module, so
|
|
20
|
+
# +MIN_ID+ would raise +NameError+. Use +self.class::CONST+ to
|
|
21
|
+
# reach the constants attached to the Handle class itself. Do not
|
|
22
|
+
# "simplify" this back to bare +MIN_ID+/+MAX_ID+.
|
|
23
|
+
def initialize(id:)
|
|
24
|
+
min = self.class::MIN_ID
|
|
25
|
+
max = self.class::MAX_ID
|
|
26
|
+
raise ArgumentError, "Handle id must be Integer" unless id.is_a?(Integer)
|
|
27
|
+
raise ArgumentError, "Handle id #{id} out of range [#{min}, #{max}]" unless id.between?(min, max)
|
|
28
|
+
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Handle::MIN_ID = 1
|
|
34
|
+
Handle::MAX_ID = 0x7fff_ffff
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/kobako/wire.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Host-side namespace for the kobako wire contract (SPEC.md → Wire
|
|
4
|
+
# Contract). The wire is split into two layers, mirrored on the Rust
|
|
5
|
+
# side by the +codec+ / +envelope+ modules in the +kobako-wasm+ crate:
|
|
6
|
+
#
|
|
7
|
+
# - {Codec} — byte-level MessagePack codec (SPEC.md → Wire Codec):
|
|
8
|
+
# {Codec::Encoder}, {Codec::Decoder}, {Codec::Factory}, plus the
|
|
9
|
+
# {Codec::Error} taxonomy. This is the layer that emits and
|
|
10
|
+
# consumes raw bytes; ext types 0x01 (Capability Handle) and
|
|
11
|
+
# 0x02 (Exception envelope) are registered exactly once on the
|
|
12
|
+
# Factory, where the numeric codes live as module-private constants
|
|
13
|
+
# alongside the Rust-side +codec::EXT_HANDLE+ / +codec::EXT_ERRENV+.
|
|
14
|
+
#
|
|
15
|
+
# - {Envelope} — logical message framing (SPEC.md → Wire Contract):
|
|
16
|
+
# {Envelope::Request} / {Envelope::Response} / {Envelope::Result}
|
|
17
|
+
# / {Envelope::Panic} / {Envelope::Outcome} value objects and
|
|
18
|
+
# their encode/decode helpers, built on top of {Codec}.
|
|
19
|
+
#
|
|
20
|
+
# {Handle} and {Exception} are value objects that travel through both
|
|
21
|
+
# layers; they live directly under +Wire+ so neither layer "owns" them.
|
|
22
|
+
#
|
|
23
|
+
# The namespace is intentionally self-contained — it does not depend
|
|
24
|
+
# on the native extension or on +lib/kobako.rb+ — so it can be required
|
|
25
|
+
# directly from tests that run on a clean checkout (no compiled artifacts).
|
|
26
|
+
module Kobako
|
|
27
|
+
# See the file-level documentation above for the layer split. The
|
|
28
|
+
# module body is intentionally empty: the byte-level codec lives in
|
|
29
|
+
# {Wire::Codec}, the logical framing in {Wire::Envelope}, and the
|
|
30
|
+
# shared value objects ({Wire::Handle} / {Wire::Exception}) load
|
|
31
|
+
# themselves into this namespace via the +require_relative+ calls
|
|
32
|
+
# below.
|
|
33
|
+
module Wire
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
require_relative "wire/handle"
|
|
38
|
+
require_relative "wire/exception"
|
|
39
|
+
require_relative "wire/codec"
|
|
40
|
+
require_relative "wire/envelope"
|
data/lib/kobako.rb
ADDED
data/sig/kobako.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kobako
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Aotokitsuruya
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: msgpack
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.7'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.7'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rb_sys
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.9.91
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.9.91
|
|
40
|
+
description: kobako provides an in-process Wasm sandbox (wasmtime + mruby) with a
|
|
41
|
+
MessagePack-based host/guest RPC, allowing Ruby applications to execute untrusted
|
|
42
|
+
mruby scripts under capability-based Service injection.
|
|
43
|
+
email:
|
|
44
|
+
- contact@aotoki.me
|
|
45
|
+
executables: []
|
|
46
|
+
extensions:
|
|
47
|
+
- ext/kobako/extconf.rb
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- Cargo.lock
|
|
51
|
+
- Cargo.toml
|
|
52
|
+
- LICENSE
|
|
53
|
+
- README.md
|
|
54
|
+
- data/kobako.wasm
|
|
55
|
+
- ext/kobako/Cargo.toml
|
|
56
|
+
- ext/kobako/extconf.rb
|
|
57
|
+
- ext/kobako/src/lib.rs
|
|
58
|
+
- ext/kobako/src/wasm.rs
|
|
59
|
+
- ext/kobako/src/wasm/cache.rs
|
|
60
|
+
- ext/kobako/src/wasm/dispatch.rs
|
|
61
|
+
- ext/kobako/src/wasm/host_state.rs
|
|
62
|
+
- ext/kobako/src/wasm/instance.rs
|
|
63
|
+
- lib/kobako.rb
|
|
64
|
+
- lib/kobako/errors.rb
|
|
65
|
+
- lib/kobako/registry.rb
|
|
66
|
+
- lib/kobako/registry/dispatcher.rb
|
|
67
|
+
- lib/kobako/registry/handle_table.rb
|
|
68
|
+
- lib/kobako/registry/service_group.rb
|
|
69
|
+
- lib/kobako/sandbox.rb
|
|
70
|
+
- lib/kobako/sandbox/outcome_decoder.rb
|
|
71
|
+
- lib/kobako/sandbox/output_buffer.rb
|
|
72
|
+
- lib/kobako/version.rb
|
|
73
|
+
- lib/kobako/wasm.rb
|
|
74
|
+
- lib/kobako/wire.rb
|
|
75
|
+
- lib/kobako/wire/codec.rb
|
|
76
|
+
- lib/kobako/wire/codec/decoder.rb
|
|
77
|
+
- lib/kobako/wire/codec/encoder.rb
|
|
78
|
+
- lib/kobako/wire/codec/error.rb
|
|
79
|
+
- lib/kobako/wire/codec/factory.rb
|
|
80
|
+
- lib/kobako/wire/envelope.rb
|
|
81
|
+
- lib/kobako/wire/envelope/payloads.rb
|
|
82
|
+
- lib/kobako/wire/exception.rb
|
|
83
|
+
- lib/kobako/wire/handle.rb
|
|
84
|
+
- sig/kobako.rbs
|
|
85
|
+
homepage: https://github.com/elct9620/kobako
|
|
86
|
+
licenses:
|
|
87
|
+
- Apache-2.0
|
|
88
|
+
metadata:
|
|
89
|
+
allowed_push_host: https://rubygems.org
|
|
90
|
+
homepage_uri: https://github.com/elct9620/kobako
|
|
91
|
+
source_code_uri: https://github.com/elct9620/kobako
|
|
92
|
+
changelog_uri: https://github.com/elct9620/kobako/blob/main/CHANGELOG.md
|
|
93
|
+
bug_tracker_uri: https://github.com/elct9620/kobako/issues
|
|
94
|
+
rubygems_mfa_required: 'true'
|
|
95
|
+
rdoc_options: []
|
|
96
|
+
require_paths:
|
|
97
|
+
- lib
|
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: 3.3.0
|
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: 3.3.11
|
|
108
|
+
requirements: []
|
|
109
|
+
rubygems_version: 3.6.9
|
|
110
|
+
specification_version: 4
|
|
111
|
+
summary: Embeddable Wasm sandbox for running untrusted mruby code from Ruby applications.
|
|
112
|
+
test_files: []
|