solid_objects 0.5.0 → 0.5.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f761acc1e99fba4f747cddd191fc0547ee80c7a30f2e03859f24ff22ab0dd70
4
- data.tar.gz: 4ed7f4a52926a5276fa04804ce8fbc142a0c4d9b70641b23b27dc07175956b5e
3
+ metadata.gz: 8ac17dc212792f2dfa71c01e701a9e4de27f54b6198ec6e64696b855ff82c4c8
4
+ data.tar.gz: 45bff1ddb2bbc0ff80ddfb55488fec0380d02c729e08a7f06b000b744dddb82d
5
5
  SHA512:
6
- metadata.gz: 0c63ed0b033d8028041c51c08f4d0bdb8667ab8560d469907474327bbc1536ea561ce00b062ae38f56fc72ded1e9498ecffc55d77bdc4efeaad2d6ffa8953e25
7
- data.tar.gz: 9aaa0d27ab02b23c4aa95b05cec82ea4463851f51d2ae9d778058aa93fbaa3a7b6e0c66c3ba3b52efbd3ecefa5079f23bf9d37fe12a9481b0b8b65ecc6b2847b
6
+ metadata.gz: ffb590f2e07a0de4d4ba2ab5ac1cb7d76420b742d36d656b85ecdb0fc401dbabd92b75e47df73948f7e1d42fe4a289d2f16fa8d86086c8bff336ac4e33ae89dc
7
+ data.tar.gz: 5a43e24c8358e2352de0ca1428cdc10d15bdf679c62ca1983b420fa1e739d0c975004e0e33ffb64a96fe3d09cbbcd30181db6a95a678c071fd80f85b935811cd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2 - 2026-08-09
4
+
5
+ - Read the database clock once per transaction instead of once per step, and
6
+ resolve the SQLite busy wait from configuration instead of querying the
7
+ connection for it. A synchronous call now issues 49 database queries instead
8
+ of 66, which matters most on PostgreSQL and MySQL where every query is a
9
+ network round trip.
10
+ - Apply every migration in the benchmark harness. It applied only the initial
11
+ migration, so the `state_revision` column added in 0.4.0 was missing, every
12
+ message failed at commit, and the synchronous benchmarks timed out.
13
+
14
+ ## 0.5.1 - 2026-08-07
15
+
16
+ - Restore the SQLite busy wait that a synchronous invocation suspends for its
17
+ deadline. Rails installs the busy wait as a Ruby busy handler through the
18
+ sqlite3 `timeout` configuration, which `PRAGMA busy_timeout` reports as zero
19
+ and silently replaces, so the previous save and restore left pooled
20
+ connections with no busy handler at all. Every later writer on that
21
+ connection, inside or outside Solid Objects, then failed immediately with
22
+ `SQLite3::BusyException` instead of waiting for the lock. Suspend the busy
23
+ wait only when the adapter can identify how to restore it, so an Active
24
+ Record release that stops exposing the configured timeout loosens
25
+ synchronous deadline bounds instead of stripping lock waiting from a shared
26
+ pooled connection.
27
+
28
+ - Run the doctor round-trip probe on a dedicated caller process, and accept an
29
+ explicit process registry in `SynchronousInvocation`, so the probe can no
30
+ longer stop and delete a shared application caller process, release its
31
+ activations, and unclaim its messages.
32
+ - Report doctor probe cleanup failures as a failed or warned check instead of
33
+ raising a database lock error out of the command and leaking the probe
34
+ caller process.
35
+ - Instrument component refreshes with actor identity, component name, key,
36
+ dependencies, refresh method, revision, and outcome, excluding locals.
37
+
3
38
  ## 0.5.0 - 2026-08-07
4
39
 
5
40
  - Add repeatable reactive components with signed string or integer keys and
@@ -8,12 +8,25 @@ module SolidObjects
8
8
 
9
9
  # @rbs () -> void
10
10
  def show
11
+ SolidObjects.instrument(:"component.refreshed") { |payload| refresh(payload) }
12
+ end
13
+
14
+ private
15
+
16
+ # @rbs (Hash[Symbol, untyped]) -> void
17
+ def refresh(payload)
11
18
  registration = ComponentRegistration.from_token(
12
19
  params.require(:token)
13
20
  )
