kobako 0.14.0 → 0.16.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 +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +67 -0
- data/Cargo.lock +3 -3
- data/README.md +104 -29
- data/ROADMAP.md +2 -3
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +14 -0
- data/crates/kobako-runtime/Cargo.toml +1 -1
- data/crates/kobako-runtime/README.md +1 -1
- data/crates/kobako-runtime/src/snapshot.rs +7 -3
- data/crates/kobako-wasmtime/CHANGELOG.md +14 -0
- data/crates/kobako-wasmtime/Cargo.toml +2 -2
- data/crates/kobako-wasmtime/README.md +1 -1
- data/crates/kobako-wasmtime/src/capture.rs +17 -13
- data/crates/kobako-wasmtime/src/driver.rs +6 -7
- data/crates/kobako-wasmtime/src/frames.rs +2 -9
- data/crates/kobako-wasmtime/src/guest_mem.rs +8 -1
- data/crates/kobako-wasmtime/src/trap.rs +30 -57
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +1 -1
- data/ext/kobako/src/runtime/errors.rs +9 -39
- data/ext/kobako/src/runtime.rs +5 -20
- data/lib/kobako/catalog/extensions.rb +114 -0
- data/lib/kobako/catalog/handles.rb +1 -1
- data/lib/kobako/catalog/services.rb +135 -0
- data/lib/kobako/catalog/snippets.rb +1 -1
- data/lib/kobako/catalog.rb +7 -5
- data/lib/kobako/codec/decoder.rb +3 -3
- data/lib/kobako/codec/encoder.rb +4 -4
- data/lib/kobako/codec/ext_types.rb +169 -0
- data/lib/kobako/codec/state.rb +98 -0
- data/lib/kobako/codec/utils.rb +2 -2
- data/lib/kobako/codec.rb +23 -6
- data/lib/kobako/extension.rb +47 -0
- data/lib/kobako/fault.rb +1 -1
- data/lib/kobako/handle.rb +1 -1
- data/lib/kobako/outcome.rb +13 -7
- data/lib/kobako/pool.rb +1 -1
- data/lib/kobako/sandbox.rb +47 -27
- data/lib/kobako/transport/dispatcher.rb +25 -18
- data/lib/kobako/transport/request.rb +14 -9
- data/lib/kobako/transport/response.rb +9 -2
- data/lib/kobako/transport/yield.rb +4 -1
- data/lib/kobako/transport/yielder.rb +14 -6
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/extensions.rbs +25 -0
- data/sig/kobako/catalog/services.rbs +27 -0
- data/sig/kobako/codec/ext_types.rbs +31 -0
- data/sig/kobako/codec/state.rbs +20 -0
- data/sig/kobako/codec.rbs +3 -0
- data/sig/kobako/extension.rbs +20 -0
- data/sig/kobako/sandbox.rbs +3 -1
- data/sig/kobako/transport/dispatcher.rbs +4 -4
- data/sig/kobako/transport/yielder.rbs +1 -1
- data/sig/kobako/transport.rbs +2 -2
- metadata +11 -7
- data/lib/kobako/catalog/namespaces.rb +0 -115
- data/lib/kobako/codec/factory.rb +0 -187
- data/lib/kobako/namespace.rb +0 -78
- data/sig/kobako/catalog/namespaces.rbs +0 -17
- data/sig/kobako/codec/factory.rbs +0 -34
- data/sig/kobako/namespace.rbs +0 -21
|
@@ -13,7 +13,7 @@ module Kobako
|
|
|
13
13
|
module Transport
|
|
14
14
|
# Pure-function dispatcher for guest-initiated transport calls.
|
|
15
15
|
# Decodes a msgpack-encoded Request envelope, resolves the target
|
|
16
|
-
# object through the Catalog::
|
|
16
|
+
# object through the Catalog::Services (path lookup) or
|
|
17
17
|
# Catalog::Handles (Handle lookup), invokes the method, and returns
|
|
18
18
|
# a msgpack-encoded Response envelope.
|
|
19
19
|
#
|
|
@@ -24,7 +24,7 @@ module Kobako
|
|
|
24
24
|
#
|
|
25
25
|
# Entry point:
|
|
26
26
|
#
|
|
27
|
-
# Kobako::Transport::Dispatcher.dispatch(request_bytes,
|
|
27
|
+
# Kobako::Transport::Dispatcher.dispatch(request_bytes, services, handler, yield_to_guest)
|
|
28
28
|
# # => msgpack-encoded Response bytes (never raises)
|
|
29
29
|
module Dispatcher
|
|
30
30
|
# Throw tag for the Yielder's break unwind back to the
|
|
@@ -67,7 +67,7 @@ module Kobako
|
|
|
67
67
|
|
|
68
68
|
# Dispatch a single transport request and return the encoded
|
|
69
69
|
# Response bytes. Invoked from the +Runtime#on_dispatch+ Proc that
|
|
70
|
-
# +Kobako::Sandbox#initialize+ installs on the ext side; +
|
|
70
|
+
# +Kobako::Sandbox#initialize+ installs on the ext side; +services+,
|
|
71
71
|
# +handler+, and +yield_to_guest+ are captured in that Proc's
|
|
72
72
|
# closure so the Dispatcher stays stateless and the registry doesn't
|
|
73
73
|
# need to publish accessors for the Sandbox-owned +Catalog::Handles+
|
|
@@ -77,24 +77,31 @@ module Kobako
|
|
|
77
77
|
# returns a binary String — every failure path is reified as a
|
|
78
78
|
# Response.error envelope so the guest sees a transport error rather
|
|
79
79
|
# than a wasm trap.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
#
|
|
81
|
+
# The decode runs inside +Codec.track_handles+ so #resolve_call_args
|
|
82
|
+
# can skip the argument walk when no Capability Handle crossed the
|
|
83
|
+
# wire.
|
|
84
|
+
def dispatch(request_bytes, services, handler, yield_to_guest)
|
|
85
|
+
request, carried_handle = Kobako::Codec.track_handles { Kobako::Transport::Request.decode(request_bytes) }
|
|
86
|
+
target = resolve_target(request.target, services, handler)
|
|
87
|
+
args, kwargs = resolve_call_args(request, handler, carried_handle)
|
|
84
88
|
yielder = Yielder.new(yield_to_guest, BREAK_THROW, handler) if request.block_given
|
|
85
|
-
|
|
86
|
-
encode_ok(value, handler)
|
|
89
|
+
encode_ok(catch(BREAK_THROW) { invoke(target, request.method_name, args, kwargs, yielder) }, handler)
|
|
87
90
|
rescue StandardError => e
|
|
88
91
|
encode_caught_error(e)
|
|
89
92
|
ensure
|
|
90
93
|
yielder&.invalidate!
|
|
91
94
|
end
|
|
92
95
|
|
|
93
|
-
# Resolve positional and keyword arguments off +request+ in one
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
|
|
96
|
+
# Resolve positional and keyword arguments off +request+ in one step.
|
|
97
|
+
# +carried_handle+ reports whether the decode carried any Capability
|
|
98
|
+
# Handle; when it did not, every argument resolves to itself, so the
|
|
99
|
+
# decoded values pass straight through and the walk is skipped entirely.
|
|
100
|
+
# Otherwise both go through #resolve_arg so Handles round-trip back to
|
|
101
|
+
# the host-side Ruby object before the call reaches +public_send+.
|
|
102
|
+
def resolve_call_args(request, handler, carried_handle)
|
|
103
|
+
return [request.args, request.kwargs] unless carried_handle
|
|
104
|
+
|
|
98
105
|
[request.args.map { |v| resolve_arg(v, handler) },
|
|
99
106
|
request.kwargs.transform_values { |v| resolve_arg(v, handler) }]
|
|
100
107
|
end
|
|
@@ -187,17 +194,17 @@ module Kobako
|
|
|
187
194
|
# Target type is already validated by +Transport::Request.decode+
|
|
188
195
|
# before this method is reached, so no else-branch is needed here —
|
|
189
196
|
# the wire layer is the system boundary that enforces the invariant.
|
|
190
|
-
def resolve_target(target,
|
|
197
|
+
def resolve_target(target, services, handler)
|
|
191
198
|
case target
|
|
192
199
|
when String
|
|
193
|
-
resolve_path(target,
|
|
200
|
+
resolve_path(target, services)
|
|
194
201
|
when Kobako::Handle
|
|
195
202
|
require_live_object!(target.id, handler)
|
|
196
203
|
end
|
|
197
204
|
end
|
|
198
205
|
|
|
199
|
-
def resolve_path(path,
|
|
200
|
-
|
|
206
|
+
def resolve_path(path, services)
|
|
207
|
+
services.lookup(path)
|
|
201
208
|
rescue KeyError => e
|
|
202
209
|
raise UndefinedTargetError, e.message
|
|
203
210
|
end
|
|
@@ -12,7 +12,7 @@ module Kobako
|
|
|
12
12
|
#
|
|
13
13
|
# 5-element msgpack array:
|
|
14
14
|
# +[target, method_name, args, kwargs, block_given]+. +target+ is
|
|
15
|
-
# either a +String+ (+"
|
|
15
|
+
# either a +String+ (+"MyService::KV"+, e.g. +"MyService::KV"+)
|
|
16
16
|
# or a Handle. SPEC pins +kwargs+ map keys to ext 0x00 Symbol;
|
|
17
17
|
# enforced at construction so the Value Object is the single source of
|
|
18
18
|
# truth. +block_given+ is a Boolean signalling whether the guest call
|
|
@@ -44,16 +44,21 @@ module Kobako
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# Decode +bytes+ into a Request. Raises +Codec::InvalidType+ when the
|
|
47
|
-
# envelope is not the expected 5-element msgpack array,
|
|
48
|
-
#
|
|
47
|
+
# envelope is not the expected 5-element msgpack array, when any
|
|
48
|
+
# position carries an ext 0x02 Fault envelope (a Request is a payload
|
|
49
|
+
# position; the Response fault field is the envelope's only home), or
|
|
50
|
+
# when the Value Object's construction invariants reject the decoded
|
|
51
|
+
# fields.
|
|
49
52
|
def self.decode(bytes)
|
|
50
|
-
Codec
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
Codec.forbid_faults do
|
|
54
|
+
Codec::Decoder.decode(bytes) do |arr|
|
|
55
|
+
unless arr.is_a?(Array) && arr.length == 5
|
|
56
|
+
raise Codec::InvalidType, "Request envelope is malformed (expected a 5-element array)"
|
|
57
|
+
end
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
target, method_name, args, kwargs, block_given = arr
|
|
60
|
+
new(target: target, method_name: method_name, args: args, kwargs: kwargs, block_given: block_given)
|
|
61
|
+
end
|
|
57
62
|
end
|
|
58
63
|
end
|
|
59
64
|
|
|
@@ -53,9 +53,16 @@ module Kobako
|
|
|
53
53
|
def error? = status == STATUS_ERROR
|
|
54
54
|
|
|
55
55
|
# Encode this Response to msgpack bytes as the 2-element
|
|
56
|
-
# +[status, payload]+ array.
|
|
56
|
+
# +[status, payload]+ array. The ok variant's value is a payload
|
|
57
|
+
# position, so a +Kobako::Fault+ inside it has no wire
|
|
58
|
+
# representation and surfaces as +UnsupportedType+ — the
|
|
59
|
+
# Dispatcher's auto-wrap rescue turns it into a Capability Handle.
|
|
60
|
+
# The error variant carries the Fault envelope in its one legal
|
|
61
|
+
# position and encodes it plainly.
|
|
57
62
|
def encode
|
|
58
|
-
Codec::Encoder.encode([status, payload])
|
|
63
|
+
return Codec::Encoder.encode([status, payload]) if error?
|
|
64
|
+
|
|
65
|
+
Codec.forbid_faults { Codec::Encoder.encode([status, payload]) }
|
|
59
66
|
end
|
|
60
67
|
|
|
61
68
|
# Decode +bytes+ into a Response. Raises +Codec::InvalidType+ when the
|
|
@@ -70,7 +70,10 @@ module Kobako
|
|
|
70
70
|
body = bytes.byteslice(1, bytes.bytesize - 1) # : String
|
|
71
71
|
|
|
72
72
|
reject_dead_tag!(tag)
|
|
73
|
-
|
|
73
|
+
# A YieldResponse is a payload position: an ext 0x02 Fault in its
|
|
74
|
+
# value is a wire violation (the Response fault field is the
|
|
75
|
+
# envelope's only home).
|
|
76
|
+
new(tag: tag, value: Codec.forbid_faults { Codec::Decoder.decode(body) })
|
|
74
77
|
end
|
|
75
78
|
|
|
76
79
|
def self.reject_dead_tag!(tag)
|
|
@@ -52,8 +52,14 @@ module Kobako
|
|
|
52
52
|
def yield(*args)
|
|
53
53
|
raise LocalJumpError, "guest block invoked after host dispatch frame returned" unless @active
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
# Yield arguments are a payload position: a +Kobako::Fault+ among
|
|
56
|
+
# them has no wire representation, so the encode refuses it at
|
|
57
|
+
# this call site. The tracking bracket below opens only around the
|
|
58
|
+
# decode: the guest re-entry may run nested dispatches whose own
|
|
59
|
+
# brackets would otherwise pollute the signal.
|
|
60
|
+
bytes = @yield_to_guest.call(Kobako::Codec.forbid_faults { Kobako::Codec::Encoder.encode(args) })
|
|
61
|
+
response, carried_handle = Kobako::Codec.track_handles { Kobako::Transport::Yield.decode(bytes) }
|
|
62
|
+
return restore(response.value, carried_handle) if response.ok?
|
|
57
63
|
|
|
58
64
|
throw @break_tag, response.value if response.break?
|
|
59
65
|
|
|
@@ -78,10 +84,12 @@ module Kobako
|
|
|
78
84
|
# Restore any Capability Handle in a block's ok value to its host
|
|
79
85
|
# object via the injected +Catalog::Handles+. Only the
|
|
80
86
|
# ok path calls this — host code consumes the ok value, whereas a
|
|
81
|
-
# break value returns to the guest and stays a Handle.
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
def restore(value)
|
|
87
|
+
# break value returns to the guest and stays a Handle. A response
|
|
88
|
+
# whose decode carried no Handle resolves to itself, so the walk is
|
|
89
|
+
# skipped entirely.
|
|
90
|
+
def restore(value, carried_handle)
|
|
91
|
+
return value unless carried_handle
|
|
92
|
+
|
|
85
93
|
Kobako::Codec::HandleWalk.deep_restore(value, @handler)
|
|
86
94
|
end
|
|
87
95
|
|
data/lib/kobako/version.rb
CHANGED
data/lib/kobako.rb
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Kobako
|
|
2
|
+
module Catalog
|
|
3
|
+
class Extensions
|
|
4
|
+
def initialize: () -> void
|
|
5
|
+
|
|
6
|
+
def install: (untyped extension, snippets: Kobako::Catalog::Snippets, services: Kobako::Catalog::Services) -> self
|
|
7
|
+
|
|
8
|
+
def seal!: () -> self
|
|
9
|
+
|
|
10
|
+
def refresh_backends!: (Kobako::Catalog::Services services) -> self
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def refresh_backend: (untyped extension, Kobako::Catalog::Services services, Hash[untyped, untyped] resolved) -> void
|
|
15
|
+
|
|
16
|
+
def validate!: (untyped extension) -> void
|
|
17
|
+
|
|
18
|
+
def assert_dependencies!: () -> void
|
|
19
|
+
|
|
20
|
+
def callable?: (untyped provider) -> bool
|
|
21
|
+
|
|
22
|
+
def initial_object: (untyped provider) -> untyped
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Kobako
|
|
2
|
+
module Catalog
|
|
3
|
+
class Services
|
|
4
|
+
NAME_PATTERN: Regexp
|
|
5
|
+
|
|
6
|
+
def initialize: (?handler: Kobako::Catalog::Handles) -> void
|
|
7
|
+
|
|
8
|
+
def bind: (Symbol | String path, untyped object) -> self
|
|
9
|
+
|
|
10
|
+
def lookup: (Symbol | String target) -> untyped
|
|
11
|
+
|
|
12
|
+
def refresh: (Symbol | String path, untyped object) -> self
|
|
13
|
+
|
|
14
|
+
def encode: () -> String
|
|
15
|
+
|
|
16
|
+
def seal!: () -> self
|
|
17
|
+
|
|
18
|
+
def sealed?: () -> bool
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def validate_path!: (Symbol | String path) -> String
|
|
23
|
+
|
|
24
|
+
def collision?: (String path) -> bool
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Kobako
|
|
2
|
+
module Codec
|
|
3
|
+
module ExtTypes
|
|
4
|
+
EXT_SYMBOL: Integer
|
|
5
|
+
EXT_HANDLE: Integer
|
|
6
|
+
EXT_ERRENV: Integer
|
|
7
|
+
|
|
8
|
+
def self?.build_factory: () -> MessagePack::Factory
|
|
9
|
+
|
|
10
|
+
def self?.pack_symbol: (Symbol symbol) -> String
|
|
11
|
+
|
|
12
|
+
def self?.unpack_symbol: (String payload) -> Symbol
|
|
13
|
+
|
|
14
|
+
def self?.pack_handle: (Kobako::Handle handle) -> String
|
|
15
|
+
|
|
16
|
+
def self?.unpack_handle: (String payload, State state) -> Kobako::Handle
|
|
17
|
+
|
|
18
|
+
def self?.pack_fault: (Kobako::Fault fault, State state) -> String
|
|
19
|
+
|
|
20
|
+
def self?.unpack_fault: (String payload, State state) -> Kobako::Fault
|
|
21
|
+
|
|
22
|
+
def self?.register_symbol: (MessagePack::Factory factory) -> void
|
|
23
|
+
|
|
24
|
+
def self?.register_handle: (MessagePack::Factory factory) -> void
|
|
25
|
+
|
|
26
|
+
def self?.register_fault: (MessagePack::Factory factory) -> void
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
FACTORY: MessagePack::Factory
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Kobako
|
|
2
|
+
module Codec
|
|
3
|
+
class State
|
|
4
|
+
MAX_EXT_DEPTH: Integer
|
|
5
|
+
STATE_KEY: Symbol
|
|
6
|
+
|
|
7
|
+
def self.current: () -> State
|
|
8
|
+
|
|
9
|
+
def track_handles: [T] { () -> T } -> [T, bool]
|
|
10
|
+
def record_handle!: () -> void
|
|
11
|
+
def forbid_faults: [T] { () -> T } -> T
|
|
12
|
+
def faults_forbidden?: () -> bool
|
|
13
|
+
def within_ext_frame: [T] (singleton(Kobako::Codec::Error) over_limit) { () -> T } -> T
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def initialize: () -> void
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/sig/kobako/codec.rbs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
module Kobako
|
|
2
2
|
module Codec
|
|
3
|
+
def self.track_handles: [T] { () -> T } -> [T, bool]
|
|
4
|
+
def self.forbid_faults: [T] { () -> T } -> T
|
|
5
|
+
|
|
3
6
|
# The Handle allocator/resolver a wire walk works against: alloc
|
|
4
7
|
# registers a non-wire-representable object and returns its Handle,
|
|
5
8
|
# fetch resolves a wire Handle id back to the live object. The
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Kobako
|
|
2
|
+
class Extension < Data
|
|
3
|
+
attr_reader name: Symbol
|
|
4
|
+
attr_reader source: String
|
|
5
|
+
attr_reader backend: Kobako::Extension::Backend?
|
|
6
|
+
attr_reader depends_on: Array[Symbol]
|
|
7
|
+
|
|
8
|
+
def self.new: (name: Symbol, source: String, ?backend: Kobako::Extension::Backend?, ?depends_on: Array[Symbol]) -> Kobako::Extension
|
|
9
|
+
|
|
10
|
+
def initialize: (name: Symbol, source: String, ?backend: Kobako::Extension::Backend?, ?depends_on: Array[Symbol]) -> void
|
|
11
|
+
|
|
12
|
+
class Backend < Data
|
|
13
|
+
attr_reader path: String
|
|
14
|
+
attr_reader provider: untyped
|
|
15
|
+
|
|
16
|
+
def self.new: (path: String, provider: untyped) -> Kobako::Extension::Backend
|
|
17
|
+
| (String path, untyped provider) -> Kobako::Extension::Backend
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/sig/kobako/sandbox.rbs
CHANGED
|
@@ -29,7 +29,9 @@ module Kobako
|
|
|
29
29
|
|
|
30
30
|
attr_reader usage: Kobako::Usage
|
|
31
31
|
|
|
32
|
-
def
|
|
32
|
+
def bind: (Symbol | String path, untyped object) -> Kobako::Sandbox
|
|
33
|
+
|
|
34
|
+
def install: (*untyped extensions) -> Kobako::Sandbox
|
|
33
35
|
|
|
34
36
|
def preload: (code: String, name: Symbol | String) -> Kobako::Sandbox
|
|
35
37
|
| (binary: String) -> Kobako::Sandbox
|
|
@@ -12,9 +12,9 @@ module Kobako
|
|
|
12
12
|
|
|
13
13
|
CALLABLE_ALLOW: Array[Symbol]
|
|
14
14
|
|
|
15
|
-
def self?.dispatch: (String request_bytes, Kobako::Transport::
|
|
15
|
+
def self?.dispatch: (String request_bytes, Kobako::Transport::_ServiceRegistry services, Kobako::Codec::_HandleTable handler, Kobako::Transport::_GuestYielder yield_to_guest) -> String
|
|
16
16
|
|
|
17
|
-
def self?.resolve_call_args: (Kobako::Transport::Request request, Kobako::Codec::_HandleTable handler) -> [Array[untyped], Hash[Symbol, untyped]]
|
|
17
|
+
def self?.resolve_call_args: (Kobako::Transport::Request request, Kobako::Codec::_HandleTable handler, bool carried_handle) -> [Array[untyped], Hash[Symbol, untyped]]
|
|
18
18
|
|
|
19
19
|
def self?.encode_caught_error: (StandardError error) -> String
|
|
20
20
|
|
|
@@ -26,9 +26,9 @@ module Kobako
|
|
|
26
26
|
|
|
27
27
|
def self?.resolve_arg: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
|
|
28
28
|
|
|
29
|
-
def self?.resolve_target: (String | Kobako::Handle target, Kobako::Transport::
|
|
29
|
+
def self?.resolve_target: (String | Kobako::Handle target, Kobako::Transport::_ServiceRegistry services, Kobako::Codec::_HandleTable handler) -> untyped
|
|
30
30
|
|
|
31
|
-
def self?.resolve_path: (String path, Kobako::Transport::
|
|
31
|
+
def self?.resolve_path: (String path, Kobako::Transport::_ServiceRegistry services) -> untyped
|
|
32
32
|
|
|
33
33
|
def self?.require_live_object!: (Integer id, Kobako::Codec::_HandleTable handler) -> untyped
|
|
34
34
|
|
data/sig/kobako/transport.rbs
CHANGED
|
@@ -10,10 +10,10 @@ module Kobako
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
# The Service registry a dispatch resolves constant-path targets
|
|
13
|
-
# against. The Sandbox's Kobako::Catalog::
|
|
13
|
+
# against. The Sandbox's Kobako::Catalog::Services is the
|
|
14
14
|
# production conformer; modelled structurally so the Transport
|
|
15
15
|
# layer needs no upward dependency on Catalog.
|
|
16
|
-
interface
|
|
16
|
+
interface _ServiceRegistry
|
|
17
17
|
def lookup: (String target) -> untyped
|
|
18
18
|
end
|
|
19
19
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kobako
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aotokitsuruya
|
|
@@ -91,20 +91,22 @@ files:
|
|
|
91
91
|
- lib/kobako.rb
|
|
92
92
|
- lib/kobako/capture.rb
|
|
93
93
|
- lib/kobako/catalog.rb
|
|
94
|
+
- lib/kobako/catalog/extensions.rb
|
|
94
95
|
- lib/kobako/catalog/handles.rb
|
|
95
|
-
- lib/kobako/catalog/
|
|
96
|
+
- lib/kobako/catalog/services.rb
|
|
96
97
|
- lib/kobako/catalog/snippets.rb
|
|
97
98
|
- lib/kobako/codec.rb
|
|
98
99
|
- lib/kobako/codec/decoder.rb
|
|
99
100
|
- lib/kobako/codec/encoder.rb
|
|
100
101
|
- lib/kobako/codec/error.rb
|
|
101
|
-
- lib/kobako/codec/
|
|
102
|
+
- lib/kobako/codec/ext_types.rb
|
|
102
103
|
- lib/kobako/codec/handle_walk.rb
|
|
104
|
+
- lib/kobako/codec/state.rb
|
|
103
105
|
- lib/kobako/codec/utils.rb
|
|
104
106
|
- lib/kobako/errors.rb
|
|
107
|
+
- lib/kobako/extension.rb
|
|
105
108
|
- lib/kobako/fault.rb
|
|
106
109
|
- lib/kobako/handle.rb
|
|
107
|
-
- lib/kobako/namespace.rb
|
|
108
110
|
- lib/kobako/outcome.rb
|
|
109
111
|
- lib/kobako/outcome/panic.rb
|
|
110
112
|
- lib/kobako/pool.rb
|
|
@@ -129,20 +131,22 @@ files:
|
|
|
129
131
|
- sig/kobako.rbs
|
|
130
132
|
- sig/kobako/capture.rbs
|
|
131
133
|
- sig/kobako/catalog.rbs
|
|
134
|
+
- sig/kobako/catalog/extensions.rbs
|
|
132
135
|
- sig/kobako/catalog/handles.rbs
|
|
133
|
-
- sig/kobako/catalog/
|
|
136
|
+
- sig/kobako/catalog/services.rbs
|
|
134
137
|
- sig/kobako/catalog/snippets.rbs
|
|
135
138
|
- sig/kobako/codec.rbs
|
|
136
139
|
- sig/kobako/codec/decoder.rbs
|
|
137
140
|
- sig/kobako/codec/encoder.rbs
|
|
138
141
|
- sig/kobako/codec/error.rbs
|
|
139
|
-
- sig/kobako/codec/
|
|
142
|
+
- sig/kobako/codec/ext_types.rbs
|
|
140
143
|
- sig/kobako/codec/handle_walk.rbs
|
|
144
|
+
- sig/kobako/codec/state.rbs
|
|
141
145
|
- sig/kobako/codec/utils.rbs
|
|
142
146
|
- sig/kobako/errors.rbs
|
|
147
|
+
- sig/kobako/extension.rbs
|
|
143
148
|
- sig/kobako/fault.rbs
|
|
144
149
|
- sig/kobako/handle.rbs
|
|
145
|
-
- sig/kobako/namespace.rbs
|
|
146
150
|
- sig/kobako/outcome.rbs
|
|
147
151
|
- sig/kobako/outcome/panic.rbs
|
|
148
152
|
- sig/kobako/pool.rbs
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "handles"
|
|
4
|
-
require_relative "../codec"
|
|
5
|
-
require_relative "../errors"
|
|
6
|
-
require_relative "../namespace"
|
|
7
|
-
|
|
8
|
-
module Kobako
|
|
9
|
-
module Catalog
|
|
10
|
-
# Kobako::Catalog::Namespaces — per-Sandbox registry of
|
|
11
|
-
# +Kobako::Namespace+ entities. Holds the Namespace / Member bindings
|
|
12
|
-
# and the preamble emitted on Frame 1.
|
|
13
|
-
#
|
|
14
|
-
# Public API:
|
|
15
|
-
#
|
|
16
|
-
# namespaces = Kobako::Catalog::Namespaces.new
|
|
17
|
-
# namespace = namespaces.define(:MyService) # => Kobako::Namespace
|
|
18
|
-
# namespace.bind(:KV, kv_object) # => namespace (chainable)
|
|
19
|
-
# namespaces.encode # => msgpack bytes for Frame 1
|
|
20
|
-
# namespaces.lookup("MyService::KV") # => kv_object
|
|
21
|
-
#
|
|
22
|
-
# Namespaces live at +Kobako::Namespace+. Per-dispatch routing is
|
|
23
|
-
# +Kobako::Transport::Dispatcher+'s responsibility — the Dispatcher
|
|
24
|
-
# receives this registry and the +Catalog::Handles+ as arguments from
|
|
25
|
-
# the +Runtime#on_dispatch+ Proc that +Kobako::Sandbox#initialize+
|
|
26
|
-
# installs. The registry holds an injected +Catalog::Handles+ reference so
|
|
27
|
-
# dispatch target resolution and host→guest auto-wrap share the same
|
|
28
|
-
# Sandbox-owned allocator.
|
|
29
|
-
class Namespaces
|
|
30
|
-
# Build a fresh registry. +handler+ is an internal seam that injects
|
|
31
|
-
# a pre-configured +Catalog::Handles+; tests pass one whose +next_id+
|
|
32
|
-
# is pinned near +MAX_ID+ to exercise the cap-exhaustion path
|
|
33
|
-
# without 2³¹ allocations. Production callers leave it at the default.
|
|
34
|
-
def initialize(handler: Catalog::Handles.new)
|
|
35
|
-
@namespaces = {} # : Hash[String, Kobako::Namespace]
|
|
36
|
-
@handler = handler
|
|
37
|
-
@sealed = false
|
|
38
|
-
@encoded = nil # : String?
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Declare or retrieve the Namespace named +name+ (idempotent).
|
|
42
|
-
# +name+ is a constant-form name as a +Symbol+ or +String+ (must satisfy
|
|
43
|
-
# +Namespace::NAME_PATTERN+). Returns the +Kobako::Namespace+ for that
|
|
44
|
-
# name, creating it if it does not exist. Raises +ArgumentError+ when
|
|
45
|
-
# +name+ is malformed, or when called after the owning Sandbox has been
|
|
46
|
-
# sealed by its first invocation.
|
|
47
|
-
def define(name)
|
|
48
|
-
raise ArgumentError, "cannot define after first Sandbox invocation" if @sealed
|
|
49
|
-
|
|
50
|
-
name_str = name.to_s
|
|
51
|
-
unless Namespace::NAME_PATTERN.match?(name_str)
|
|
52
|
-
raise ArgumentError,
|
|
53
|
-
"Namespace name must match #{Namespace::NAME_PATTERN.inspect} (got #{name.inspect})"
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
@namespaces[name_str] ||= Namespace.new(name_str)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Resolve a +target+ path of the form +"<Namespace>::<Member>"+
|
|
60
|
-
# (e.g. +"MyService::KV"+) to the bound Host object. +target+ is a
|
|
61
|
-
# two-level path using the +::+ separator. Returns the bound Host
|
|
62
|
-
# object. Raises +KeyError+ when the namespace or the member is not
|
|
63
|
-
# bound.
|
|
64
|
-
def lookup(target)
|
|
65
|
-
namespace_name, member_name = target.to_s.split("::", 2)
|
|
66
|
-
namespace = @namespaces[namespace_name]
|
|
67
|
-
raise KeyError, "no namespace named #{namespace_name.inspect}" if namespace.nil?
|
|
68
|
-
raise KeyError, "no member in target #{target.inspect}" unless member_name
|
|
69
|
-
|
|
70
|
-
namespace.fetch(member_name)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Encode the preamble as msgpack bytes for stdin Frame 1 delivery.
|
|
74
|
-
# Routes through Kobako::Codec::Encoder like every other host-side
|
|
75
|
-
# wire encode so there is a single codec path; the preamble carries
|
|
76
|
-
# only Strings and Arrays, so none of the kobako ext types actually
|
|
77
|
-
# fire. Structure: +[["Namespace", ["MemberA", "MemberB"]], ...]+.
|
|
78
|
-
# Returns a binary +String+ of msgpack bytes.
|
|
79
|
-
#
|
|
80
|
-
# Once sealed, the bytes are computed once and reused for every
|
|
81
|
-
# subsequent invocation: sealing freezes Service registration at the
|
|
82
|
-
# first invocation, so the preamble is exactly the bindings that
|
|
83
|
-
# existed at that moment — a bind reaching a +Kobako::Namespace+
|
|
84
|
-
# after the seal raises +ArgumentError+ and never alters Frame 1.
|
|
85
|
-
# The memo is gated on the seal rather than dropped per mutation (the
|
|
86
|
-
# +Catalog::Snippets#encode+ approach) because a +Member+ bind lands
|
|
87
|
-
# on a child +Kobako::Namespace+, invisible to this collection; only
|
|
88
|
-
# the seal guarantees nothing further can change.
|
|
89
|
-
def encode
|
|
90
|
-
return @encoded if @encoded
|
|
91
|
-
|
|
92
|
-
bytes = Codec::Encoder.encode(@namespaces.values.map(&:to_preamble)).freeze
|
|
93
|
-
@encoded = bytes if @sealed
|
|
94
|
-
bytes
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Mark the registry as sealed and propagate the seal to every
|
|
98
|
-
# declared +Kobako::Namespace+. Called by +Sandbox+ on the first
|
|
99
|
-
# invocation. After sealing, both #define and +Namespace#bind+
|
|
100
|
-
# raise ArgumentError. Idempotent.
|
|
101
|
-
def seal!
|
|
102
|
-
return self if @sealed
|
|
103
|
-
|
|
104
|
-
@sealed = true
|
|
105
|
-
@namespaces.each_value(&:seal!)
|
|
106
|
-
self
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Returns +true+ when #seal! has been called, +false+ otherwise.
|
|
110
|
-
def sealed?
|
|
111
|
-
@sealed
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|