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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +67 -0
  4. data/Cargo.lock +3 -3
  5. data/README.md +104 -29
  6. data/ROADMAP.md +2 -3
  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/snapshot.rs +7 -3
  12. data/crates/kobako-wasmtime/CHANGELOG.md +14 -0
  13. data/crates/kobako-wasmtime/Cargo.toml +2 -2
  14. data/crates/kobako-wasmtime/README.md +1 -1
  15. data/crates/kobako-wasmtime/src/capture.rs +17 -13
  16. data/crates/kobako-wasmtime/src/driver.rs +6 -7
  17. data/crates/kobako-wasmtime/src/frames.rs +2 -9
  18. data/crates/kobako-wasmtime/src/guest_mem.rs +8 -1
  19. data/crates/kobako-wasmtime/src/trap.rs +30 -57
  20. data/data/kobako.wasm +0 -0
  21. data/ext/kobako/Cargo.toml +1 -1
  22. data/ext/kobako/src/runtime/errors.rs +9 -39
  23. data/ext/kobako/src/runtime.rs +5 -20
  24. data/lib/kobako/catalog/extensions.rb +114 -0
  25. data/lib/kobako/catalog/handles.rb +1 -1
  26. data/lib/kobako/catalog/services.rb +135 -0
  27. data/lib/kobako/catalog/snippets.rb +1 -1
  28. data/lib/kobako/catalog.rb +7 -5
  29. data/lib/kobako/codec/decoder.rb +3 -3
  30. data/lib/kobako/codec/encoder.rb +4 -4
  31. data/lib/kobako/codec/ext_types.rb +169 -0
  32. data/lib/kobako/codec/state.rb +98 -0
  33. data/lib/kobako/codec/utils.rb +2 -2
  34. data/lib/kobako/codec.rb +23 -6
  35. data/lib/kobako/extension.rb +47 -0
  36. data/lib/kobako/fault.rb +1 -1
  37. data/lib/kobako/handle.rb +1 -1
  38. data/lib/kobako/outcome.rb +13 -7
  39. data/lib/kobako/pool.rb +1 -1
  40. data/lib/kobako/sandbox.rb +47 -27
  41. data/lib/kobako/transport/dispatcher.rb +25 -18
  42. data/lib/kobako/transport/request.rb +14 -9
  43. data/lib/kobako/transport/response.rb +9 -2
  44. data/lib/kobako/transport/yield.rb +4 -1
  45. data/lib/kobako/transport/yielder.rb +14 -6
  46. data/lib/kobako/version.rb +1 -1
  47. data/lib/kobako.rb +1 -0
  48. data/sig/kobako/catalog/extensions.rbs +25 -0
  49. data/sig/kobako/catalog/services.rbs +27 -0
  50. data/sig/kobako/codec/ext_types.rbs +31 -0
  51. data/sig/kobako/codec/state.rbs +20 -0
  52. data/sig/kobako/codec.rbs +3 -0
  53. data/sig/kobako/extension.rbs +20 -0
  54. data/sig/kobako/sandbox.rbs +3 -1
  55. data/sig/kobako/transport/dispatcher.rbs +4 -4
  56. data/sig/kobako/transport/yielder.rbs +1 -1
  57. data/sig/kobako/transport.rbs +2 -2
  58. metadata +11 -7
  59. data/lib/kobako/catalog/namespaces.rb +0 -115
  60. data/lib/kobako/codec/factory.rb +0 -187
  61. data/lib/kobako/namespace.rb +0 -78
  62. data/sig/kobako/catalog/namespaces.rbs +0 -17
  63. data/sig/kobako/codec/factory.rbs +0 -34
  64. data/sig/kobako/namespace.rbs +0 -21