21
+ payload.merge!(registration_payload(registration))
14
22
  requested_revision = requested_revision_key
15
23
  snapshot = ActorSnapshot.new(registration.reference)
16
- return head :conflict if newer_than_snapshot?(requested_revision, snapshot)
24
+ payload[:instance_id] = snapshot.instance_id
25
+ payload[:revision] = snapshot.revision
26
+ if newer_than_snapshot?(requested_revision, snapshot)
27
+ payload[:outcome] = "conflict"
28
+ return head :conflict
29
+ end
17
30
 
18
31
  authorization_context = SolidObjects
19
32
  .configuration
@@ -26,18 +39,32 @@ module SolidObjects
26
39
  authorization_context:
27
40
  ).call
28
41
  response.headers["Cache-Control"] = "private, no-store"
42
+ payload[:outcome] = "rendered"
29
43
  render html: component_frame(registration, snapshot, rendered)
30
44
  rescue Unauthorized
45
+ payload[:outcome] = "unauthorized"
31
46
  head :forbidden
32
47
  rescue UnknownComponent
48
+ payload[:outcome] = "unknown_component"
33
49
  head :not_found
34
50
  rescue ActionController::ParameterMissing,
35
51
  ArgumentError,
36
52
  InvalidComponentToken
53
+ payload[:outcome] = "invalid_token"
37
54
  head :bad_request
38
55
  end
39
56
 
40
- private
57
+ # @rbs (ComponentRegistration) -> Hash[Symbol, untyped]
58
+ def registration_payload(registration)
59
+ {
60
+ actor_type: registration.reference.actor_type,
61
+ actor_id: registration.reference.actor_id,
62
+ component_name: registration.component_name,
63
+ component_key: registration.component_key,
64
+ dependencies: registration.dependencies,
65
+ refresh_method: registration.refresh_method
66
+ }
67
+ end
41
68
 
42
69
  # @rbs () -> Array[Integer]
43
70
  def requested_revision_key
data/benchmark/support.rb CHANGED
@@ -228,7 +228,9 @@ module SolidObjectsBenchmark
228
228
  # @rbs () -> void
229
229
  def migrate
230
230
  require_relative "../db/migrate/20260805000000_create_solid_objects_tables"
231
+ require_relative "../db/migrate/20260806000000_add_state_revision_to_solid_objects_instances"
231
232
  CreateSolidObjectsTables.new.migrate(:up)
233
+ AddStateRevisionToSolidObjectsInstances.new.migrate(:up)
232
234
  end
233
235
 
234
236
  # @rbs () -> void
data/docs/benchmarks.md CHANGED
@@ -41,6 +41,13 @@ scenario used four worker threads.
41
41
  | Synchronous latency | p50 1.8 ms, p95 25.6 ms, p99 156.2 ms |
42
42
  | Activation reuse | 98.0%, four activations for 200 messages |
43
43
  | Queries for one message turn | 29 |
44
+ | Queries for one synchronous call | 49 |
45
+
46
+ A synchronous call costs far more queries than a worker turn because the caller
47
+ also registers or heartbeats its caller process, claims the activation, and
48
+ observes the result. Query count, not query time, dominates synchronous latency
49
+ on a networked database: measured locally against SQLite, database time is
50
+ roughly 5% of a call and the remaining 95% is Ruby.
44
51
 
45
52
  The difference between the SQLite development result and the MySQL adoption
46
53
  result is why Solid Objects does not publish one latency promise. Network
data/docs/correctness.md CHANGED
@@ -167,7 +167,21 @@ result. Adapter lock/query deadlines cover the durable enqueue, caller-process
167
167
  registration and heartbeat, activation coordination, and result observation.
168
168
  SQLite retries busy coordination operations only within the original call
169
169
  deadline and reports `waiting_on=database_contention` when the database cannot
