kobako 0.13.0 → 0.15.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +81 -0
  4. data/Cargo.lock +3 -3
  5. data/README.md +107 -32
  6. data/ROADMAP.md +25 -0
  7. data/SECURITY.md +1 -1
  8. data/crates/kobako-runtime/CHANGELOG.md +14 -0
  9. data/crates/kobako-runtime/Cargo.toml +1 -1
  10. data/crates/kobako-runtime/README.md +1 -1
  11. data/crates/kobako-runtime/src/error.rs +8 -0
  12. data/crates/kobako-runtime/src/snapshot.rs +7 -3
  13. data/crates/kobako-wasmtime/CHANGELOG.md +14 -0
  14. data/crates/kobako-wasmtime/Cargo.toml +2 -2
  15. data/crates/kobako-wasmtime/README.md +1 -1
  16. data/crates/kobako-wasmtime/src/capture.rs +17 -13
  17. data/crates/kobako-wasmtime/src/driver.rs +7 -8
  18. data/crates/kobako-wasmtime/src/frames.rs +3 -10
  19. data/crates/kobako-wasmtime/src/guest_mem.rs +8 -1
  20. data/crates/kobako-wasmtime/src/instance_pre.rs +7 -6
  21. data/crates/kobako-wasmtime/src/invocation.rs +4 -5
  22. data/crates/kobako-wasmtime/src/trap.rs +65 -71
  23. data/data/kobako.wasm +0 -0
  24. data/ext/kobako/Cargo.toml +1 -1
  25. data/ext/kobako/src/runtime/errors.rs +9 -39
  26. data/ext/kobako/src/runtime.rs +5 -20
  27. data/lib/kobako/catalog/handles.rb +3 -3
  28. data/lib/kobako/catalog/services.rb +123 -0
  29. data/lib/kobako/catalog/snippets.rb +2 -2
  30. data/lib/kobako/catalog.rb +2 -2
  31. data/lib/kobako/codec/decoder.rb +10 -10
  32. data/lib/kobako/codec/encoder.rb +5 -5
  33. data/lib/kobako/codec/error.rb +3 -3
  34. data/lib/kobako/codec/ext_types.rb +169 -0
  35. data/lib/kobako/codec/handle_walk.rb +17 -21
  36. data/lib/kobako/codec/state.rb +98 -0
  37. data/lib/kobako/codec/utils.rb +9 -9
  38. data/lib/kobako/codec.rb +23 -6
  39. data/lib/kobako/errors.rb +14 -39
  40. data/lib/kobako/fault.rb +1 -1
  41. data/lib/kobako/handle.rb +6 -3
  42. data/lib/kobako/outcome.rb +14 -7
  43. data/lib/kobako/pool.rb +1 -1
  44. data/lib/kobako/sandbox.rb +30 -25
  45. data/lib/kobako/transport/dispatcher.rb +41 -31
  46. data/lib/kobako/transport/request.rb +16 -11
  47. data/lib/kobako/transport/response.rb +11 -4
  48. data/lib/kobako/transport/run.rb +1 -1
  49. data/lib/kobako/transport/yield.rb +7 -4
  50. data/lib/kobako/transport/yielder.rb +20 -12
  51. data/lib/kobako/version.rb +1 -1
  52. data/release-please-config.json +37 -7
  53. data/sig/kobako/catalog/services.rbs +25 -0
  54. data/sig/kobako/codec/ext_types.rbs +31 -0
  55. data/sig/kobako/codec/handle_walk.rbs +2 -2
  56. data/sig/kobako/codec/state.rbs +20 -0
  57. data/sig/kobako/codec.rbs +14 -0
  58. data/sig/kobako/handle.rbs +0 -2
  59. data/sig/kobako/sandbox.rbs +1 -1
  60. data/sig/kobako/transport/dispatcher.rbs +8 -8
  61. data/sig/kobako/transport/run.rbs +1 -1
  62. data/sig/kobako/transport/yielder.rbs +3 -3
  63. data/sig/kobako/transport.rbs +8 -0
  64. metadata +8 -7
  65. data/lib/kobako/catalog/namespaces.rb +0 -115
  66. data/lib/kobako/codec/factory.rb +0 -187
  67. data/lib/kobako/namespace.rb +0 -78
  68. data/sig/kobako/catalog/namespaces.rbs +0 -17
  69. data/sig/kobako/codec/factory.rbs +0 -34
  70. data/sig/kobako/namespace.rbs +0 -21
