event_engine 0.1.0 → 0.2.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/README.md +210 -399
  4. data/Rakefile +0 -1
  5. data/lib/event_engine/catalog_entry.rb +84 -0
  6. data/lib/event_engine/configuration.rb +9 -9
  7. data/lib/event_engine/definition_publisher.rb +22 -0
  8. data/lib/event_engine/event.rb +0 -8
  9. data/lib/event_engine/event_builder.rb +0 -8
  10. data/lib/event_engine/event_schema.rb +32 -57
  11. data/lib/event_engine/event_schema_json_loader.rb +19 -0
  12. data/lib/event_engine/handler_registry.rb +4 -4
  13. data/lib/event_engine/invalid_rules_error.rb +8 -0
  14. data/lib/event_engine/processing_rules.rb +39 -0
  15. data/lib/event_engine/processor_registry.rb +19 -0
  16. data/lib/event_engine/processor_resolver.rb +16 -0
  17. data/lib/event_engine/railtie.rb +54 -4
  18. data/lib/event_engine/rules_file.rb +14 -0
  19. data/lib/event_engine/schema_catalog_builder.rb +18 -0
  20. data/lib/event_engine/schema_registry.rb +7 -58
  21. data/lib/event_engine/unregistered_processor_error.rb +8 -0
  22. data/lib/event_engine/unroutable_event_error.rb +9 -0
  23. data/lib/event_engine/unrouted_events_error.rb +9 -0
  24. data/lib/event_engine/version.rb +1 -1
  25. data/lib/event_engine.rb +117 -123
  26. data/lib/tasks/event_engine_catalog.rake +15 -6
  27. data/lib/tasks/event_engine_rules.rake +10 -0
  28. data/the_local/agents/event_engine-develop.md +154 -0
  29. data/the_local/agents/event_engine-info.md +82 -0
  30. data/the_local/agents/event_engine-install.md +99 -0
  31. data/the_local/interface.yml +27 -0
  32. metadata +45 -48
  33. data/app/assets/config/event_engine_manifest.js +0 -1
  34. data/app/assets/stylesheets/event_engine/application.css +0 -15
  35. data/app/controllers/event_engine/application_controller.rb +0 -4
  36. data/app/helpers/event_engine/application_helper.rb +0 -4
  37. data/app/jobs/event_engine/application_job.rb +0 -4
  38. data/app/mailers/event_engine/application_mailer.rb +0 -6
  39. data/app/models/event_engine/application_record.rb +0 -5
  40. data/app/views/layouts/event_engine/application.html.erb +0 -15
  41. data/config/routes.rb +0 -2
  42. data/lib/event_engine/definition_loader.rb +0 -26
  43. data/lib/event_engine/dsl_compiler.rb +0 -50
  44. data/lib/event_engine/engine.rb +0 -56
  45. data/lib/event_engine/event_definition/inputs.rb +0 -43
  46. data/lib/event_engine/event_definition/payloads.rb +0 -47
  47. data/lib/event_engine/event_definition/schemas.rb +0 -158
  48. data/lib/event_engine/event_definition/validation.rb +0 -18
  49. data/lib/event_engine/event_definition.rb +0 -76
  50. data/lib/event_engine/event_schema_dumper.rb +0 -13
  51. data/lib/event_engine/event_schema_loader.rb +0 -37
  52. data/lib/event_engine/event_schema_merger.rb +0 -62
  53. data/lib/event_engine/event_schema_writer.rb +0 -47
  54. data/lib/event_engine/lifecycle_definition.rb +0 -86
  55. data/lib/event_engine/process_type.rb +0 -26
  56. data/lib/event_engine/reference/guide.md +0 -129
  57. data/lib/event_engine/reference.rb +0 -16
  58. data/lib/event_engine/schema_catalog.rb +0 -50
  59. data/lib/event_engine/schema_compatibility.rb +0 -50
  60. data/lib/event_engine/schema_diff.rb +0 -35
  61. data/lib/event_engine/schema_drift_guard.rb +0 -38
  62. data/lib/event_engine/subject_registry.rb +0 -40
  63. data/lib/event_engine/the_local/agents/event_engine-develop.md +0 -142
  64. data/lib/event_engine/the_local/agents/event_engine-info.md +0 -140
  65. data/lib/event_engine/the_local/agents/event_engine-install.md +0 -140
  66. data/lib/event_engine/the_local.rb +0 -55
  67. data/lib/generators/event_engine/install_generator.rb +0 -31
  68. data/lib/generators/event_engine/templates/event_schema.rb +0 -10
  69. data/lib/generators/event_engine/templates/initializer.rb +0 -4
  70. data/lib/tasks/event_engine_schema.rake +0 -82
  71. data/lib/tasks/event_engine_schema_check.rake +0 -20
  72. data/lib/tasks/event_engine_tasks.rake +0 -4