170
- be inspected at timeout. If enqueue cannot commit, `SyncEnqueueTimeout` is
170
+ be inspected at timeout. To keep those retries in Ruby, the SQLite adapter
171
+ suspends the connection's busy wait for the duration of each deadline-bound
172
+ transaction and restores it afterwards. Restoration reinstalls the Ruby busy
173
+ handler Rails configures from the sqlite3 `timeout` setting, which
174
+ `PRAGMA busy_timeout` neither reports nor preserves, so a synchronous call
175
+ leaves the connection's lock waiting behaviour exactly as it found it for
176
+ later writers inside and outside Solid Objects.
177
+
178
+ The adapter suspends the busy wait only when it can identify how to restore
179
+ it. When a future Active Record release stops exposing the configured
180
+ timeout, the adapter leaves the connection untouched: synchronous deadlines
181
+ lose their tight bound and wait as long as the configured busy wait allows,
182
+ rather than stripping lock waiting from a pooled connection the rest of the
183
+ application shares. A test asserts the timeout stays discoverable so the
184
+ looser bound cannot be adopted silently. If enqueue cannot commit, `SyncEnqueueTimeout` is
171
185
  raised and no message reference exists. MySQL lock waits have one-second InnoDB
172
186
  granularity. Ruby handlers that already started are not preempted.
173
187
 
data/docs/operations.md CHANGED
@@ -15,6 +15,14 @@ so the schema check compares the required shape instead of a fixed timestamp.
15
15
  Warnings such as an all-deny neutral policy do not fail the command because a
16
16
  context-aware production policy may correctly deny the probe.
17
17
 
18
+ The round-trip probe runs on its own dedicated caller process rather than the
19
+ shared application caller process, and removes that record together with its
20
+ temporary actor when it finishes. Running the doctor inside a process that
21
+ already serves synchronous calls therefore leaves the application caller
22
+ process, its activations, and its claimed messages untouched, including when an
23
+ application call overlaps the probe. A database busy enough to block cleanup
24
+ reports a failed or warned check rather than raising out of the command.
25
+
18
26
  ## Runtime
19
27
 
20
28
  Start all configured roles:
@@ -147,10 +155,20 @@ transaction rejection, commit-action start/completion/failure, effect and
147
155
  broadcast enqueue/completion, reminder enqueue, actor destruction/expiration,
148
156
  retention pruning, process cleanup, and supervisor lifecycle.
149
157
 
158
+ `solid_objects.component.refreshed` covers every authorized component refresh
159
+ request. Its payload carries the actor identity, `component_name`,
160
+ `component_key`, declared `dependencies`, `refresh_method`, the rendered
161
+ `instance_id` and `revision`, and an `outcome` of `rendered`, `conflict`,
162
+ `unauthorized`, `unknown_component`, or `invalid_token`. Use it to watch
163
+ refresh rate per key, authorization denials, superseded requests, and render
164
+ duration. A rejected token reports only the outcome, since no signed identity
165
+ was recovered.
166
+
150
167
  Payloads contain stable runtime identifiers, actor identity, sequence,
151
168
  attempts, ownership generations, and safe exception summaries where relevant.
152
- Arguments, actor state, results, and outbox payloads are excluded. The bundled
153
- log subscriber turns the same notifications into structured logger hashes.
169
+ Arguments, component locals, actor state, results, and outbox payloads are
170
+ excluded. The bundled log subscriber turns the same notifications into
171
+ structured logger hashes.
154
172
 
155
173
  ## Retention and backups
156
174
 
@@ -4,6 +4,9 @@ require "time"
4
4
 
5
5
  module SolidObjects
6
6
  class DatabaseAdapter
7
+ TRANSACTION_CLOCK = :solid_objects_transaction_clock
8
+ TRANSACTION_CLOCK_SCOPE = :solid_objects_transaction_clock_scope
9
+
7
10
  class << self
8
11
  # @rbs (untyped) -> DatabaseAdapter
9
12
  def for(connection)
@@ -46,10 +49,9 @@ module SolidObjects
46
49
 
47
50
  # @rbs () -> Time
48
51
  def database_now
49
- value = with_connection do |connection|
50
- connection.select_value("SELECT #{current_time_expression}")
51
- end
52
- value.is_a?(Time) ? value.utc : Time.parse("#{value} UTC").utc
52
+ return read_database_now unless ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE]
53
+
54
+ ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK] ||= read_database_now
53
55
  end
54
56
 
55
57
  # @rbs () { () -> untyped } -> untyped
@@ -70,7 +72,7 @@ module SolidObjects
70
72
  with_transaction_deadline(connection) do