@@ -51,7 +51,7 @@ module Kobako
51
51
  # Encode this Run to the msgpack bytes the guest's +__kobako_run+
52
52
  # entry point consumes as its command-buffer payload
53
53
  # ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
54
- # Walks +args+ / +kwargs+ through {Codec::HandleWalk.deep_wrap} so
54
+ # Walks +args+ / +kwargs+ through Codec::HandleWalk.deep_wrap so
55
55
  # any non-wire-representable leaf is allocated into +handler+ and
56
56
  # replaced with a +Kobako::Handle+; the
57
57
  # +handler+ argument is the Sandbox's table, sharing the same
@@ -10,9 +10,9 @@ module Kobako
10
10
  # First byte of the YieldResponse for the success branch — body is
11
11
  # the block's return value encoded as a single msgpack value.
12
12
  TAG_OK = 0x01
13
- # First byte for `break val` — body is the break value.
13
+ # First byte for +break val+ — body is the break value.
14
14
  TAG_BREAK = 0x02
15
- # Reserved for future `return val` support; both sides reject this
15
+ # Reserved for future +return val+ support; both sides reject this
16
16
  # tag as a wire violation (YieldResponse envelope contract).
17
17
  TAG_RESERVED = 0x03
18
18
  # First byte for an error / fault outcome — body is a
@@ -58,7 +58,7 @@ module Kobako
58
58
  [tag].pack("C") + Codec::Encoder.encode(value)
59
59
  end
60
60
 
61
- # Decode +bytes+ into a {Yield}. Rejects empty input, the reserved
61
+ # Decode +bytes+ into a Yield. Rejects empty input, the reserved
62
62
  # tag 0x03, and any tag outside +LIVE_TAGS+ by raising
63
63
  # +Kobako::Codec::InvalidType+ — these are wire violations per the
64
64
  # SPEC's YieldResponse envelope contract.
@@ -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
- new(tag: tag, value: Codec::Decoder.decode(body))
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)
@@ -13,7 +13,7 @@ module Kobako
13
13
  # Each guest call that carries +block_given: true+ gets a Yielder
14
14
  # that the Dispatcher hands to the Service method as +&block+. The
15
15
  # Service method observes it as an ordinary Ruby Proc through
16
- # {#to_proc}; +yield val+ / +block.call(val)+ invokes {#yield}, which
16
+ # #to_proc; +yield val+ / +block.call(val)+ invokes #yield, which
17
17
  # serialises the positional args, re-enters the guest via the injected
18
18
  # +yield_to_guest+ lambda, and reifies the +YieldResponse+ into Ruby
19
19
  # control flow:
@@ -24,13 +24,13 @@ module Kobako
24
24
  # * +tag 0x04+ error — raise the +{class, message}+ payload at the
25
25
  # Service's yield site
26
26
  #
27
- # The Dispatcher calls {#invalidate!} from its +ensure+ block once
27
+ # The Dispatcher calls #invalidate! from its +ensure+ block once
28
28
  # dispatch completes; any later call to a stashed Yielder then raises
29
29
  # +LocalJumpError+ — the observable shape of an escaped Yielder.
30
30
  class Yielder
31
31
  # +yield_to_guest+ is a +String → String+ callable (the ext's
32
32
  # per-dispatch +Kobako::Runtime::GuestYielder+) that
33
- # {#yield} invokes to re-enter the guest; +break_tag+ is the +catch+
33
+ # #yield invokes to re-enter the guest; +break_tag+ is the +catch+
34
34
  # throw tag the Dispatcher matches against to unwind the Service on
35
35
  # +tag 0x02+. +handler+ is the Sandbox's +Kobako::Catalog::Handles+,
36
36
  # used to restore a Capability Handle in the block's ok value back to
@@ -44,7 +44,7 @@ module Kobako
44
44
 
45
45
  # Re-enter the guest with +args+ and reify the YieldResponse into
46
46
  # Ruby control flow. Raises +LocalJumpError+ if called after
47
- # {#invalidate!}. The ok value is consumed by the host Service
47
+ # #invalidate!. The ok value is consumed by the host Service
48
48
  # method, so a Capability Handle in it is restored to its host object.
49
49
  # The break value unwinds past the Service back to the guest
50
50
  # Member call, so it passes through verbatim — a Handle stays a
@@ -52,22 +52,28 @@ module Kobako
52
52
  def yield(*args)
53
53
  raise LocalJumpError, "guest block invoked after host dispatch frame returned" unless @active
