Skip to content

Commit e675248

Browse files
committed
fix: Type the portable telemetry contracts
Observer blocks, diagnostics, telemetry helpers, and message results used untyped, so Steep accepted a consumer that read the wrong field. sig/public/telemetry.rbs now publishes portable_event, metric_sample, event_observer, diagnostic_summary, and actor_diagnostics. A json_value type covers message results and actor state. A strict packaged type test checks a consumer and five invalid versions of it. With the old untyped observer block, the invalid observer consumer type-checked. authorization_context stays untyped, as on main, because it holds the application's own subject.
1 parent f31f50e commit e675248

27 files changed

Lines changed: 203 additions & 68 deletions

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
- Publish RBS types for portable events, metric samples, actor diagnostics, and
6+
event observers in `sig/public/telemetry.rbs`, and a `json_value` type for
7+
message results and actor state. Observer blocks, diagnostics, and results now
8+
type-check against these contracts instead of `untyped`.
59
- `solid_objects.activation.started` now fires before the actor's `activate`
610
hook. Before, it fired after a successful hook. The new
711
`solid_objects.activation.completed` event takes that meaning, and

‎app/models/solid_objects/message.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def dead?
4747
dead_letter.present?
4848
end
4949

50-
# @rbs () -> untyped
50+
# @rbs () -> json_value
5151
def result!
5252
if rejected?
5353
raise Rejected.new(

‎docs/observability.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ Every event has `schemaVersion`, `name` (prefixed with `solid_objects.`),
2323
`occurredAt` (UTC ISO 8601), `adapter`, `actorType`, `actorId`, `incarnation`,
2424
`revision`, `messageId`, `attempt`, `attributes`, and `metrics`.
2525
Unavailable identifiers are null; `attempt` is zero outside a message attempt.
26+
Ruby publishes the RBS types `SolidObjects::portable_event`,
27+
`SolidObjects::actor_diagnostics`, and `SolidObjects::event_observer`. JavaScript
28+
exports `InstrumentationEvent`, `ActorDiagnostics`, and `EventObserver`.
2629
An incarnation identifies a persisted actor instance, independently of its lease
2730
generation. Revisions and IDs are strings. Process-wide events have null actor
2831
identity. A message ID or revision correlates actor work where applicable.

‎lib/solid_objects/actor.rb‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ def intent_count
545545
reminder_intents.length + outbound_message_intents.length
546546
end
547547

548-
# @rbs (String) { () -> untyped } -> untyped
548+
# @rbs [Result] (String) { () -> Result } -> Result
549549
def read_projection(operation)
550550
state_before = state.to_h
551551
intents_before = intent_snapshot
@@ -565,7 +565,7 @@ def read_projection(operation)
565565
:reminder_intents,
566566
:outbound_message_intents
567567

568-
# @rbs () -> Array[Array[Hash[Symbol, untyped]]]
568+
# @rbs () -> Array[Array[Hash[Symbol, Object]]]
569569
def intent_snapshot
570570
[ effect_intents, effect_recovery_intents, commit_action_intents, reminder_intents, outbound_message_intents ].map do |intents|
571571
intents.map { |intent| intent.to_h.deep_dup }

‎lib/solid_objects/actor_snapshot.rb‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class ActorSnapshot
77
# @rbs @actor: Actor
88
# @rbs @instance_id: Integer
99
# @rbs @revision: Integer
10-
# @rbs @state_data: Hash[String, untyped]
10+
# @rbs @state_data: Hash[String, json_value]
1111
# @rbs @observable_values: Hash[String, untyped]?
1212
# @rbs @observable_value_cache: Hash[String, untyped]
1313

@@ -58,7 +58,7 @@ def build_actor
5858

5959
attr_reader :instance
6060

61-
# @rbs () -> Hash[String, untyped]
61+
# @rbs () -> Hash[String, json_value]
6262
def migrated_state
6363
state_version = instance&.state_version || actor_class.state_version
6464
ApplicationWriteGuard.call(

‎lib/solid_objects/configuration.rb‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class Configuration
4040
# @rbs @broadcast_worker_count: Integer
4141
# @rbs @reminder_scheduler_count: Integer
4242
# @rbs @connects_to: Hash[Symbol, untyped]?
43-
# @rbs @instrumentation: Proc?
43+
# @rbs @instrumentation: event_observer?
4444
# @rbs @logger: untyped
4545
# @rbs @stream_signing_secret: String?
4646
# @rbs @broadcast_adapter: Proc?
@@ -96,7 +96,6 @@ class Configuration
9696
:reminder_scheduler_count,
9797
:connects_to,
9898
:logger,
99-
:instrumentation,
10099
:stream_signing_secret,
101100
:broadcast_adapter,
102101
:wake_up_adapter,
@@ -112,6 +111,8 @@ class Configuration
112111
:administration_identity,
113112
:transmission_actor_type_resolver
114113

114+
attr_accessor :instrumentation #: event_observer?
115+
115116
# @rbs @additional_components: Array[untyped]
116117
attr_reader :additional_components
117118

‎lib/solid_objects/diagnostics.rb‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def initialize(reference)
88
@reference = reference
99
end
1010

11-
# @rbs (?limit: Integer, ?authorization_context: untyped) -> Hash[String, untyped]
11+
# @rbs (?limit: Integer, ?authorization_context: untyped) -> actor_diagnostics
1212
def summary(limit: 100, authorization_context: nil)
1313
authorize!(:inspect, authorization_context:)
1414
raise ArgumentError, "diagnostic limit must be an integer between 1 and 100" unless limit.is_a?(Integer) && limit.between?(1, 100)
@@ -42,7 +42,7 @@ def summary(limit: 100, authorization_context: nil)
4242
Serialization.readonly_copy(result)
4343
end
4444

45-
# @rbs (?authorization_context: untyped) { (Hash[String, untyped]) -> untyped } -> Proc
45+
# @rbs (?authorization_context: untyped) { (portable_event) -> void } -> Proc
4646
def observe(authorization_context: nil, &block)
4747
raise ArgumentError, "an actor observer requires a block" unless block
4848

@@ -57,7 +57,7 @@ def observe(authorization_context: nil, &block)
5757

5858
private
5959

60-
attr_reader :reference
60+
attr_reader :reference #: Reference
6161

6262
# @rbs (Symbol, ?authorization_context: untyped) -> void
6363
def authorize!(action, authorization_context: nil)
@@ -70,7 +70,7 @@ def authorize!(action, authorization_context: nil)
7070
raise Unauthorized, "actor diagnostics are not authorized" unless allowed
7171
end
7272

73-
# @rbs (Array[Time?], now: Time, limit: Integer) -> Hash[String, untyped]
73+
# @rbs (Array[Time?], now: Time, limit: Integer) -> diagnostic_summary
7474
def summarize(timestamps, now:, limit:)
7575
oldest = timestamps.compact.min
7676
{

‎lib/solid_objects/executor.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def invoke_actor(message_context)
6969
end
7070
end
7171

72-
# @rbs (Hash[String, untyped], Hash[String, untyped]) -> void
72+
# @rbs (Hash[String, json_value], Hash[String, json_value]) -> void
7373
def ensure_query_is_read_only!(state_before, state_after)
7474
return unless actor.class.definition.queries.key?(message.operation.to_sym)
7575

‎lib/solid_objects/message_reference.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def status(authorization_context: nil)
3838
status_of(SolidObjects.client.read_message(self, authorization_context:))
3939
end
4040

41-
# @rbs (?authorization_context: untyped) -> untyped
41+
# @rbs (?authorization_context: untyped) -> json_value
4242
def result(authorization_context: nil)
4343
SolidObjects.client.read_message(self, authorization_context:).result!
4444
end

‎lib/solid_objects/payload_broadcast.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def evaluated_payload(handler)
7171
# Distinguishes a block that relied on the old class-level receiver from an
7272
# ordinary typo, so the one behaviour change reports itself instead of
7373
# surfacing as an unexplained NameError.
74-
# @rbs (NameError[untyped], Actor?) -> bool
74+
# @rbs (NameError[BasicObject], Actor?) -> bool
7575
def class_level_receiver?(error, actor)
7676
error.receiver.equal?(actor) &&
7777
snapshot.actor_class.respond_to?(error.name)

0 commit comments

Comments
 (0)