71
73
  connection.transaction(requires_new: true) do
72
74
  configure_transaction_deadline(connection)
73
- block.call
75
+ with_transaction_clock { block.call }
74
76
  end
75
77
  end
76
78
  end
@@ -91,6 +93,27 @@ module SolidObjects
91
93
 
92
94
  attr_reader :connection_pool, :fixed_connection
93
95
 
96
+ # @rbs () { () -> untyped } -> untyped
97
+ def with_transaction_clock
98
+ return yield if ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE]
99
+
100
+ ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE] = true
101
+ begin
102
+ yield
103
+ ensure
104
+ ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE] = false
105
+ ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK] = nil
106
+ end
107
+ end
108
+
109
+ # @rbs () -> Time
110
+ def read_database_now
111
+ value = with_connection do |connection|
112
+ connection.select_value("SELECT #{current_time_expression}")
113
+ end
114
+ value.is_a?(Time) ? value.utc : Time.parse("#{value} UTC").utc
115
+ end
116
+
94
117
  # @rbs (untyped) { () -> untyped } -> untyped
95
118
  def with_transaction_deadline(_connection)
96
119
  yield
@@ -67,11 +67,49 @@ module SolidObjects
67
67
  def with_transaction_deadline(connection)
68
68
  return yield unless SyncDeadline.active?
69
69
 
70
- previous_timeout = connection.select_value("PRAGMA busy_timeout").to_i
71
- connection.execute("PRAGMA busy_timeout = 0")
72
- yield
73
- ensure
74
- connection.execute("PRAGMA busy_timeout = #{previous_timeout}") if previous_timeout
70
+ busy_wait = restorable_busy_wait(connection)
71
+ return yield unless busy_wait
72
+
73
+ begin
74
+ connection.execute("PRAGMA busy_timeout = 0")
75
+ yield
76
+ ensure
77
+ restore_busy_wait(connection, busy_wait)
78
+ end
79
+ end
80
+
81
+ # @rbs (untyped) -> Hash[Symbol, untyped]?
82
+ def restorable_busy_wait(connection)
83
+ handler_timeout = configured_busy_handler_timeout(connection)
84
+ return { handler_timeout: } if handler_timeout
85
+
86
+ pragma_timeout = connection.select_value("PRAGMA busy_timeout").to_i
87
+ return nil unless pragma_timeout.positive?
88
+
89
+ { pragma_timeout: }
90
+ end
91
+
92
+ # @rbs (untyped, Hash[Symbol, untyped]) -> void
93
+ def restore_busy_wait(connection, busy_wait)
94
+ handler_timeout = busy_wait[:handler_timeout]
95
+ if handler_timeout
96
+ connection.raw_connection.busy_handler_timeout = handler_timeout
97
+ return
98
+ end
99
+
100
+ connection.execute("PRAGMA busy_timeout = #{busy_wait.fetch(:pragma_timeout)}")
101
+ end
102
+
103
+ # @rbs (untyped) -> Integer?
104
+ def configured_busy_handler_timeout(connection)
105
+ return nil unless connection.respond_to?(:raw_connection)
106
+ return nil unless connection.raw_connection.respond_to?(:busy_handler_timeout=)
107
+
108
+ pool = connection.respond_to?(:pool) ? connection.pool : nil
109
+ return nil unless pool.respond_to?(:db_config)
110
+
111
+ timeout = pool.db_config.configuration_hash[:timeout]
112
+ timeout&.to_i
75
113
  end
76
114
 
77
115
  # @rbs (Exception) -> bool
@@ -215,25 +215,63 @@ module SolidObjects
215
215
  # @rbs () -> Check
216
216
  def check_sync_round_trip
217
217
  actor_id = SecureRandom.uuid
218
+ probe_registry = ProcessRegistry.new
219
+ check = run_sync_probe(actor_id, probe_registry)
220
+ leftovers = remove_probe_records(actor_id:, probe_registry:)
221
+ return check if leftovers.empty? || check.failed?
222
+
223
+ warn_check(
224
+ :sync_round_trip,
225
+ "#{check.message}; could not remove the #{leftovers.join(" and ")}"
226
+ )
227
+ end
228
+
229
+ # @rbs (String, ProcessRegistry) -> Check
230
+ def run_sync_probe(actor_id, probe_registry)
231
+ probe_registry.register(kind: "caller", metadata: { execution: "doctor" })
218
232
  value = SecureRandom.hex(8)