@@ -0,0 +1,9 @@
1
+ module EventEngine
2
+ class UnroutedEventsError < StandardError
3
+ def initialize(event_names)
4
+ super("no processing rule routes #{event_names.map(&:inspect).join(", ")}; " \
5
+ "declare #{event_names.one? ? "it" : "them"} in the rules file " \
6
+ "under events, packs, or default")
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module EventEngine
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/event_engine.rb CHANGED
@@ -1,56 +1,31 @@
1
1
  require_relative "event_engine/version"
2
2
 
3
- require "event_engine/engine"
3
+ require "event_engine/railtie"
4
4
  require "event_engine/configuration"
5
- require "event_engine/process_type"
6
- require "event_engine/event_definition"
7
- require "event_engine/lifecycle_definition"
5
+ require "event_engine/catalog_entry"
8
6
  require "event_engine/event_builder"
7
+ require "event_engine/definition_publisher"
9
8
  require "event_engine/handler_registry"
9
+ require "event_engine/processor_registry"
10
+ require "event_engine/unroutable_event_error"
11
+ require "event_engine/unregistered_processor_error"
12
+ require "event_engine/invalid_rules_error"
13
+ require "event_engine/unrouted_events_error"
14
+ require "event_engine/processor_resolver"
15
+ require "event_engine/processing_rules"
16
+ require "event_engine/rules_file"
10
17
  require "event_engine/event_schema"
11
18
  require "event_engine/schema_registry"
12
- require "event_engine/subject_registry"
13
19
  require "event_engine/event"
14
- require "event_engine/dsl_compiler"
15
- require "event_engine/event_schema_loader"
16
- require "event_engine/event_schema_writer"
17
- require "event_engine/event_schema_merger"
18
- require "event_engine/event_schema_dumper"
19
- require "event_engine/schema_drift_guard"
20
- require "event_engine/schema_compatibility"
21
- require "event_engine/schema_catalog"
22
- require "event_engine/railtie"
23
- require "event_engine/definition_loader"
24
- require "event_engine/the_local"
25
-
26
- # EventEngine is the schema-first core of the event pipeline.
27
- #
28
- # Events are defined via a Ruby DSL and compiled into a canonical schema file.
29
- # At boot, a helper method is installed on this module for each registered event
30
- # (e.g. +EventEngine.cow_fed(cow: cow)+); the helper validates inputs, builds an
31
- # +Event+, and dispatches it to registered handlers by level. Companion gems
32
- # (e.g. event_engine-delivery) register handlers to process the events.
33
- #
34
- # @example Define, build, and dispatch an event
35
- # EventEngine.register_handler(MyHandler, levels: :all)
36
- # EventEngine.cow_fed(cow: cow, occurred_at: Time.current)
20
+ require "event_engine/event_schema_json_loader"
21
+ require "event_engine/schema_catalog_builder"
22
+
37
23
  module EventEngine
38
- mattr_accessor :_installed_event_helpers, default: Set.new
39
24
  class << self