54
54
 
55
- response = Kobako::Transport::Yield.decode(@yield_to_guest.call(Kobako::Codec::Encoder.encode(args)))
56
- return restore(response.value) if response.ok?
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
 
60
66
  raise yield_failure(response.value, default: "yield error")
61
67
  end
62
68
 
63
- # The Proc the Dispatcher passes as +&block+, binding {#yield} so a
69
+ # The Proc the Dispatcher passes as +&block+, binding #yield so a
64
70
  # Service method's +yield+ / +block.call+ drives the round-trip.
65
71
  def to_proc
66
72
  method(:yield).to_proc
67
73
  end
68
74
 
69
75
  # Mark this Yielder dead. Called by the Dispatcher's +ensure+ block
70
- # when the originating dispatch frame returns; any later {#yield}
76
+ # when the originating dispatch frame returns; any later #yield
71
77
  # call then raises +LocalJumpError+.
72
78
  def invalidate!
73
79
  @active = false
@@ -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. Walks nested
82
- # Array / Hash one level at a time; a plain value passes through
83
- # unchanged.
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.13.0"
4
+ VERSION = "0.15.0"
5
5
  end
@@ -29,28 +29,28 @@
29
29
  }
30
30
  ]
31
31
  },
32
- "wasm/kobako": {
33
- "component": "kobako-rs",
32
+ "wasm/kobako-mruby": {
33
+ "component": "kobako-mruby",
34
34
  "release-type": "rust",
35
35
  "extra-files": [
36
36
  {
37
37
  "type": "toml",
38
38
  "path": "/wasm/Cargo.lock",
39
- "jsonpath": "$.package[?(@.name=='kobako')].version"
39
+ "jsonpath": "$.package[?(@.name=='kobako-mruby')].version"
40
40
  },
41
41
  {
42
42
  "type": "toml",
43
- "path": "/wasm/kobako/Cargo.toml",
43
+ "path": "/wasm/kobako-mruby/Cargo.toml",
44
44
  "jsonpath": "$.dependencies['kobako-core'].version"
45
45
  },
46
46
  {
47
47
  "type": "toml",
48
- "path": "/wasm/kobako/Cargo.toml",
48
+ "path": "/wasm/kobako-mruby/Cargo.toml",
49
49
  "jsonpath": "$.dependencies['kobako-codec'].version"
50
50
  },
51
51
  {
52
52
  "type": "generic",
53
- "path": "/wasm/kobako/README.md"
53
+ "path": "/wasm/kobako-mruby/README.md"
54
54
  }
55
55
  ]
56
56
  },
@@ -178,13 +178,43 @@
178
178
  "path": "/crates/kobako-wasmtime/README.md"
179
179
  }
180
180
  ]
181
+ },
182
+ "crates/kobako": {
183
+ "component": "kobako-sdk",
184
+ "release-type": "rust",
185
+ "extra-files": [
186
+ {
187
+ "type": "toml",
188
+ "path": "/crates/Cargo.lock",
189
+ "jsonpath": "$.package[?(@.name=='kobako')].version"
190
+ },
191
+ {
192
+ "type": "toml",
193
+ "path": "/crates/kobako/Cargo.toml",
194
+ "jsonpath": "$.dependencies['kobako-codec'].version"
195
+ },
196
+ {
197
+ "type": "toml",
198
+ "path": "/crates/kobako/Cargo.toml",
199
+ "jsonpath": "$.dependencies['kobako-runtime'].version"
200
+ },
201
+ {
202
+ "type": "toml",
203
+ "path": "/crates/kobako/Cargo.toml",
204
+ "jsonpath": "$.dependencies['kobako-wasmtime'].version"
205
+ },
206
+ {
207
+ "type": "generic",
208
+ "path": "/crates/kobako/README.md"
209
+ }
210
+ ]
181
211
  }
182
212
  },
183
213
  "plugins": [
184
214
  {
185
215
  "type": "linked-versions",
186
216
  "groupName": "kobako crates",
187
- "components": ["kobako-codec", "kobako-core", "kobako-rs", "kobako-io", "kobako-json", "kobako-regexp", "kobako-baker", "kobako-runtime", "kobako-wasmtime"]
217
+ "components": ["kobako-codec", "kobako-core", "kobako-mruby", "kobako-io", "kobako-json", "kobako-regexp", "kobako-baker", "kobako-runtime", "kobako-wasmtime", "kobako-sdk"]
188
218
  }
189
219
  ],