@@ -1,187 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "singleton"
4
- require "forwardable"
5
- require "msgpack"
6
-
7
- require_relative "error"
8
- require_relative "utils"
9
- require_relative "../handle"
10
- require_relative "../fault"
11
-
12
- module Kobako
13
- module Codec
14
- # Cached +MessagePack::Factory+ that owns the kobako wire ext-type
15
- # registration ({docs/wire-codec.md}[link:../../../docs/wire-codec.md]
16
- # § Ext Types).
17
- #
18
- # The factory is the single place in the host gem that touches the
19
- # msgpack API — both Encoder and Decoder delegate through it, so
20
- # the three kobako ext codes (0x00 Symbol, 0x01 Capability Handle,
21
- # 0x02 Exception envelope) are configured exactly once at first use.
22
- #
23
- # Lifecycle is owned by +Singleton+ from the Ruby standard library:
24
- # +Factory.instance+ is lazy, thread-safe, and process-wide. Class-level
25
- # +Factory.dump+ / +Factory.load+ shortcuts are exposed via
26
- # +SingleForwardable+ so callers do not have to spell the +.instance+
27
- # hop at every call site; the instance-level +#dump+ / +#load+ are in
28
- # turn delegated to the wrapped +MessagePack::Factory+ via +Forwardable+.
29
- class Factory
30
- include Singleton
31
- extend Forwardable
32
- extend SingleForwardable
33
-
34
- # MessagePack ext type code reserved for Symbol
35
- # ({docs/wire-codec.md}[link:../../../docs/wire-codec.md] § Ext Types
36
- # → ext 0x00). Class-private — mirrors +codec::EXT_SYMBOL+ on the
37
- # Rust side.
38
- EXT_SYMBOL = 0x00
39
- # MessagePack ext type code reserved for Capability Handle
40
- # ({docs/wire-codec.md}[link:../../../docs/wire-codec.md] § Ext Types
41
- # → ext 0x01). Class-private — mirrors +codec::EXT_HANDLE+ on the
42
- # Rust side.
43
- EXT_HANDLE = 0x01
44
- # MessagePack ext type code reserved for Exception envelope
45
- # ({docs/wire-codec.md}[link:../../../docs/wire-codec.md] § Ext Types
46
- # → ext 0x02). Class-private — mirrors +codec::EXT_ERRENV+ on the
47
- # Rust side.
48
- EXT_ERRENV = 0x02
49
- private_constant :EXT_SYMBOL, :EXT_HANDLE, :EXT_ERRENV
50
-
51
- # An ext 0x02 (Fault) envelope nests through its +details+ field, and
52
- # each level re-enters the codec with a fresh +MessagePack+ unpacker
53
- # whose built-in stack guard resets — so ext-envelope depth is tracked
54
- # here instead. The cap matches the wire's overall nesting bound and
55
- # keeps a nested chain from exhausting the native stack: an over-deep
56
- # chain fails as a clean wire error, never a stack-level trap.
57
- MAX_EXT_DEPTH = 128
58
- EXT_DEPTH_KEY = :__kobako_codec_ext_depth__
59
- private_constant :MAX_EXT_DEPTH, :EXT_DEPTH_KEY
60
-
61
- # Instance-level pass-through onto the wrapped +MessagePack::Factory+.
62
- # Spelled +def_instance_delegators+ rather than +def_delegators+ because
63
- # the class also extends +SingleForwardable+ (see the +extend+ block
64
- # above), which defines its own +def_delegators+ that shadows
65
- # +Forwardable+'s — the unambiguous forms keep both delegation tiers
66
- # wired to the right scope.
67
- def_instance_delegators :@factory, :dump, :load
68
-
69
- # Class-level shortcuts so callers can write +Factory.dump(v)+ instead
70
- # of +Factory.instance.dump(v)+; both resolve to the same singleton.
71
- def_single_delegators :instance, :dump, :load
72
-
73
- def initialize
74
- @factory = MessagePack::Factory.new
75
- register_symbol
76
- register_handle
77
- register_fault
78
- end
79
-
80
- private
81
-
82
- def register_symbol
83
- @factory.register_type(
84
- EXT_SYMBOL, Symbol,
85
- packer: method(:pack_symbol),
86
- unpacker: method(:unpack_symbol)
87
- )
88
- end
89
-
90
- # Symbol-to-name packer for the ext-0x00 registration.
91
- def pack_symbol(symbol)
92
- symbol.name
93
- end
94
-
95
- # Validate the ext-0x00 payload as UTF-8 and intern. Raises
96
- # InvalidEncoding on invalid bytes — SPEC forbids the
97
- # binary-encoding fallback that msgpack-gem's default unpacker
98
- # would otherwise apply. The re-tag step lives here because the
99
- # msgpack ext-type unpacker hands us binary bytes; the assertion
100
- # itself is shared with Decoder via Utils.assert_utf8!. The
101
- # +"Symbol"+ label keeps the error message in Ruby vocabulary
102
- # rather than wire-ext-code vocabulary.
103
- def unpack_symbol(payload)
104
- name = payload.b.force_encoding(Encoding::UTF_8)
105
- Utils.assert_utf8!(name, "Symbol payload")
106
- name.to_sym
107
- end
108
-
109
- def register_handle
110
- @factory.register_type(
111
- EXT_HANDLE, Kobako::Handle,
112
- packer: ->(handle) { [handle.id].pack("N") },
113
- unpacker: ->(payload) { unpack_handle(payload) }
114
- )
115
- end
116
-
117
- def register_fault
118
- @factory.register_type(
119
- EXT_ERRENV, Kobako::Fault,
120
- packer: ->(fault) { pack_fault(fault) },
121
- unpacker: ->(payload) { unpack_fault(payload) }
122
- )
123
- end
124
-
125
- # Peel off the fixext-4 frame, hand the bytes to the
126
- # Host-Gem-internal +Kobako::Handle.restore+ factory, and
127
- # translate the +ArgumentError+ raised by Handle's invariants
128
- # into a wire-layer +InvalidType+ via Codec::Utils.with_boundary.
129
- # The Value Object owns the id-range contract; this method only
130
- # owns the frame shape.
131
- def unpack_handle(payload)
132
- bytes = payload.b
133
- raise InvalidType, "Handle payload must be 4 bytes, got #{bytes.bytesize}" unless bytes.bytesize == 4
134
-
135
- id = bytes.unpack1("N") # : Integer
136
- Codec::Utils.with_boundary { Kobako::Handle.restore(id) }
137
- end
138
-
139
- # Encode the inner ext-0x02 map via Encoder (not +factory.dump+) so
140
- # the embedded payload flows through the same boundary as a top-level
141
- # encode — nested kobako values (Handle, nested Fault) reach the
142
- # registered ext-type packers via the cached singleton. A +details+
143
- # chain nested past MAX_EXT_DEPTH has no wire representation and
144
- # surfaces as +UnsupportedType+.
145
- def pack_fault(fault)
146
- within_ext_frame(UnsupportedType) do
147
- Encoder.encode("type" => fault.type, "message" => fault.message, "details" => fault.details)
148
- end
149
- end
150
-
151
- # Peel the embedded msgpack map and hand it to +Kobako::Fault.new+
152
- # inside Decoder.decode's block form, so the value-object's
153
- # +ArgumentError+ invariants surface as +InvalidType+ through the
154
- # decoder boundary. Inner decode goes through Decoder (not
155
- # +factory.load+) so the embedded +str+ payloads flow through the
156
- # same UTF-8 validation as a top-level decode. A nested ext 0x02 in
157
- # +details+ re-enters this method, so #within_ext_frame bounds the
158
- # chain depth to keep it from exhausting the native stack.
159
- def unpack_fault(payload)
160
- within_ext_frame(InvalidType) do
161
- Decoder.decode(payload) do |map|
162
- raise InvalidType, "Fault payload must be a map" unless map.is_a?(Hash)
163
-
164
- Kobako::Fault.new(type: map["type"], message: map["message"], details: map["details"])
165
- end
166
- end
167
- end
168
-
169
- # Track ext-envelope re-entry depth and refuse a chain past
170
- # MAX_EXT_DEPTH, raising +over_limit+ so the failure lands in the
171
- # caller's existing wire-error class. The counter is thread-scoped and
172
- # balanced by the +ensure+, so a raise mid-chain still unwinds it to
173
- # its entry value.
174
- def within_ext_frame(over_limit)
175
- depth = (Thread.current[EXT_DEPTH_KEY] || 0) + 1
176
- raise over_limit, "ext envelope nesting exceeds #{MAX_EXT_DEPTH} levels" if depth > MAX_EXT_DEPTH
177
-
178
- Thread.current[EXT_DEPTH_KEY] = depth
179
- begin
180
- yield
181
- ensure
182
- Thread.current[EXT_DEPTH_KEY] = depth - 1
183
- end
184
- end
185
- end
186
- end
187
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kobako
4
- # A named grouping of Members for one Sandbox.
5
- # Returned by +Sandbox#define+. Each instance owns a flat name→object
6
- # table of Members; member binding is validated against NAME_PATTERN.
7
- class Namespace
8
- # Ruby constant-name pattern shared by Namespace and Member names.
9
- NAME_PATTERN = /\A[A-Z]\w*\z/
10
-
11
- attr_reader :name
12
-
13
- # Build a new Namespace. +name+ is an already-validated Namespace
14
- # name (must satisfy NAME_PATTERN; validation is the caller's
15
- # responsibility).
16
- def initialize(name)
17
- @name = name
18
- @members = {} # : Hash[String, untyped]
19
- @sealed = false
20
- end
21
-
22
- # Bind +object+ under +member+ inside this Namespace. +member+ is a
23
- # constant-form name as a +Symbol+ or +String+. +object+ is any Ruby
24
- # object that responds to the methods guest code will invoke. Returns
25
- # +self+ for chaining. Raises +ArgumentError+ when +member+ does not
26
- # match the constant pattern, when a Member of the same name is
27
- # already bound, or when the owning Sandbox's first invocation has
28
- # sealed Service registration.
29
- def bind(member, object)
30
- raise ArgumentError, "cannot bind after first Sandbox invocation" if @sealed
31
-
32
- member_str = validate_member_name!(member)
33
- raise ArgumentError, "Member #{@name}::#{member_str} is already bound" if @members.key?(member_str)
34
-
35
- @members[member_str] = object
36
- self
37
- end
38
-
39
- # Mark this Namespace as sealed. Called by
40
- # +Kobako::Catalog::Namespaces#seal!+ on the owning Sandbox's first
41
- # invocation; afterwards #bind raises +ArgumentError+. Idempotent;
42
- # returns +self+.
43
- def seal!
44
- @sealed = true
45
- self
46
- end
47
-
48
- # Member lookup; raises +KeyError+ when no Member is registered
49
- # under +member+.
50
- def fetch(member)
51
- member_str = member.to_s
52
- unless @members.key?(member_str)
53
- raise KeyError,
54
- "no member named #{member_str.inspect} in namespace #{@name.inspect}"
55
- end
56
-
57
- @members[member_str]
58
- end
59
-
60
- # Structured description for the guest preamble (Frame 1). Returns a
61
- # two-element array +[name, member_keys]+ suitable for msgpack encoding.
62
- def to_preamble
63
- [@name, @members.keys]
64
- end
65
-
66
- private
67
-
68
- def validate_member_name!(member)
69
- member_str = member.to_s
70
- unless NAME_PATTERN.match?(member_str)
71
- raise ArgumentError,
72
- "MemberName must match #{NAME_PATTERN.inspect} (got #{member.inspect})"
73
- end
74
-
75
- member_str
76
- end
77
- end
78
- end
@@ -1,17 +0,0 @@
1
- module Kobako
2
- module Catalog
3
- class Namespaces
4
- def initialize: (?handler: Kobako::Catalog::Handles) -> void
5
-
6
- def define: (Symbol | String name) -> Kobako::Namespace
7
-
8
- def lookup: (String target) -> untyped
9
-
10
- def encode: () -> String
11
-
12
- def seal!: () -> self
13
-
14
- def sealed?: () -> bool
15
- end
16
- end
17
- end
@@ -1,34 +0,0 @@
1
- module Kobako
2
- module Codec
3
- class Factory
4
- include Singleton
5
- extend Forwardable
6
- extend SingleForwardable
7
-
8
- EXT_SYMBOL: Integer
9
- EXT_HANDLE: Integer
10
- EXT_ERRENV: Integer
11
- MAX_EXT_DEPTH: Integer
12
- EXT_DEPTH_KEY: Symbol
13
-
14
- def dump: (untyped value) -> String
15
- def load: (String bytes) -> untyped
16
-
17
- def self.dump: (untyped value) -> String
18
- def self.load: (String bytes) -> untyped
19
-
20
- private
21
-
22
- def initialize: () -> void
23
- def register_symbol: () -> void
24
- def pack_symbol: (Symbol symbol) -> String
25
- def unpack_symbol: (String payload) -> Symbol
26
- def register_handle: () -> void
27
- def register_fault: () -> void
28
- def unpack_handle: (String payload) -> Kobako::Handle
29
- def pack_fault: (Kobako::Fault fault) -> String
30
- def unpack_fault: (String payload) -> Kobako::Fault
31
- def within_ext_frame: [T] (singleton(Kobako::Codec::Error) over_limit) { () -> T } -> T
32
- end
33
- end
34
- end
@@ -1,21 +0,0 @@
1
- module Kobako
2
- class Namespace
3
- NAME_PATTERN: Regexp
4
-
5
- attr_reader name: String
6
-
7
- def initialize: (String name) -> void
8
-
9
- def bind: (Symbol | String member, untyped object) -> self
10
-
11
- def seal!: () -> self
12
-
13
- def fetch: (Symbol | String member) -> untyped
14
-
15
- def to_preamble: () -> [String, Array[String]]
16
-
17
- private
18
-
19
- def validate_member_name!: (Symbol | String member) -> String
20
- end
21
- end