40
- # Returns the current configuration instance.
41
- #
42
- # @return [Configuration]
43
25
  def configuration
44
26
  @configuration ||= Configuration.new
45
27
  end
46
28
 
47
- # Yields the configuration for modification.
48
- #
49
- # @yieldparam config [Configuration] the configuration instance
50
- # @example
51
- # EventEngine.configure do |config|
52
- # config.logger = Rails.logger
53
- # end
54
29
  def configure
55
30
  yield(configuration)
56
31
  end
@@ -59,16 +34,81 @@ module EventEngine
59
34
  @handler_registry ||= HandlerRegistry.new
60
35
  end
61
36
 
62
- def subject_registry
63
- @subject_registry ||= SubjectRegistry.new
37
+ def processor_registry
38
+ @processor_registry ||= ProcessorRegistry.new
39
+ end
40
+
41
+ def schema_registry
42
+ @schema_registry ||= SchemaRegistry.new
43
+ end
44
+
45
+ attr_writer :schema_registry
46
+
47
+ def emit(event_name, inputs:, domain: nil, event_version: nil, occurred_at: nil,
48
+ metadata: nil, idempotency_key: nil, aggregate_type: nil,
49
+ aggregate_id: nil, aggregate_version: nil)
50
+ schema = schema_registry.schema(event_name, version: event_version, domain: domain)
51
+
52
+ attrs = EventBuilder.build(schema: schema, data: inputs)
53
+ attrs[:occurred_at] = occurred_at || Time.current
54
+ attrs[:metadata] = enriched_metadata(metadata)
55
+ attrs[:idempotency_key] = idempotency_key || SecureRandom.uuid
56
+ attrs[:aggregate_type] = aggregate_type
57
+ attrs[:aggregate_id] = aggregate_id
58
+ attrs[:aggregate_version] = aggregate_version
59
+ attrs[:process_type] = processing_rules.for(event_name: schema.event_name, pack: schema.domain)
60
+ attrs[:subject] = schema.subject
61
+ attrs[:domain] = schema.domain
62
+
63
+ event = Event.new(**attrs)
64
+ process(event)
65
+ dispatch(event)
66
+ end
67
+
68
+ def processing_rules
69
+ @processing_rules ||= ProcessingRules.load(configuration.rules_path)
70
+ end
71
+
72
+ attr_writer :processing_rules
73
+
74
+ def validate_rules!
75
+ missing = processing_rules.processor_names.reject { |name| processor_registry.fetch(name) }
76
+ raise InvalidRulesError, missing if missing.any?
77
+
78
+ raise UnroutedEventsError, unrouted_events if unrouted_events.any?
79
+
80
+ true
81
+ end
82
+
83
+ def unrouted_events
84
+ return [] unless processing_rules.any?
85
+
86
+ schema_registry.events.reject do |event_name|
87
+ processing_rules.for(event_name: event_name, pack: schema_registry.latest_for(event_name).domain)
88
+ end
89
+ end
90
+
91
+ def schema_sources(port = definition_port)
92
+ configured = configuration.publisher_schema_paths
93
+ return configured if configured.any?
94
+
95
+ discovered_schema_paths(port)
96
+ end
97
+
98
+ def discovered_schema_paths(port = definition_port)
99
+ return [] unless port.respond_to?(:pack_schema_paths)
100
+
101
+ port.pack_schema_paths
64
102
  end
65
103
 
66
- def define_subjects(&block)
67
- subject_registry.instance_eval(&block)
104
+ def register_definition_publisher!(port = definition_port)
105
+ return nil unless port.respond_to?(:publisher=)
106
+
107
+ port.publisher = DefinitionPublisher.new
68
108
  end
69
109
 
70
- def reset_subjects!
71
- @subject_registry = nil
110
+ def definition_port
111
+ ::EventEngine::Definition if defined?(::EventEngine::Definition)
72
112
  end