219
- process_registry = SolidObjects.caller_process.process_registry
220
- reference = ProbeActor.ref(actor_id)
221
233
  message_reference = Mailbox.new.enqueue(
222
- reference,
234
+ ProbeActor.ref(actor_id),
223
235
  :ping,
224
236
  { value: },
225
237
  kind: "sync"
226
238
  )
227
- result = SynchronousInvocation.new.call(message_reference, timeout: 5.seconds)
239
+ result = SynchronousInvocation
240
+ .new(process_registry: probe_registry)
241
+ .call(message_reference, timeout: 5.seconds)
228
242
  raise Error, "unexpected round-trip result" unless result == value
229
243
 
230
244
  pass(:sync_round_trip, "durable synchronous actor call completed without a worker")
231
245
  rescue => error
232
246
  fail_check(:sync_round_trip, "#{error.class}: #{error.message}")
233
- ensure
234
- Instance.where(actor_type: ProbeActor.actor_type, actor_id:).delete_all if actor_id
235
- process_registry&.stop
236
- process_registry&.process_record&.delete
247
+ end
248
+
249
+ # @rbs (actor_id: String, probe_registry: ProcessRegistry) -> Array[String]
250
+ def remove_probe_records(actor_id:, probe_registry:)
251
+ leftovers = []
252
+ leftovers << "probe actor" unless delete_probe_actor(actor_id)
253
+ leftovers << "probe caller process" unless delete_probe_caller_process(probe_registry)
254
+ leftovers
255
+ end
256
+
257
+ # @rbs (String) -> bool
258
+ def delete_probe_actor(actor_id)
259
+ Instance.where(actor_type: ProbeActor.actor_type, actor_id:).delete_all
260
+ true
261
+ rescue
262
+ false
263
+ end
264
+
265
+ # @rbs (ProcessRegistry) -> bool
266
+ def delete_probe_caller_process(probe_registry)
267
+ process_record = probe_registry.process_record
268
+ return true unless process_record
269
+
270
+ probe_registry.stop
271
+ process_record.delete
272
+ true
273
+ rescue
274
+ false
237
275
  end
238
276
 
239
277
  # @rbs (Check, Check) -> bool
@@ -6,6 +6,13 @@ require "solid_objects/sync_diagnostics"
6
6
 
7
7
  module SolidObjects
8
8
  class SynchronousInvocation
9
+ # @rbs @dedicated_process_registry: ProcessRegistry?
10
+
11
+ # @rbs (?process_registry: ProcessRegistry?) -> void
12
+ def initialize(process_registry: nil)
13
+ @dedicated_process_registry = process_registry
14
+ end
15
+
9
16
  # @rbs (MessageReference, timeout: Numeric) -> untyped
10
17
  def call(message_reference, timeout:)
11
18
  return call_before_deadline(message_reference, timeout:) if SyncDeadline.active?
@@ -92,9 +99,17 @@ module SolidObjects
92
99
  )
93
100
  end
94
101
 
102
+ # @rbs () -> ProcessRegistry
103
+ def process_registry
104
+ dedicated_registry = @dedicated_process_registry
105
+ return SolidObjects.caller_process.process_registry unless dedicated_registry
106
+
107
+ dedicated_registry.tap(&:heartbeat)
108
+ end
109
+
95
110
  # @rbs (Message, deadline: Float) -> Integer
96
111
  def assist(message, deadline:)
97
- process_registry = SolidObjects.caller_process.process_registry
112
+ process_registry = self.process_registry
98
113
  activation = ActivationManager
99
114
  .new(owner_id: process_registry.process_record.id)
100
115
  .claim(instance_id: message.instance_id)
@@ -1,5 +1,5 @@
1
1
  # rbs_inline: enabled
2
2
 
3
3
  module SolidObjects
4
- VERSION = "0.5.0"
4
+ VERSION = "0.5.2"
5
5
  end
@@ -7,6 +7,12 @@ module SolidObjects
7
7
 
8
8
  private
9
9
 