190
220
  "extra-files": [
@@ -0,0 +1,25 @@
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 encode: () -> String
13
+
14
+ def seal!: () -> self
15
+
16
+ def sealed?: () -> bool
17
+
18
+ private
19
+
20
+ def validate_path!: (Symbol | String path) -> String
21
+
22
+ def collision?: (String path) -> bool
23
+ end
24
+ end
25
+ 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
@@ -5,9 +5,9 @@ module Kobako
5
5
 
6
6
  def self?.representable?: (untyped value) -> bool
7
7
 
8
- def self?.deep_wrap: (untyped value, Kobako::Catalog::Handles handler) -> untyped
8
+ def self?.deep_wrap: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
9
9
 
10
- def self?.deep_restore: (untyped value, Kobako::Catalog::Handles handler) -> untyped
10
+ def self?.deep_restore: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
11
11
 
12
12
  def self?.primitive_type?: (untyped value) -> bool
13
13
 
@@ -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,4 +1,18 @@
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
+
6
+ # The Handle allocator/resolver a wire walk works against: alloc
7
+ # registers a non-wire-representable object and returns its Handle,
8
+ # fetch resolves a wire Handle id back to the live object. The
9
+ # Sandbox's Kobako::Catalog::Handles is the production conformer;
10
+ # modelled structurally so the Codec and Transport tiers need no
11
+ # upward dependency on Catalog.
12
+ interface _HandleTable
13
+ def alloc: (untyped object) -> Kobako::Handle
14
+
15
+ def fetch: (Integer id) -> untyped
16
+ end
3
17
  end
4
18
  end
@@ -9,8 +9,6 @@ module Kobako
9
9
 
10
10
  def self.restore: (Integer id) -> Handle
11
11
 
12
- def with: (?id: Integer) -> Handle
13
-
14
12
  def ==: (untyped other) -> bool
15
13
 
16
14
  def hash: () -> Integer
@@ -29,7 +29,7 @@ module Kobako
29
29
 
30
30
  attr_reader usage: Kobako::Usage
31
31
 
32
- def define: (Symbol | String name) -> Kobako::Namespace
32
+ def bind: (Symbol | String path, untyped object) -> Kobako::Sandbox
33
33
 
34
34
  def preload: (code: String, name: Symbol | String) -> Kobako::Sandbox
35
35
  | (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::Catalog::Namespaces namespaces, Kobako::Catalog::Handles handler, Kobako::Transport::_GuestYielder yield_to_guest) -> String
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::Catalog::Handles 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
 
@@ -24,17 +24,17 @@ module Kobako
24
24
 
25
25
  def self?.reject_unexposed!: (untyped target, Symbol name) -> void
26
26
 
27
- def self?.resolve_arg: (untyped value, Kobako::Catalog::Handles handler) -> untyped
27
+ def self?.resolve_arg: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped
28
28
 
29
- def self?.resolve_target: (String | Kobako::Handle target, Kobako::Catalog::Namespaces namespaces, Kobako::Catalog::Handles handler) -> untyped
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::Catalog::Namespaces namespaces) -> untyped
31
+ def self?.resolve_path: (String path, Kobako::Transport::_ServiceRegistry services) -> untyped
32
32
 
33
- def self?.require_live_object!: (Integer id, Kobako::Catalog::Handles handler) -> untyped
33
+ def self?.require_live_object!: (Integer id, Kobako::Codec::_HandleTable handler) -> untyped
34
34
 
35
- def self?.encode_ok: (untyped value, Kobako::Catalog::Handles handler) -> String
35
+ def self?.encode_ok: (untyped value, Kobako::Codec::_HandleTable handler) -> String
36
36
 
37
- def self?.wrap_as_handle: (untyped value, Kobako::Catalog::Handles handler) -> Kobako::Handle
37
+ def self?.wrap_as_handle: (untyped value, Kobako::Codec::_HandleTable handler) -> Kobako::Handle
38
38
 
39
39
  def self?.encode_error: (String type, String message) -> String
40
40
  end
@@ -11,7 +11,7 @@ module Kobako
11
11
 
12
12
  def initialize: (entrypoint: Symbol | String, ?args: Array[untyped], ?kwargs: Hash[untyped, untyped]) -> void
13
13
 
14
- def encode: (Kobako::Catalog::Handles handler) -> String
14
+ def encode: (Kobako::Codec::_HandleTable handler) -> String
15
15
 
16
16
  private
17
17
 