73
113
 
74
114
  def enriched_metadata(call_site_metadata)
@@ -88,107 +128,61 @@ module EventEngine
88
128
  nil
89
129
  end
90
130
 
91
- def register_handler(handler, levels:)
92
- handler_registry.register(handler, levels: levels)
131
+ def register_handler(handler, process_types:)
132
+ handler_registry.register(handler, process_types: process_types)
133
+ end
134
+
135
+ def register_processor(name, processor)
136
+ processor_registry.register(name, processor)
93
137
  end
94
138
 
95
139
  def dispatch(event)
96
140
  handler_registry.dispatch(event)
97
141
  end
98
142
 
143
+ def process(event)
144
+ resolver = ProcessorResolver.new(processing_rules)
145
+ return event unless resolver.routes?
146
+
147
+ name = resolver.resolve(event)
148
+ processor = processor_registry.fetch(name) || raise(UnregisteredProcessorError.new(name, event))
149
+
150
+ processor.call(event)
151
+ event
152
+ end
153
+
99
154
  def reset_handlers!
100
155
  handler_registry.clear!
101
156
  end
102
157
 
103
- # Loads a schema file, populates the registry, and installs helper methods.
104
- # Called automatically by the engine at Rails boot.
105
- #
106
- # @param schema_path [String, Pathname] path to the compiled schema file
107
- # @param registry [SchemaRegistry] the registry to populate
108
- # @return [EventSchema] the loaded schema
158
+ def reset_processors!
159
+ processor_registry.clear!
160
+ end
161
+
109
162
  def boot_from_schema!(schema_path:, registry:)
110
- event_schema = EventSchemaLoader.load(schema_path)
163
+ event_schema = EventSchemaJsonLoader.load(schema_path)
111
164
 
112
165
  registry.reset!
113
166
  registry.load_from_schema!(event_schema)
114
167
 
115
- install_helpers(registry: registry)
168
+ self.schema_registry = registry
116
169
 
117
170
  event_schema
118
171
  end
119
172
 
120
- # Installs singleton helper methods on the EventEngine module for each
121
- # event in the registry. Previous helpers are removed first.
122
- #
123
- # @param registry [SchemaRegistry] the loaded registry
124
- def install_helpers(registry:)
125
- _installed_event_helpers.each do |method_name|
126
- singleton_class.remove_method(method_name) if singleton_class.method_defined?(method_name)
127
- end
128
- _installed_event_helpers.clear
129
-
130
- registry.events.each do |event_name|
131
- schema = registry.schema(event_name)
132
-
133
- required = schema.required_inputs
134
- optional = schema.optional_inputs
135
-
136
- define_singleton_method(event_name) do |**args|
137
- event_version = args.delete(:event_version)
138
- occurred_at = args.delete(:occurred_at)
139
- metadata = args.delete(:metadata)
140
- idempotency_key = args.delete(:idempotency_key)
141
- aggregate_type = args.delete(:aggregate_type)
142
- aggregate_id = args.delete(:aggregate_id)
143
- aggregate_version = args.delete(:aggregate_version)
144
-
145
- input_keys = required + optional
146
- inputs = args.slice(*input_keys)
147
-
148
- missing = required - inputs.keys
149
- raise ArgumentError, "Missing required inputs: #{missing.join(', ')}" if missing.any?
150
-
151
- unknown = args.keys - input_keys
152
- raise ArgumentError, "Unknown inputs: #{unknown.join(', ')}" if unknown.any?
153
-
154
- schema = registry.schema(event_name, version: event_version)
155
- attrs = EventBuilder.build(schema: schema, data: inputs)
156
- attrs[:occurred_at] = occurred_at || Time.current
157
- attrs[:metadata] = EventEngine.enriched_metadata(metadata)
158
- attrs[:idempotency_key] = idempotency_key || SecureRandom.uuid
159
- attrs[:aggregate_type] = aggregate_type
160
- attrs[:aggregate_id] = aggregate_id
161
- attrs[:aggregate_version] = aggregate_version
162
- attrs[:process_type] = schema.process_type
163
- attrs[:subject] = schema.subject
164
- attrs[:domain] = schema.domain
165
-
166
- EventEngine.dispatch(Event.new(**attrs))
167
- end
168
-
169
- _installed_event_helpers << event_name
173
+ def register_slice!(schema_path:)
174
+ slice = EventSchemaJsonLoader.load(schema_path)
175
+
176
+ schema_registry.load_from_schema!(EventSchema.new) unless schema_registry.loaded?
177
+ slice.event_schema.schemas_by_event.each_value do |versions|
178
+ versions.each_value { |schema| schema_registry.register(schema) }
170
179
  end
