kobako 0.18.0-aarch64-linux → 0.20.0-aarch64-linux
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 +31 -0
- data/README.md +4 -1
- data/crates/kobako-runtime/CHANGELOG.md +14 -0
- data/crates/kobako-runtime/README.md +1 -1
- data/crates/kobako-wasmtime/CHANGELOG.md +14 -0
- data/crates/kobako-wasmtime/README.md +1 -1
- data/data/kobako.wasm +0 -0
- data/lib/kobako/3.3/kobako.so +0 -0
- data/lib/kobako/3.4/kobako.so +0 -0
- data/lib/kobako/4.0/kobako.so +0 -0
- data/lib/kobako/catalog/extensions.rb +50 -43
- data/lib/kobako/catalog/handles.rb +7 -16
- data/lib/kobako/catalog/services.rb +22 -36
- data/lib/kobako/catalog/snippets.rb +11 -10
- data/lib/kobako/context.rb +202 -0
- data/lib/kobako/errors.rb +26 -4
- data/lib/kobako/execution.rb +52 -0
- data/lib/kobako/extension.rb +24 -10
- data/lib/kobako/pool.rb +1 -7
- data/lib/kobako/sandbox.rb +43 -182
- data/lib/kobako/sandbox_options.rb +23 -2
- data/lib/kobako/transport/dispatcher.rb +16 -15
- data/lib/kobako/transport/yielder.rb +1 -1
- data/lib/kobako/unresolved.rb +17 -0
- data/lib/kobako/usage.rb +6 -6
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/release-please-config.json +2 -1
- data/sig/kobako/catalog/extensions.rbs +4 -6
- data/sig/kobako/catalog/handles.rbs +0 -2
- data/sig/kobako/catalog/services.rbs +3 -3
- data/sig/kobako/catalog/snippets.rbs +2 -0
- data/sig/kobako/context.rbs +36 -0
- data/sig/kobako/errors.rbs +12 -3
- data/sig/kobako/execution.rbs +24 -0
- data/sig/kobako/extension.rbs +4 -2
- data/sig/kobako/runtime.rbs +37 -9
- data/sig/kobako/sandbox.rbs +5 -24
- data/sig/kobako/sandbox_options.rbs +9 -2
- data/sig/kobako/transport/dispatcher.rbs +3 -3
- data/sig/kobako/unresolved.rbs +4 -0
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b09a439fa6c5bfc4598a565bc0ec6cfd026c65c48a39e9c9c0ae73c53ffb00d9
|
|
4
|
+
data.tar.gz: 0b1a09a6af7b5eabf918667c9efcc434952d693322fda2f71cf0195df174d21a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5475e836c30518290c9b94eab8c0fe92c222efea64c93d909a099d50612e674a75733777fd3c37e884b7564dbc40fa46a3f90a04a26c9be40aa5d54dc548f1f
|
|
7
|
+
data.tar.gz: d76951bea7e5eae63d2c79052dcd3d2e03204afa738020f958139a7b293ae55624d94b8e11a6aa1a124f5b9094b281852e4fca6042c6dbf5d797e29dac874fc5
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.20.0","wasm/kobako-core":"0.12.0","wasm/kobako-mruby":"0.12.0","wasm/kobako-io":"0.12.0","wasm/kobako-json":"0.12.0","wasm/kobako-regexp":"0.12.0","wasm/kobako-baker":"0.12.0","crates/kobako-codec":"0.12.0","crates/kobako-runtime":"0.12.0","crates/kobako-wasmtime":"0.12.0","crates/kobako":"0.12.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.20.0](https://github.com/elct9620/kobako/compare/v0.19.0...v0.20.0) (2026-07-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **bench:** mark a run in progress with a pid lock file ([3ca85fb](https://github.com/elct9620/kobako/commit/3ca85fbe9860ce32d58c1a5bf4ff85cbdc210780))
|
|
9
|
+
* **execution:** tell a nil-value success from a failure with #failed? ([2f3d382](https://github.com/elct9620/kobako/commit/2f3d382b24d65fabda7771429cd3adfc90a8c7e8))
|
|
10
|
+
* **extension:** declare a backend's kind by explicit keyword ([3f6c628](https://github.com/elct9620/kobako/commit/3f6c6283080ba4346ac8ef878e469dab8039e650))
|
|
11
|
+
* **sandbox:** add the gvl: hold/release scheduling option ([ca2a884](https://github.com/elct9620/kobako/commit/ca2a884415b3271c44b20d131536b3fe7c5a3077))
|
|
12
|
+
* **sandbox:** declare fillable Service paths defaulting to Kobako::Unresolved ([1d60f76](https://github.com/elct9620/kobako/commit/1d60f764ddff3b5b10206993e2b281c838ec0022))
|
|
13
|
+
* **sandbox:** fill and override declared paths per invocation with ctx.bind ([629ef7d](https://github.com/elct9620/kobako/commit/629ef7dfa35273c952685fefb4c5793b11886aee))
|
|
14
|
+
* **sandbox:** return a frozen Kobako::Invocation from #eval and #run ([e9f4634](https://github.com/elct9620/kobako/commit/e9f463403bea26fad32e52d560b979684f555ed1))
|
|
15
|
+
* **tasks:** gate the wasmtime driver against a magnus dependency ([268c3f6](https://github.com/elct9620/kobako/commit/268c3f65f53d64b465458bca5c0c9b0d0467df44))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **context:** raise ArgumentError for a spent ctx.bind ([2783bba](https://github.com/elct9620/kobako/commit/2783bba20f139554178389d4c860e0c280cab550))
|
|
21
|
+
* **extension:** re-assert dependencies after a failed seal ([72839ba](https://github.com/elct9620/kobako/commit/72839bac6186e81a1d4323bd3f1eb4f10abf3d64))
|
|
22
|
+
|
|
23
|
+
## [0.19.0](https://github.com/elct9620/kobako/compare/v0.18.0...v0.19.0) (2026-07-19)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### ⚠ BREAKING CHANGES
|
|
27
|
+
|
|
28
|
+
* **readme:** guest scripts using `class X < Kobako::Member` must switch to `extend Kobako::Proxy`; Kobako::Member no longer exists in the guest.
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
|
|
32
|
+
* **readme:** switch the Extension File idiom off the removed Kobako::Member ([0d96b9e](https://github.com/elct9620/kobako/commit/0d96b9eafed11df6fa030c38f93b2a0e9b190ed8))
|
|
33
|
+
|
|
3
34
|
## [0.18.0](https://github.com/elct9620/kobako/compare/v0.17.0...v0.18.0) (2026-07-18)
|
|
4
35
|
|
|
5
36
|
|
data/README.md
CHANGED
|
@@ -388,7 +388,8 @@ An Extension teaches the guest a native-style constant by pairing a guest idiom
|
|
|
388
388
|
|
|
389
389
|
```ruby
|
|
390
390
|
FILE = <<~'MRUBY'
|
|
391
|
-
class File
|
|
391
|
+
class File
|
|
392
|
+
extend Kobako::Proxy
|
|
392
393
|
def self.basename(path) = path.split("/").last || "" # pure: runs in-guest
|
|
393
394
|
# read / write are undefined here, so they dispatch to the backend
|
|
394
395
|
end
|
|
@@ -476,6 +477,8 @@ Building from source requires a WASI-capable Rust toolchain in addition to the s
|
|
|
476
477
|
|
|
477
478
|
Bug reports and pull requests are welcome at <https://github.com/elct9620/kobako>. Please open an issue before starting on non-trivial changes so we can align on scope.
|
|
478
479
|
|
|
480
|
+
Releases are automated with release-please across two tracks — the gem and the linked guest-crate group. The version rules, commit conventions, and how to cut each release live in [`docs/releasing.md`](docs/releasing.md).
|
|
481
|
+
|
|
479
482
|
## License
|
|
480
483
|
|
|
481
484
|
Kobako is released under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.11.0...kobako-runtime-v0.12.0) (2026-07-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* **kobako-runtime:** Synchronize kobako crates versions
|
|
9
|
+
|
|
10
|
+
## [0.11.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.10.2...kobako-runtime-v0.11.0) (2026-07-19)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Miscellaneous Chores
|
|
14
|
+
|
|
15
|
+
* **kobako-runtime:** Synchronize kobako crates versions
|
|
16
|
+
|
|
3
17
|
## [0.10.2](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.10.1...kobako-runtime-v0.10.2) (2026-07-18)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.11.0...kobako-wasmtime-v0.12.0) (2026-07-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* **kobako-wasmtime:** Synchronize kobako crates versions
|
|
9
|
+
|
|
10
|
+
## [0.11.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.10.2...kobako-wasmtime-v0.11.0) (2026-07-19)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Miscellaneous Chores
|
|
14
|
+
|
|
15
|
+
* **kobako-wasmtime:** Synchronize kobako crates versions
|
|
16
|
+
|
|
3
17
|
## [0.10.2](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.10.1...kobako-wasmtime-v0.10.2) (2026-07-18)
|
|
4
18
|
|
|
5
19
|
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/lib/kobako/3.3/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/3.4/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/4.0/kobako.so
CHANGED
|
Binary file
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../unresolved"
|
|
4
|
+
|
|
3
5
|
module Kobako
|
|
4
6
|
module Catalog
|
|
5
7
|
# Kobako::Catalog::Extensions — per-Sandbox registry of installed
|
|
@@ -7,7 +9,9 @@ module Kobako
|
|
|
7
9
|
# install time (its +source+ into +Catalog::Snippets+, its +backend+
|
|
8
10
|
# path into +Catalog::Services+), asserts declared dependencies are
|
|
9
11
|
# present when the Sandbox seals, and resolves each callable-backed
|
|
10
|
-
# path to a fresh object
|
|
12
|
+
# path to a fresh object for every invocation — returning that map to the
|
|
13
|
+
# driving +Context+ rather than mutating the shared registry, so
|
|
14
|
+
# concurrent invocations stay shared-nothing.
|
|
11
15
|
#
|
|
12
16
|
# Sealing and the reject-after-install guard are governed by the owning
|
|
13
17
|
# Sandbox through +Catalog::Services#sealed?+, the shared seal signal;
|
|
@@ -21,77 +25,87 @@ module Kobako
|
|
|
21
25
|
|
|
22
26
|
# Install +extension+: validate its shape, register its +source+ as a
|
|
23
27
|
# preloaded snippet, and — when it carries a +backend+ — reserve the
|
|
24
|
-
# backend path in +services+. A
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# +
|
|
29
|
-
#
|
|
30
|
-
# +
|
|
31
|
-
#
|
|
28
|
+
# backend path in +services+. A static +object:+ binds directly; a
|
|
29
|
+
# per-invocation +provider:+ or a fillable backend reserves the path with
|
|
30
|
+
# +Kobako::Unresolved+ (the per-invocation result is layered onto the
|
|
31
|
+
# Context, a fillable stays Unresolved until filled). Raises
|
|
32
|
+
# +ArgumentError+ for the two shapes +#validate!+ owns — +source+ not a
|
|
33
|
+
# String, or a present +backend+ that omits +path+ / +object+ /
|
|
34
|
+
# +provider+. The Extension readers are duck-typed, so an object missing
|
|
35
|
+
# them surfaces the underlying +NoMethodError+, and a malformed +name+ or
|
|
36
|
+
# +backend.path+ surfaces through the +snippets+ / +services+
|
|
37
|
+
# registration it routes to.
|
|
32
38
|
def install(extension, snippets:, services:)
|
|
33
39
|
validate!(extension)
|
|
34
40
|
snippets.register(code: extension.source, name: extension.name)
|
|
35
41
|
backend = extension.backend
|
|
36
|
-
services.bind(backend.path,
|
|
42
|
+
services.bind(backend.path, install_object(backend)) if backend
|
|
37
43
|
@entries << extension
|
|
38
44
|
self
|
|
39
45
|
end
|
|
40
46
|
|
|
41
47
|
# Assert every installed Extension's +depends_on+ names a fellow
|
|
42
|
-
# installed Extension. Runs once, when the Sandbox first seals
|
|
43
|
-
# unmet dependency raises +ArgumentError+ naming the
|
|
44
|
-
# guest runs.
|
|
48
|
+
# installed Extension. Runs once, when the Sandbox first seals
|
|
49
|
+
# successfully; an unmet dependency raises +ArgumentError+ naming the
|
|
50
|
+
# gap, before the guest runs. The asserted flag flips only on success,
|
|
51
|
+
# so a seal that failed re-checks on the next attempt rather than
|
|
52
|
+
# silently passing a broken Sandbox. Idempotent across later invocations.
|
|
45
53
|
def seal!
|
|
46
54
|
return self if @asserted
|
|
47
55
|
|
|
48
|
-
@asserted = true
|
|
49
56
|
assert_dependencies!
|
|
57
|
+
@asserted = true
|
|
50
58
|
self
|
|
51
59
|
end
|
|
52
60
|
|
|
53
|
-
# Resolve each
|
|
54
|
-
#
|
|
55
|
-
# providers yield distinct objects; one
|
|
56
|
-
# Extensions is invoked once and its result
|
|
57
|
-
# identity is resource identity.
|
|
58
|
-
# they stay the object bound at install
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
resolved
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
# Resolve each +provider:+-backed path to this invocation's object and
|
|
62
|
+
# return the +path => object+ map the driving +Context+ layers over the
|
|
63
|
+
# static base bindings. Distinct providers yield distinct objects; one
|
|
64
|
+
# provider shared by several Extensions is invoked once and its result
|
|
65
|
+
# shared, so provider identity is resource identity. Static and fillable
|
|
66
|
+
# backends are omitted — they stay the object bound at install in the
|
|
67
|
+
# base registry (+Kobako::Unresolved+ for a fillable).
|
|
68
|
+
def resolve
|
|
69
|
+
resolved = {} # : Hash[String, untyped]
|
|
70
|
+
by_provider = {} # : Hash[untyped, untyped]
|
|
71
|
+
by_provider.compare_by_identity
|
|
72
|
+
@entries.each { |extension| resolve_backend(extension, resolved, by_provider) }
|
|
73
|
+
resolved
|
|
64
74
|
end
|
|
65
75
|
|
|
66
76
|
private
|
|
67
77
|
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
# The object a backend binds at install: a static +object:+ directly, or
|
|
79
|
+
# +Kobako::Unresolved+ as the placeholder for a per-invocation +provider:+
|
|
80
|
+
# (layered onto the Context) or a fillable (neither keyword).
|
|
81
|
+
def install_object(backend) = backend.object.nil? ? Kobako::Unresolved : backend.object
|
|
82
|
+
|
|
83
|
+
# Resolve one Extension's +provider:+-backed path against the shared
|
|
84
|
+
# per-invocation identity cache (keyed by provider identity) and record
|
|
85
|
+
# it in +resolved+. A static or fillable backend is skipped — its object
|
|
86
|
+
# stays as bound at install in the base registry.
|
|
87
|
+
def resolve_backend(extension, resolved, by_provider)
|
|
73
88
|
backend = extension.backend
|
|
74
89
|
return unless backend
|
|
75
90
|
|
|
76
91
|
provider = backend.provider
|
|
77
|
-
return
|
|
92
|
+
return if provider.nil?
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
services.refresh(backend.path, object)
|
|
94
|
+
resolved[backend.path.to_s] = by_provider.fetch(provider) { by_provider[provider] = provider.call }
|
|
81
95
|
end
|
|
82
96
|
|
|
83
97
|
# Enforce the Extension-shape checks +#preload+ / +#bind+ do not: a
|
|
84
98
|
# mandatory String +source+ (the install/bind boundary) and a
|
|
85
|
-
# +backend+ that, when present, exposes +path+
|
|
99
|
+
# +backend+ that, when present, exposes +path+ / +object+ / +provider+.
|
|
86
100
|
def validate!(extension)
|
|
87
101
|
source = extension.source
|
|
88
102
|
raise ArgumentError, "Extension #source must be a String, got #{source.class}" unless source.is_a?(String)
|
|
89
103
|
|
|
90
104
|
backend = extension.backend
|
|
91
105
|
return if backend.nil?
|
|
92
|
-
return if backend.respond_to?(:path) && backend.respond_to?(:provider)
|
|
106
|
+
return if backend.respond_to?(:path) && backend.respond_to?(:object) && backend.respond_to?(:provider)
|
|
93
107
|
|
|
94
|
-
raise ArgumentError, "Extension #backend must expose #path and #provider"
|
|
108
|
+
raise ArgumentError, "Extension #backend must expose #path, #object, and #provider"
|
|
95
109
|
end
|
|
96
110
|
|
|
97
111
|
def assert_dependencies!
|
|
@@ -110,13 +124,6 @@ module Kobako
|
|
|
110
124
|
# of a constant token are interchangeable; a value that is neither
|
|
111
125
|
# falls through unchanged to the not-installed path.
|
|
112
126
|
def symbolize(name) = name.is_a?(String) ? name.to_sym : name
|
|
113
|
-
|
|
114
|
-
def callable?(provider) = provider.respond_to?(:call)
|
|
115
|
-
|
|
116
|
-
# A callable provider reserves its path with a placeholder that the
|
|
117
|
-
# per-invocation refresh replaces before any dispatch; a fixed
|
|
118
|
-
# provider is the bound object itself.
|
|
119
|
-
def initial_object(provider) = callable?(provider) ? nil : provider
|
|
120
127
|
end
|
|
121
128
|
end
|
|
122
129
|
end
|
|
@@ -5,10 +5,10 @@ require_relative "../handle"
|
|
|
5
5
|
module Kobako
|
|
6
6
|
module Catalog
|
|
7
7
|
# Host-side mapping from opaque integer Handle IDs to Ruby objects.
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
8
|
+
# Each invocation's +Kobako::Context+ mints its own table and hands it to
|
|
9
|
+
# the dispatcher, so guest→host dispatch resolves Handle targets and
|
|
10
|
+
# arguments against the same table that host→guest wire encoding allocates
|
|
11
|
+
# into for that run.
|
|
12
12
|
#
|
|
13
13
|
# Lifecycle invariants:
|
|
14
14
|
#
|
|
@@ -17,10 +17,9 @@ module Kobako
|
|
|
17
17
|
# invocation is 1; ID 0 is reserved as the invalid sentinel and is
|
|
18
18
|
# never returned by +#alloc+.
|
|
19
19
|
#
|
|
20
|
-
# -
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# argument).
|
|
20
|
+
# - A fresh table backs each invocation, so a Handle issued in one
|
|
21
|
+
# invocation resolves in no other — uniformly regardless of
|
|
22
|
+
# allocation source (Service return or host-injected argument).
|
|
24
23
|
#
|
|
25
24
|
# - The cap is +0x7fff_ffff+ (2³¹ − 1). Allocation beyond the cap
|
|
26
25
|
# raises immediately — no silent truncation, no wrap, no ID reuse.
|
|
@@ -63,14 +62,6 @@ module Kobako
|
|
|
63
62
|
@entries[id]
|
|
64
63
|
end
|
|
65
64
|
|
|
66
|
-
# Clear all entries AND reset the counter to 1. Called at the
|
|
67
|
-
# per-invocation boundary by +Kobako::Sandbox+. Returns +self+.
|
|
68
|
-
def reset!
|
|
69
|
-
@entries.clear
|
|
70
|
-
@next_id = 1
|
|
71
|
-
self
|
|
72
|
-
end
|
|
73
|
-
|
|
74
65
|
# Number of currently-bound entries. Used by tests of the Dispatcher
|
|
75
66
|
# and Codec::HandleWalk#deep_wrap to observe whether each path allocates
|
|
76
67
|
# exactly the Handle entries it should — the +Handles+ table itself never
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "handles"
|
|
4
3
|
require_relative "../codec"
|
|
5
4
|
require_relative "../errors"
|
|
6
5
|
|
|
@@ -18,23 +17,19 @@ module Kobako
|
|
|
18
17
|
# services.lookup("MyService::KV") # => kv_object
|
|
19
18
|
#
|
|
20
19
|
# Per-dispatch routing is +Kobako::Transport::Dispatcher+'s
|
|
21
|
-
# responsibility — the Dispatcher
|
|
22
|
-
# +
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
20
|
+
# responsibility — the Dispatcher resolves a path against the invocation's
|
|
21
|
+
# +Kobako::Context+ (which layers per-invocation providers and +ctx.bind+
|
|
22
|
+
# overrides over these base bindings) and the +Catalog::Handles+, both
|
|
23
|
+
# passed in the per-invocation dispatch Proc the Context hands to
|
|
24
|
+
# +Runtime#eval+ / +#run+.
|
|
26
25
|
class Services
|
|
27
26
|
# Ruby constant-name pattern each +::+-separated bind-path segment
|
|
28
27
|
# must match.
|
|
29
28
|
NAME_PATTERN = /\A[A-Z]\w*\z/
|
|
30
29
|
|
|
31
|
-
# Build a fresh registry.
|
|
32
|
-
|
|
33
|
-
# is pinned near +MAX_ID+ to exercise the cap-exhaustion path
|
|
34
|
-
# without 2³¹ allocations. Production callers leave it at the default.
|
|
35
|
-
def initialize(handler: Catalog::Handles.new)
|
|
30
|
+
# Build a fresh registry.
|
|
31
|
+
def initialize
|
|
36
32
|
@bindings = {} # : Hash[String, untyped]
|
|
37
|
-
@handler = handler
|
|
38
33
|
@sealed = false
|
|
39
34
|
@encoded = nil # : String?
|
|
40
35
|
end
|
|
@@ -56,6 +51,12 @@ module Kobako
|
|
|
56
51
|
self
|
|
57
52
|
end
|
|
58
53
|
|
|
54
|
+
# Returns +true+ iff a Service is bound at +path+ — the declared (Frame 1)
|
|
55
|
+
# key set a per-invocation +ctx.bind+ override may target.
|
|
56
|
+
def bound?(path)
|
|
57
|
+
@bindings.key?(path.to_s)
|
|
58
|
+
end
|
|
59
|
+
|
|
59
60
|
# Resolve a +target+ constant path to the bound Service. Raises
|
|
60
61
|
# +KeyError+ when no Service is bound at +target+.
|
|
61
62
|
def lookup(target)
|
|
@@ -65,18 +66,6 @@ module Kobako
|
|
|
65
66
|
@bindings[target_str]
|
|
66
67
|
end
|
|
67
68
|
|
|
68
|
-
# Replace the object bound at an already-registered +path+ without the
|
|
69
|
-
# collision or seal checks +#bind+ applies. +Catalog::Extensions+ calls
|
|
70
|
-
# this during +begin_invocation!+ to swap a callable backend provider's
|
|
71
|
-
# per-invocation result behind a path whose Frame 1 membership was
|
|
72
|
-
# fixed at the seal. A path that was never registered is left untouched,
|
|
73
|
-
# so the sealed key set can never grow here.
|
|
74
|
-
def refresh(path, object)
|
|
75
|
-
key = path.to_s
|
|
76
|
-
@bindings[key] = object if @bindings.key?(key)
|
|
77
|
-
self
|
|
78
|
-
end
|
|
79
|
-
|
|
80
69
|
# Encode the preamble as msgpack bytes for stdin Frame 1 delivery —
|
|
81
70
|
# a flat array of the bound constant paths, in bind order:
|
|
82
71
|
# +["MyService::KV", "File"]+. Routes through Kobako::Codec::Encoder
|
|
@@ -84,23 +73,20 @@ module Kobako
|
|
|
84
73
|
# Strings, so none of the kobako ext types fire. Returns a binary
|
|
85
74
|
# +String+ of msgpack bytes.
|
|
86
75
|
#
|
|
87
|
-
#
|
|
88
|
-
# subsequent
|
|
89
|
-
#
|
|
90
|
-
# raises +ArgumentError+ and never alters Frame 1.
|
|
76
|
+
# The bytes are pinned eagerly by #seal! at the first invocation, so
|
|
77
|
+
# every subsequent call is a pure read of the frozen preamble — a bind
|
|
78
|
+
# can never reach the registry after the seal to alter Frame 1.
|
|
91
79
|
def encode
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
bytes = Codec::Encoder.encode(@bindings.keys).freeze
|
|
95
|
-
@encoded = bytes if @sealed
|
|
96
|
-
bytes
|
|
80
|
+
@encoded || Codec::Encoder.encode(@bindings.keys).freeze
|
|
97
81
|
end
|
|
98
82
|
|
|
99
|
-
# Mark the registry as sealed
|
|
100
|
-
#
|
|
101
|
-
#
|
|
83
|
+
# Mark the registry as sealed and pin the Frame 1 preamble bytes so
|
|
84
|
+
# registration can never alter them afterwards. Called by +Sandbox+ on
|
|
85
|
+
# the first invocation; afterwards #bind raises ArgumentError.
|
|
86
|
+
# Idempotent; returns +self+.
|
|
102
87
|
def seal!
|
|
103
88
|
@sealed = true
|
|
89
|
+
@encoded ||= Codec::Encoder.encode(@bindings.keys).freeze
|
|
104
90
|
self
|
|
105
91
|
end
|
|
106
92
|
|
|
@@ -41,17 +41,19 @@ module Kobako
|
|
|
41
41
|
# externally via +entry_payload+ rather than asking each entry to
|
|
42
42
|
# self-encode.
|
|
43
43
|
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# Unlike +Catalog::Services#encode+, which gates its memo on the
|
|
48
|
-
# seal, this one can fill eagerly and invalidate in +#register+
|
|
49
|
-
# because every mutation funnels through that single method — there is
|
|
50
|
-
# no out-of-sight child object to change the result behind its back.
|
|
44
|
+
# Before the first invocation this recomputes from the still-open
|
|
45
|
+
# table; #seal! then pins the bytes, so every later call is a pure
|
|
46
|
+
# read of the frozen memo.
|
|
51
47
|
def encode
|
|
52
|
-
|
|
48
|
+
@encoded || Codec::Encoder.encode(@entries.map { |entry| entry_payload(entry) }).freeze
|
|
49
|
+
end
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
# Freeze the Frame 3 table bytes at the first invocation so the
|
|
52
|
+
# replayed snippet set can never change afterwards. Called by +Sandbox+
|
|
53
|
+
# alongside the Service / Extension seals. Idempotent; returns +self+.
|
|
54
|
+
def seal!
|
|
55
|
+
@encoded ||= Codec::Encoder.encode(@entries.map { |entry| entry_payload(entry) }).freeze
|
|
56
|
+
self
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
# Register one preloaded snippet in either of two forms.
|
|
@@ -73,7 +75,6 @@ module Kobako
|
|
|
73
75
|
# missing keywords, wrong types, malformed +name+, or
|
|
74
76
|
# duplicate +code:+ +name+.
|
|
75
77
|
def register(code: nil, name: nil, binary: nil)
|
|
76
|
-
@encoded = nil
|
|
77
78
|
if binary
|
|
78
79
|
raise ArgumentError, "cannot combine binary: with code: / name:" if code || name
|
|
79
80
|
|