@@ -3,10 +3,10 @@ module Kobako
3
3
  class Yielder
4
4
  @yield_to_guest: Kobako::Transport::_GuestYielder
5
5
  @break_tag: Symbol
6
- @handler: Kobako::Catalog::Handles
6
+ @handler: Kobako::Codec::_HandleTable
7
7
  @active: bool
8
8
 
9
- def initialize: (Kobako::Transport::_GuestYielder yield_to_guest, Symbol break_tag, Kobako::Catalog::Handles handler) -> void
9
+ def initialize: (Kobako::Transport::_GuestYielder yield_to_guest, Symbol break_tag, Kobako::Codec::_HandleTable handler) -> void
10
10
 
11
11
  def yield: (*untyped args) -> untyped
12
12
 
@@ -16,7 +16,7 @@ module Kobako
16
16
 
17
17
  private
18
18
 
19
- def restore: (untyped value) -> untyped
19
+ def restore: (untyped value, bool carried_handle) -> untyped
20
20
 
21
21
  def yield_failure: (untyped payload, default: String) -> RuntimeError
22
22
  end
@@ -8,5 +8,13 @@ module Kobako
8
8
  interface _GuestYielder
9
9
  def call: (String) -> String
10
10
  end
11
+
12
+ # The Service registry a dispatch resolves constant-path targets
13
+ # against. The Sandbox's Kobako::Catalog::Services is the
14
+ # production conformer; modelled structurally so the Transport
15
+ # layer needs no upward dependency on Catalog.
16
+ interface _ServiceRegistry
17
+ def lookup: (String target) -> untyped
18
+ end
11
19
  end
12
20
  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.13.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aotokitsuruya
@@ -53,6 +53,7 @@ files:
53
53
  - Cargo.toml
54
54
  - LICENSE
55
55
  - README.md
56
+ - ROADMAP.md
56
57
  - SECURITY.md
57
58
  - crates/kobako-runtime/CHANGELOG.md
58
59
  - crates/kobako-runtime/Cargo.toml
@@ -91,19 +92,19 @@ files:
91
92
  - lib/kobako/capture.rb
92
93
  - lib/kobako/catalog.rb
93
94
  - lib/kobako/catalog/handles.rb
94
- - lib/kobako/catalog/namespaces.rb
95
+ - lib/kobako/catalog/services.rb
95
96
  - lib/kobako/catalog/snippets.rb
96
97
  - lib/kobako/codec.rb
97
98
  - lib/kobako/codec/decoder.rb
98
99
  - lib/kobako/codec/encoder.rb
99
100
  - lib/kobako/codec/error.rb
100
- - lib/kobako/codec/factory.rb
101
+ - lib/kobako/codec/ext_types.rb
101
102
  - lib/kobako/codec/handle_walk.rb
103
+ - lib/kobako/codec/state.rb
102
104
  - lib/kobako/codec/utils.rb
103
105
  - lib/kobako/errors.rb
104
106
  - lib/kobako/fault.rb
105
107
  - lib/kobako/handle.rb
106
- - lib/kobako/namespace.rb
107
108
  - lib/kobako/outcome.rb
108
109
  - lib/kobako/outcome/panic.rb
109
110
  - lib/kobako/pool.rb
@@ -129,19 +130,19 @@ files:
129
130
  - sig/kobako/capture.rbs
130
131
  - sig/kobako/catalog.rbs
131
132
  - sig/kobako/catalog/handles.rbs
132
- - sig/kobako/catalog/namespaces.rbs
133
+ - sig/kobako/catalog/services.rbs
133
134
  - sig/kobako/catalog/snippets.rbs
134
135
  - sig/kobako/codec.rbs
135
136
  - sig/kobako/codec/decoder.rbs
136
137
  - sig/kobako/codec/encoder.rbs
137
138
  - sig/kobako/codec/error.rbs
138
- - sig/kobako/codec/factory.rbs
139
+ - sig/kobako/codec/ext_types.rbs
139
140
  - sig/kobako/codec/handle_walk.rbs
141
+ - sig/kobako/codec/state.rbs
140
142
  - sig/kobako/codec/utils.rbs
141
143
  - sig/kobako/errors.rbs
142
144
  - sig/kobako/fault.rbs
143
145
  - sig/kobako/handle.rbs
144
- - sig/kobako/namespace.rbs
145
146
  - sig/kobako/outcome.rbs
146
147
  - sig/kobako/outcome/panic.rbs
147
148
  - 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