171
- end
172
180
 
173
- # Compiles event definitions from source into a registry.
174
- # Used by rake tasks for schema drift detection.
175
- #
176
- # @return [SchemaRegistry]
177
- def compiled_schema_registry
178
- DefinitionLoader.ensure_loaded!
179
- definitions = EventDefinition.descendants
180
- compiled = DslCompiler.compile(definitions)
181
- registry = SchemaRegistry.new
182
- registry.load_from_schema!(compiled)
183
- registry
181
+ schema_registry
184
182
  end
185
183
 
186
- # Loads the committed schema file into a registry.
187
- # Used by rake tasks for schema drift detection.
188
- #
189
- # @return [SchemaRegistry]
190
- def file_schema_registry
191
- loaded = EventSchemaLoader.load(Rails.root.join("db/event_schema.rb"))
184
+ def file_schema_registry(schema_path: configuration.schema_path)
185
+ loaded = EventSchemaJsonLoader.load(schema_path)
192
186
  registry = SchemaRegistry.new
193
187
  registry.load_from_schema!(loaded)
194
188
  registry
@@ -1,13 +1,22 @@
1
1
  namespace :event_engine do
2
- desc "Render the event schema and subjects as a markdown catalog"
2
+ desc "Build the committed catalog from every pack, and keep the rules file in step"
3
3
  task catalog: :environment do
4
- EventEngine::DefinitionLoader.ensure_loaded!
4
+ catalog_path = Rails.root.join(EventEngine.configuration.schema_path)
5
5
 