10
+ # @rbs (Hash[Symbol, untyped]) -> void
11
+ def refresh: (Hash[Symbol, untyped]) -> void
12
+
13
+ # @rbs (ComponentRegistration) -> Hash[Symbol, untyped]
14
+ def registration_payload: (ComponentRegistration) -> Hash[Symbol, untyped]
15
+
10
16
  # @rbs () -> Array[Integer]
11
17
  def requested_revision_key: () -> Array[Integer]
12
18
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  module SolidObjects
4
4
  class DatabaseAdapter
5
+ TRANSACTION_CLOCK: ::Symbol
6
+
7
+ TRANSACTION_CLOCK_SCOPE: ::Symbol
8
+
5
9
  # @rbs (untyped) -> DatabaseAdapter
6
10
  def self.for: (untyped) -> DatabaseAdapter
7
11
 
@@ -42,6 +46,12 @@ module SolidObjects
42
46
 
43
47
  attr_reader fixed_connection: untyped
44
48
 
49
+ # @rbs () { () -> untyped } -> untyped
50
+ def with_transaction_clock: () { () -> untyped } -> untyped
51
+
52
+ # @rbs () -> Time
53
+ def read_database_now: () -> Time
54
+
45
55
  # @rbs (untyped) { () -> untyped } -> untyped
46
56
  def with_transaction_deadline: (untyped) { () -> untyped } -> untyped
47
57
 
@@ -26,6 +26,15 @@ module SolidObjects
26
26
  # @rbs (untyped) { () -> untyped } -> untyped
27
27
  def with_transaction_deadline: (untyped) { () -> untyped } -> untyped
28
28
 
29
+ # @rbs (untyped) -> Hash[Symbol, untyped]?
30
+ def restorable_busy_wait: (untyped) -> Hash[Symbol, untyped]?
31
+
32
+ # @rbs (untyped, Hash[Symbol, untyped]) -> void
33
+ def restore_busy_wait: (untyped, Hash[Symbol, untyped]) -> void
34
+
35
+ # @rbs (untyped) -> Integer?
36
+ def configured_busy_handler_timeout: (untyped) -> Integer?
37
+
29
38
  # @rbs (Exception) -> bool
30
39
  def deadline_error?: (Exception) -> bool
31
40
 
@@ -78,6 +78,18 @@ module SolidObjects
78
78
  # @rbs () -> Check
79
79
  def check_sync_round_trip: () -> Check
80
80
 
81
+ # @rbs (String, ProcessRegistry) -> Check
82
+ def run_sync_probe: (String, ProcessRegistry) -> Check
83
+
84
+ # @rbs (actor_id: String, probe_registry: ProcessRegistry) -> Array[String]
85
+ def remove_probe_records: (actor_id: String, probe_registry: ProcessRegistry) -> Array[String]
86
+
87
+ # @rbs (String) -> bool
88
+ def delete_probe_actor: (String) -> bool
89
+
90
+ # @rbs (ProcessRegistry) -> bool
91
+ def delete_probe_caller_process: (ProcessRegistry) -> bool
92
+
81
93
  # @rbs (Check, Check) -> bool
82
94
  def ready_for_round_trip?: (Check, Check) -> bool
83
95
 
@@ -2,6 +2,11 @@
2
2
 
3
3
  module SolidObjects
4
4
  class SynchronousInvocation
5
+ @dedicated_process_registry: ProcessRegistry?
6
+
7
+ # @rbs (?process_registry: ProcessRegistry?) -> void
8
+ def initialize: (?process_registry: ProcessRegistry?) -> void
9
+
5
10
  # @rbs (MessageReference, timeout: Numeric) -> untyped
6
11
  def call: (MessageReference, timeout: Numeric) -> untyped
7
12
 
@@ -22,6 +27,9 @@ module SolidObjects
22
27
  # @rbs (Message) -> bot
23
28
  def raise_rejection: (Message) -> bot
24
29
 
30
+ # @rbs () -> ProcessRegistry
31
+ def process_registry: () -> ProcessRegistry
32
+
25
33
  # @rbs (Message, deadline: Float) -> Integer
26
34
  def assist: (Message, deadline: Float) -> Integer
27
35
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_objects
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Carlson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-07 00:00:00.000000000 Z
11
+ date: 2026-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actioncable