6
- catalog = EventEngine::SchemaCatalog.new(
7
- schema_registry: EventEngine.file_schema_registry,
8
- subject_registry: EventEngine.subject_registry
6
+ EventEngine::SchemaCatalogBuilder.build(
7
+ sources: EventEngine.schema_sources,
8
+ catalog_path: catalog_path
9
9
  )
10
10
 
11
- puts catalog.to_markdown
11
+ puts "Wrote EventEngine catalog to #{catalog_path}"
12
+
13
+ rules_path = Rails.root.join(EventEngine.configuration.rules_path)
14
+
15
+ EventEngine::RulesFile.sync(
16
+ path: rules_path,
17
+ event_names: JSON.parse(File.read(catalog_path)).map { |entry| entry["event_name"] }
18
+ )
19
+
20
+ puts "Wrote EventEngine processing rules to #{rules_path}"
12
21
  end
13
22
  end
@@ -0,0 +1,10 @@
1
+ namespace :event_engine do
2
+ namespace :rules do
3
+ desc "Check that every processing rule names a registered processor"
4
+ task check: :environment do
5
+ EventEngine.validate_rules!
6
+
7
+ puts "EventEngine rules OK: #{EventEngine.processing_rules.processor_names.map(&:inspect).join(", ")}"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: event_engine-develop
3
+ description: Use PROACTIVELY for wiring an app's event processing on event_engine — registering processors and handlers, routing events to the right processor, building the committed schema catalog from event packs, and printing the catalog — MUST BE USED instead of hand-rolling event dispatch, a processor lookup table, or a script that stitches pack schemas together.
4
+ tools: Read, Write, Edit, Grep
5
+ scope: events — registering processors and publishers, building the schema catalog, and directing emitted events to the right processor
6
+ ---
7
+
8
+ This local wires a host app's event processing on event_engine, following the
9
+ steps below in order. Where a step names a decision, it asks the developer
10
+ instead of choosing.
11
+
12
+ ## What event_engine is
13
+
14
+ A Rails engine that loads a committed catalog of event schemas at boot, turns an
15
+ event pack's raw inputs into a checked event, sends that event to exactly one
16
+ **processor**, and then to every matching **handler**. Fire this local when an app
17
+ needs its events built, routed, or catalogued — anything from "our pack's events
18
+ aren't reaching the subscriber" to "add the new pack's schema to the app".
19
+
20
+ ## Interface
21
+
22
+ - `publisher_schema_paths` — configuration setting; the list of pack `schema.json`
23
+ files the catalog is built from. Defaults to empty.
24
+ - `bin/rails event_engine:schema:catalog` — reads every `publisher_schema_paths`
25
+ source and writes them into the app's committed catalog at `db/event_schema.json`.
26
+ - `EventEngine.register_processor` — registers a processor under a name, so a
27
+ routing setting can point at that name.
28
+ - `event_processors` — configuration setting; a hash of event name → processor
29
+ name. The most specific routing rule.
30
+ - `domain_processors` — configuration setting; a hash of domain → processor name.
31
+ Applies to every event in that domain.
32
+ - `default_processor` — configuration setting; the processor name used when no
33
+ event or domain rule matches.
34
+ - `EventEngine.register_handler` — registers a handler that runs after the
35
+ processor, filtered by the event's process type.
36
+ - `EventEngine.register_definition_publisher!` — points a pack's publisher port at
37
+ event_engine, so the pack's helpers produce real events. Runs automatically at
38
+ boot; call it directly only for a port other than the default.
39
+ - `bin/rails event_engine:catalog` — prints the catalogued events as markdown.
40
+
41
+ ## How to use it
42
+
43
+ All configuration below goes in the host app's
44
+ `config/initializers/event_engine.rb`, on the configuration object yielded inside
45
+ it. The `event_engine-install` local creates that file — if it is missing, hand
46
+ off to that local first and come back.
47
+
48
+ 1. Set `publisher_schema_paths` to the `schema.json` of every pack whose events
49
+ this app handles. Ask the developer which packs are in scope; do not infer the
50
+ list from the Gemfile, and do not guess a path.
51
+
52
+ ```ruby
53
+ config.publisher_schema_paths = [
54
+ MarketingEvents.schema_path,
55
+ SalesEvents.schema_path
56
+ ]
57
+ ```
58
+
59
+ Leave this unset and step 2 writes an empty catalog — no event is emittable.
60
+
61
+ 2. Build the catalog and commit it:
62
+
63
+ ```bash
64
+ bin/rails event_engine:schema:catalog
65
+ ```
66
+
67
+ It writes `db/event_schema.json` from the sources, in order, overwriting the
68
+ file. It never edits a source, and it never recomputes a schema's fingerprint —
69
+ what a pack published is what lands. Commit the result; the app reads that file
70
+ at boot, warns in `development` and `test` when it is absent, and refuses to
71
+ boot in every other environment. Re-run it whenever a pack's schema changes or
72
+ a pack is added to step 1.
73
+
74
+ 3. Register each processor by name, in the initializer, so the names exist before
75
+ any event is emitted. A processor is anything responding to `#call(event)`:
76
+
77
+ ```ruby
78
+ EventEngine.register_processor(:subscribers, MyApp::SubscriberProcessor)
79
+ ```
80
+
81
+ Registering the same name twice replaces the earlier one.
82
+
83
+ 4. Route events to those processors with the three settings. Ask the developer how
84
+ their events should route — there is no safe default, and the right answer
85
+ depends on which processors the app actually runs:
86
+
87
+ ```ruby
88
+ config.default_processor = :subscribers
89
+ config.domain_processors = { marketing: :delivery }
90
+ config.event_processors = { lead_created: :telemetry }
91
+ ```
92
+
93
+ Resolution is `event_processors[event_name]`, then
94
+ `domain_processors[domain]`, then `default_processor` — first match wins.
95
+ Every name used here must be registered in step 3.
96
+
97
+ 5. Register handlers. A handler is anything responding to `#call(event)`, and it
98
+ runs after the processor. `process_types:` is required — either `:all`, or a
99
+ list of process types that is matched against the event's own process type:
100
+
101
+ ```ruby
102
+ EventEngine.register_handler(MyApp::AuditLog, process_types: %i[durable broker])
103
+ EventEngine.register_handler(MyApp::Firehose, process_types: :all)
104
+ ```
105
+
106
+ Every handler whose filter matches runs, in registration order. An event with
107
+ no matching handler is still built and processed.
108
+
109
+ 6. Leave the publisher port alone unless the app has one of its own. At boot the
110
+ engine points the default port at event_engine, which is what makes a pack's
111
+ generated helper produce a real event instead of raising. Call it directly only
112
+ to install it on a different port object:
113
+
114
+ ```ruby
115
+ EventEngine.register_definition_publisher!(MyApp::CustomPort)
116
+ ```
117
+
118
+ It is a no-op returning `nil` when the port cannot accept a publisher — which
119
+ is also what happens when no pack is loaded.
120
+
121
+ 7. Verify the wiring:
122
+
123
+ ```bash
124
+ bin/rails event_engine:catalog
125
+ ```
126
+
127
+ It prints one markdown section per catalogued event — name, version, type,
128
+ subject, payload fields — read from the committed catalog. An event missing
129
+ here will not emit; go back to step 1.
130
+
131
+ ## Conventions
132
+
133
+ - Both processors and handlers receive the same built event, carrying
134
+ `event_name`, `event_type`, `event_version`, `process_type`, `subject`,
135
+ `domain`, `payload`, `metadata`, `occurred_at`, `idempotency_key`, and the
136
+ `aggregate_type` / `aggregate_id` / `aggregate_version` trio. Read from it; do
137
+ not mutate it.
138
+ - The process types a handler filter can name are `inline`, `background`,
139
+ `durable`, `broker`, `telemetry`, and `sourced`. A schema declares one; the
140
+ filter is matched literally, so a typo silently never fires.
141
+ - With none of the three routing settings set, no processor runs at all — events
142
+ are built and go straight to handlers. Once **any** of them is set, an event
143
+ matching none of the rules raises `EventEngine::UnroutableEventError` at emit.
144
+ Setting only `domain_processors` for one domain therefore breaks every other
145
+ domain; pair narrow rules with a `default_processor`.
146
+ - Routing at a name with no registered processor fails at emit time, not at boot.
147
+ Keep steps 3 and 4 in the same initializer so they cannot drift.
148
+ - A pack helper firing an event that is not in the committed catalog raises
149
+ `EventEngine::DefinitionPublisher::EventNotInCatalogError`. That means the
150
+ catalog is stale — rebuild it in step 2, do not work around it in app code.
151
+ - Initializer changes need an app restart; nothing is re-read at runtime.
152
+ - Out of scope for this local: adding the gem and writing the initializer itself
153
+ (`event_engine-install`), and authoring event definitions or packs — this local
154
+ consumes a pack's published `schema.json`, it never writes one.
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: event_engine-info
3
+ description: Use to learn what event_engine offers — events, domains and subjects, process types, processors and handlers, and the schema catalog the rest of the gem reads from.
4
+ tools: Read
5
+ scope: events — registering processors and publishers, building the schema catalog, and directing emitted events to the right processor
6
+ ---
7
+
8
+ This local explains event_engine and the vocabulary its world is built on. It
9
+ changes nothing and gives no steps — read it to orient, then go to the local that
10
+ owns the work.
11
+
12
+ ## What event_engine is
13
+
14
+ event_engine is a Rails engine for applications that want events as a first-class
15
+ seam rather than a pile of callbacks. Each event is described by a schema — a
16
+ name, a version, the domain it belongs to, its subject, and the shape of its
17
+ payload. Those schemas are aggregated into one committed catalog that the app
18
+ loads at boot, and the catalog is authoritative: an event the catalog doesn't
19
+ know cannot be emitted. Emitting is therefore a checked operation, and the set of
20
+ events a system can produce is reviewable in a single file.
21
+
22
+ Reach for it when several parts of an app need to react to the same domain
23
+ occurrence, when you want the contract for those occurrences written down and
24
+ diffable, or when different classes of event need to travel different roads —
25
+ some handled in the request, some queued, some pushed to a broker, some recorded
26
+ for telemetry or sourcing. An emitted event is checked against the catalog,
27
+ routed to exactly one processor for that road, and then dispatched to any
28
+ handlers subscribed to its process type. Routing is opt-in: configure nothing and
29
+ events skip processing and go straight to handlers; configure any routing at all
30
+ and an event with no matching processor is an error rather than a silent drop.
31
+
32
+ ## Interface
33
+
34
+ This local documents no commands — it is background only.
35
+
36
+ The `event_engine-install` local owns hooking the engine into a host app and
37
+ configuring it. The `event_engine-develop` local owns the working surface:
38
+ registering processors, publishers and handlers, pointing the engine at the
39
+ schema sources it should aggregate, choosing how events route, and the rake tasks
40
+ that build and render the catalog. Everything you would actually call or run
41
+ lives in one of those two.
42
+
43
+ ## How to use it
44
+
45
+ One decision: are you wiring event_engine into an app for the first time, or
46
+ building with it?
47
+
48
+ - Standing the engine up in a host app, or changing how it is configured →
49
+ `event_engine-install`.
50
+ - Adding events, registering processors or handlers, setting up routing,
51
+ publishing a pack's schema, or rebuilding the catalog → `event_engine-develop`.
52
+
53
+ If you only needed the vocabulary, you have it — stop here.
54
+
55
+ ## Conventions
56
+
57
+ - **Event** — one occurrence, identified by an `event_name` and an
58
+ `event_version`. Carries a payload plus an envelope: metadata, the time it
59
+ occurred, an idempotency key, and optional aggregate identity
60
+ (`aggregate_type`, `aggregate_id`, `aggregate_version`).
61
+ - **Domain** — the bounded area an event belongs to. Event names are looked up
62
+ within a domain, so the same name may exist in more than one.
63
+ - **Subject** — the thing an event is about, declared in its own registry with
64
+ metadata. Subjects are named vocabulary shared across events, not per-event
65
+ fields.
66
+ - **Process type** — how an event should travel: `inline`, `background`,
67
+ `durable`, `broker`, `telemetry`, or `sourced`. It comes from the schema, not
68
+ the call site.
69
+ - **Processor** — the single destination an event is routed to. Resolution is
70
+ most-specific-first: a processor bound to the event name, then one bound to the
71
+ domain, then the configured default.
72
+ - **Handler** — a subscriber that runs after processing. Handlers are registered
73
+ against process types (or all of them), and every match runs. Many handlers per
74
+ event; one processor.
75
+ - **Catalog** — the committed JSON file the app boots from, aggregated from each
76
+ publishing pack's own `schema.json`. Packs own their schemas; the catalog is
77
+ the assembled whole. If it is missing, development and test warn, other
78
+ environments refuse to boot.
79
+ - **Publisher** — the seam a pack emits through, so a pack depends on a small
80
+ publishing port rather than on the engine's internals.
81
+ - **Version** — omit one when emitting and the latest schema for that event wins;
82
+ name one to pin.