mammoth 0.6.0 → 0.7.1

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -1
  3. data/README.md +16 -0
  4. data/config/mammoth.example.yml +17 -0
  5. data/config/mammoth.schema.json +56 -1
  6. data/lib/mammoth/application.rb +58 -23
  7. data/lib/mammoth/capabilities.rb +59 -0
  8. data/lib/mammoth/cdc_source.rb +1 -1
  9. data/lib/mammoth/cli.rb +16 -24
  10. data/lib/mammoth/commands/bootstrap_command.rb +26 -0
  11. data/lib/mammoth/commands/dead_letters_command.rb +22 -0
  12. data/lib/mammoth/commands/deliver_sample_command.rb +35 -0
  13. data/lib/mammoth/commands/start_command.rb +28 -0
  14. data/lib/mammoth/commands/status_command.rb +28 -0
  15. data/lib/mammoth/commands/validate_command.rb +24 -0
  16. data/lib/mammoth/configuration.rb +66 -7
  17. data/lib/mammoth/dead_letter_commands.rb +17 -9
  18. data/lib/mammoth/dead_letter_store.rb +4 -0
  19. data/lib/mammoth/delivered_envelope_store.rb +3 -0
  20. data/lib/mammoth/destinations/adapter.rb +20 -0
  21. data/lib/mammoth/destinations/registry.rb +46 -0
  22. data/lib/mammoth/destinations/webhook_adapter.rb +29 -0
  23. data/lib/mammoth/lifecycle_hooks.rb +56 -0
  24. data/lib/mammoth/node_identity.rb +54 -0
  25. data/lib/mammoth/observability_server.rb +2 -0
  26. data/lib/mammoth/operational_state/adapter.rb +37 -0
  27. data/lib/mammoth/operational_state/registry.rb +46 -0
  28. data/lib/mammoth/operational_state/sqlite_adapter.rb +44 -0
  29. data/lib/mammoth/registry.rb +46 -0
  30. data/lib/mammoth/runtimes/adapter.rb +23 -0
  31. data/lib/mammoth/runtimes/concurrent_adapter.rb +31 -0
  32. data/lib/mammoth/runtimes/inline_adapter.rb +40 -0
  33. data/lib/mammoth/runtimes/registry.rb +46 -0
  34. data/lib/mammoth/status.rb +55 -13
  35. data/lib/mammoth/version.rb +1 -1
  36. data/lib/mammoth.rb +28 -3
  37. metadata +21 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1249f6166082904d6ecac8d74819442681eaac72ae1e23c80a2134c412e235a
4
- data.tar.gz: 292272e0696a218c3a75d970476efa3acaa71d54164da139aefa007bbd256ae5
3
+ metadata.gz: d7437cc30ed1a50ca4b414c37bc75ba374569ef3cdfafc7b88f0631d5f462790
4
+ data.tar.gz: d75b6cf2476c892915aa24ceb5169f69889a794cb923e5d1748a7c5a726129e3
5
5
  SHA512:
6
- metadata.gz: 4e401e3a8a1778ac6b63338636cc15d5c06ccbeea77e2179af1189ba8d60d1005a2281884e3cc3a09208ac4836f37d94132ab16c3f00b08edfb1c3347fe0e944
7
- data.tar.gz: fe8efe54a12f5dae1960eb4cf34c91b1eacd1d91a8609c082349b2a4e900ebda7f5160052b8f7d3bf062b09d16bb84438712f0f7d514ff23b4b16606da8b279a
6
+ metadata.gz: b38367dbafad260559c1f80558d2aeb6f452688fc1404b5652dbd8310625fbe0058eb2ac45ab0dea9dc226b19e0447efca79ff8a70556f3b10f24ba1ebd2e8c9
7
+ data.tar.gz: f7950310f899a5291889853f95b096937b51332899c8adabe7209e6bef3b5c186863cc37bc0cb331e06a51c3ff5abc15b5049995a0822f07ff51c3076b5b79b5
data/CHANGELOG.md CHANGED
@@ -1,6 +1,32 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.7.1 Unreleased
4
+
5
+ ### Added
6
+
7
+ - Added local lifecycle hooks for start, shutdown, and dead-letter replay extension points.
8
+ - Added file and hash configuration providers for CLI, tests, and future control-plane integration.
9
+ - Added reusable command objects for validate, bootstrap, start, sample delivery, and dead-letter commands.
10
+
11
+ ### Changed
12
+
13
+ - Bumped Mammoth version to `0.7.1`.
14
+
15
+ ## 0.7.0
16
+
17
+ ### Added
18
+
19
+ - Added explicit adapter registries for operational state, destinations, and delivery runtimes.
20
+ - Added the built-in SQLite operational state adapter.
21
+ - Added the built-in webhook destination adapter registration.
22
+ - Added inline and concurrent runtime adapter registration.
23
+ - Added optional node identity configuration for future control-plane integration.
24
+ - Added local capability reporting for state, destination, runtime, and relay features.
25
+ - Added a reusable status command object behind the CLI status command.
26
+
27
+ ### Changed
28
+
29
+ - Bumped Mammoth version to `0.7.0`.
4
30
 
5
31
  ## 0.6.0
6
32
 
data/README.md CHANGED
@@ -57,6 +57,9 @@ https://kanutocd.github.io/mammoth/Mammoth.html
57
57
  - public Helm chart support
58
58
  - unit and e2e test tasks
59
59
  - health and metrics endpoints
60
+ - explicit extension registries for state, destination, and runtime adapters
61
+ - node identity and local capability reporting
62
+ - lifecycle hooks, configuration providers, and reusable local command objects
60
63
 
61
64
  ## Boundary
62
65
 
@@ -66,6 +69,19 @@ Mammoth does not own pgoutput protocol parsing, value decoding, source
66
69
  normalization, ordering policy, or runtime execution. Those belong to the
67
70
  upstream CDC Ecosystem components.
68
71
 
72
+ ## Extensions
73
+
74
+ Mammoth OSS exposes small adapter registries for future extensions:
75
+
76
+ - operational state adapters
77
+ - destination adapters
78
+ - runtime adapters
79
+ - lifecycle hooks
80
+ - configuration providers
81
+ - local command objects
82
+
83
+ See [`docs/EXTENSIONS.md`](docs/EXTENSIONS.md).
84
+
69
85
  ## Configuration
70
86
 
71
87
  Mammoth configuration is YAML-backed and IDE-friendly.
@@ -34,6 +34,19 @@ mammoth:
34
34
  # production_mammoth
35
35
  name: local_mammoth
36
36
 
37
+ node:
38
+ # Optional local node identity for status output and future control-plane
39
+ # agents. Omit this section to default node_id to the host name and node_name
40
+ # to mammoth.name.
41
+ node_id: local-mammoth-1
42
+ node_name: local-mammoth-dev
43
+ fleet_id: local-dev
44
+ environment: development
45
+ labels:
46
+ region: local
47
+ metadata:
48
+ owner: platform
49
+
37
50
  postgres:
38
51
  # PostgreSQL host reachable from the Mammoth process or pod.
39
52
  #
@@ -331,6 +344,10 @@ sqlite:
331
344
  # records here. In Kubernetes, back this path with a PersistentVolumeClaim.
332
345
  path: data/mammoth.db
333
346
 
347
+ operational_state:
348
+ # Mammoth OSS 0.7.x ships the sqlite operational state adapter.
349
+ adapter: sqlite
350
+
334
351
  observability:
335
352
  # Bind host for the optional health, readiness, and metrics server.
336
353
  host: 0.0.0.0
@@ -40,6 +40,47 @@
40
40
  },
41
41
  "description": "Mammoth instance identity and operator-facing metadata."
42
42
  },
43
+ "node": {
44
+ "type": "object",
45
+ "additionalProperties": false,
46
+ "properties": {
47
+ "node_id": {
48
+ "type": "string",
49
+ "minLength": 1,
50
+ "description": "Stable node identifier used by status output and future control-plane agents."
51
+ },
52
+ "node_name": {
53
+ "type": "string",
54
+ "minLength": 1,
55
+ "description": "Human-readable node name."
56
+ },
57
+ "fleet_id": {
58
+ "type": "string",
59
+ "minLength": 1,
60
+ "description": "Optional fleet identifier for grouping Mammoth nodes."
61
+ },
62
+ "environment": {
63
+ "type": "string",
64
+ "minLength": 1,
65
+ "description": "Optional deployment environment such as development, staging, or production."
66
+ },
67
+ "labels": {
68
+ "type": "object",
69
+ "additionalProperties": {
70
+ "type": "string"
71
+ },
72
+ "description": "Operator-defined node labels."
73
+ },
74
+ "metadata": {
75
+ "type": "object",
76
+ "additionalProperties": {
77
+ "type": "string"
78
+ },
79
+ "description": "Operator-defined node metadata."
80
+ }
81
+ },
82
+ "description": "Optional local node identity. Mammoth OSS uses this for status and future extension surfaces only."
83
+ },
43
84
  "postgres": {
44
85
  "type": "object",
45
86
  "additionalProperties": false,
@@ -223,7 +264,7 @@
223
264
  "enum": [
224
265
  "webhook"
225
266
  ],
226
- "description": "Destination adapter type. Mammoth OSS 0.6.0 supports webhook destinations."
267
+ "description": "Destination adapter type. Mammoth OSS 0.7.x supports webhook destinations."
227
268
  },
228
269
  "enabled": {
229
270
  "type": "boolean",
@@ -385,6 +426,20 @@
385
426
  },
386
427
  "description": "SQLite operational store settings for checkpoints, attempts, and dead-letter records."
387
428
  },
429
+ "operational_state": {
430
+ "type": "object",
431
+ "additionalProperties": false,
432
+ "properties": {
433
+ "adapter": {
434
+ "type": "string",
435
+ "enum": [
436
+ "sqlite"
437
+ ],
438
+ "description": "Operational state adapter. Mammoth OSS 0.7.x ships the sqlite adapter."
439
+ }
440
+ },
441
+ "description": "Operational state adapter selection. SQLite remains the default OSS adapter."
442
+ },
388
443
  "logging": {
389
444
  "type": "object",
390
445
  "additionalProperties": false,
@@ -10,25 +10,62 @@ module Mammoth
10
10
  # injected CDC work source rather than owning upstream CDC source-adapter
11
11
  # lifecycle decisions.
12
12
  class Application
13
- attr_reader :config, :sqlite_store, :consumer, :delivery_worker, :checkpoint_store
13
+ attr_reader :config, :state_adapter, :consumer, :delivery_worker, :checkpoint_store, :lifecycle_hooks
14
14
 
15
15
  # @param config [Mammoth::Configuration] loaded configuration
16
16
  # @param source [#each, nil] injectable event source for tests and demos
17
17
  # @param sink [#deliver, nil] optional destination sink
18
18
  # @param sleeper [#call] retry sleep strategy
19
- def initialize(config, source: nil, sink: nil, sleeper: Kernel.method(:sleep))
19
+ # @param lifecycle_hooks [Mammoth::LifecycleHooks, Hash] local lifecycle callbacks
20
+ def initialize(config, source: nil, sink: nil, sleeper: Kernel.method(:sleep), lifecycle_hooks: LifecycleHooks.new)
20
21
  @config = config
21
- @sqlite_store = SQLiteStore.connect(config.dig("sqlite", "path")).bootstrap!
22
- @checkpoint_store = CheckpointStore.new(sqlite_store)
22
+ @lifecycle_hooks = build_lifecycle_hooks(lifecycle_hooks)
23
+ @state_adapter = build_state_adapter
24
+ @checkpoint_store = state_adapter.checkpoint_store
23
25
  @consumer = ReplicationConsumer.new(source: source || build_source, delivery_unit: delivery_unit)
24
26
  @delivery_worker = sink ? build_delivery_worker(sink: sink, sleeper: sleeper) : build_configured_delivery_worker(sleeper:)
25
27
  end
26
28
 
29
+ # @return [Mammoth::SQLiteStore] underlying SQLite store for compatibility
30
+ def sqlite_store
31
+ state_adapter.respond_to?(:sqlite_store) ? state_adapter.sqlite_store : nil
32
+ end
33
+
27
34
  # Start the application runtime and deliver consumed CDC work.
28
35
  #
29
36
  # @return [Integer] number of processed work units
30
37
  def start
31
38
  runtime = build_runtime
39
+ processed = nil
40
+
41
+ lifecycle_hooks.call(:before_start, application_context(runtime: runtime))
42
+ processed = process_consumer(runtime)
43
+ lifecycle_hooks.call(:after_start, application_context(runtime: runtime, processed: processed))
44
+ processed
45
+ ensure
46
+ lifecycle_hooks.call(:before_shutdown, application_context(runtime: runtime, processed: processed))
47
+ runtime.shutdown if runtime.respond_to?(:shutdown)
48
+ lifecycle_hooks.call(:after_shutdown, application_context(runtime: runtime, processed: processed))
49
+ end
50
+
51
+ private
52
+
53
+ def build_lifecycle_hooks(hooks)
54
+ return hooks if hooks.is_a?(LifecycleHooks)
55
+
56
+ LifecycleHooks.new(hooks)
57
+ end
58
+
59
+ def application_context(extra = {})
60
+ {
61
+ config: config,
62
+ state_adapter: state_adapter,
63
+ checkpoint_store: checkpoint_store,
64
+ delivery_worker: delivery_worker
65
+ }.merge(extra)
66
+ end
67
+
68
+ def process_consumer(runtime)
32
69
  processed = 0
33
70
  batch = [nil].compact
34
71
 
@@ -45,22 +82,17 @@ module Mammoth
45
82
  end
46
83
  end
47
84
 
48
- processed += process_batch(runtime, batch) if runtime_batching?(runtime) && batch.any?
49
- processed
50
- ensure
51
- runtime.shutdown if runtime.respond_to?(:shutdown)
85
+ processed + flush_batch(runtime, batch)
52
86
  end
53
87
 
54
- private
88
+ def flush_batch(runtime, batch)
89
+ return 0 unless runtime_batching?(runtime) && batch.any?
90
+
91
+ process_batch(runtime, batch)
92
+ end
55
93
 
56
94
  def process_work(runtime, work)
57
- if runtime
58
- runtime.process_many([work])
59
- elsif transaction_delivery?
60
- delivery_worker.deliver_transaction(work)
61
- else
62
- delivery_worker.deliver(work)
63
- end
95
+ runtime.process_many([work])
64
96
  end
65
97
 
66
98
  def process_batch(runtime, batch)
@@ -73,9 +105,8 @@ module Mammoth
73
105
  end
74
106
 
75
107
  def build_runtime
76
- return unless runtime_adapter == "concurrent"
77
-
78
- ConcurrentDeliveryRuntime.new(
108
+ Runtimes::Registry.build(
109
+ runtime_adapter,
79
110
  processor: DeliveryProcessor.new(delivery_worker:, delivery_unit: delivery_unit),
80
111
  concurrency: runtime_concurrency,
81
112
  timeout: runtime_timeout,
@@ -92,7 +123,7 @@ module Mammoth
92
123
  config,
93
124
  sink: sink,
94
125
  checkpoint_store: checkpoint_store,
95
- dead_letter_store: DeadLetterStore.new(sqlite_store),
126
+ dead_letter_store: state_adapter.dead_letter_store,
96
127
  sleeper: sleeper,
97
128
  delivery_policy: delivery_policy
98
129
  )
@@ -116,7 +147,7 @@ module Mammoth
116
147
 
117
148
  destinations.map.with_index(1) do |destination, index|
118
149
  {
119
- sink: WebhookSink.from_destination_config(destination, label: "destinations[#{index - 1}]"),
150
+ sink: Destinations::Registry.build(destination, label: "destinations[#{index - 1}]"),
120
151
  delivery_policy: destination_delivery_policy(destination)
121
152
  }
122
153
  end
@@ -132,8 +163,12 @@ module Mammoth
132
163
  }
133
164
  end
134
165
 
135
- def transaction_delivery?
136
- delivery_unit == :transaction
166
+ def build_state_adapter
167
+ OperationalState::Registry.build(operational_state_adapter, config)
168
+ end
169
+
170
+ def operational_state_adapter
171
+ config.dig("operational_state", "adapter") || "sqlite"
137
172
  end
138
173
 
139
174
  def delivery_unit
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ # Reports local Mammoth capabilities without inspecting private internals.
5
+ class Capabilities
6
+ # Built-in OSS data-plane feature capabilities.
7
+ FEATURES = %w[
8
+ checkpointing
9
+ delivery_ledger
10
+ dead_letters
11
+ replay
12
+ health
13
+ metrics
14
+ routing
15
+ fanout
16
+ ].freeze
17
+
18
+ attr_reader :config
19
+
20
+ # @param config [Mammoth::Configuration] loaded configuration
21
+ def initialize(config)
22
+ @config = config
23
+ end
24
+
25
+ # @param config [Mammoth::Configuration] loaded configuration
26
+ # @return [Hash] JSON-friendly capabilities
27
+ def self.call(config)
28
+ new(config).to_h
29
+ end
30
+
31
+ # @return [Hash] JSON-friendly capabilities
32
+ def to_h
33
+ {
34
+ operational_state: operational_state_adapter,
35
+ destinations: destination_types,
36
+ runtimes: Runtimes::Registry.names,
37
+ runtime: runtime_adapter,
38
+ features: FEATURES
39
+ }
40
+ end
41
+
42
+ private
43
+
44
+ def operational_state_adapter
45
+ config.dig("operational_state", "adapter") || "sqlite"
46
+ end
47
+
48
+ def runtime_adapter
49
+ config.dig("runtime", "adapter") || "inline"
50
+ end
51
+
52
+ def destination_types
53
+ destinations = config.data["destinations"]
54
+ return [config.dig("webhook", "type") || "webhook"] unless destinations
55
+
56
+ destinations.map { |destination| destination.fetch("type", "webhook") }.uniq.sort
57
+ end
58
+ end
59
+ end
@@ -3,7 +3,7 @@
3
3
  module Mammoth
4
4
  # Backwards-compatible internal alias for the PostgreSQL CDC source.
5
5
  #
6
- # New code should use {Mammoth::Sources::Postgres}. Mammoth v0.1.0 keeps this
6
+ # New code should use {Mammoth::Sources::Postgres}. Mammoth keeps this
7
7
  # constant so older tests or examples that referenced the transitional
8
8
  # CdcSource name continue to work while the product-facing source name moves
9
9
  # to PostgreSQL.
data/lib/mammoth/cli.rb CHANGED
@@ -85,53 +85,37 @@ module Mammoth
85
85
  end
86
86
 
87
87
  def validate
88
- load_config
89
- puts "Configuration OK: #{config_path}"
90
- 0
88
+ require_config_path!
89
+ Commands::ValidateCommand.new(config_provider).call
91
90
  end
92
91
 
93
92
  def bootstrap
94
- config = load_config
95
- store = SQLiteStore.connect(config.dig("sqlite", "path")).bootstrap!
96
- puts "SQLite database initialized"
97
- puts "Path: #{store.path}"
98
- puts "Tables: #{store.tables.join(", ")}"
99
- 0
93
+ Commands::BootstrapCommand.new(load_config).call
100
94
  end
101
95
 
102
96
  def status
103
97
  config = load_config
104
98
  store = SQLiteStore.connect(config.dig("sqlite", "path"))
105
- Status.call(config, sqlite_store: store)
106
- 0
99
+ Commands::StatusCommand.new(config, sqlite_store: store).call
107
100
  end
108
101
 
109
102
  def start
110
- config = load_config
111
- processed = Application.new(config).start
112
- puts "Processed events: #{processed}"
113
- 0
103
+ Commands::StartCommand.new(load_config).call
114
104
  end
115
105
 
116
106
  def deliver_sample
117
107
  config = load_config
118
108
  event_path = argv.fetch(2, nil)
119
109
  raise ConfigurationError, "event JSON path required\n#{USAGE}" unless event_path
120
- raise ConfigurationError, "event JSON file not found: #{event_path}" unless File.file?(event_path)
121
110
 
122
- event = JSON.parse(File.read(event_path))
123
- processed = Application.new(config, source: [event]).start
124
- puts "Processed sample events: #{processed}"
125
- 0
126
- rescue JSON::ParserError => e
127
- raise ConfigurationError, "invalid event JSON in #{event_path}: #{e.message}"
111
+ Commands::DeliverSampleCommand.new(config, event_path: event_path).call
128
112
  end
129
113
 
130
114
  # Dispatch the nested dead-letter command group.
131
115
  #
132
116
  # @return [Integer] process status code
133
117
  def dead_letters
134
- DeadLetterCommands.call(argv)
118
+ Commands::DeadLettersCommand.new(argv).call
135
119
  end
136
120
 
137
121
  def observability
@@ -143,9 +127,17 @@ module Mammoth
143
127
  end
144
128
 
145
129
  def load_config
130
+ require_config_path!
131
+
132
+ config_provider.load
133
+ end
134
+
135
+ def require_config_path!
146
136
  raise ConfigurationError, "configuration path required\n#{USAGE}" unless config_path
137
+ end
147
138
 
148
- Configuration.load(config_path)
139
+ def config_provider
140
+ Configuration::Providers::FileProvider.new(config_path)
149
141
  end
150
142
  end
151
143
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ module Commands
5
+ # Initializes the configured operational SQLite database.
6
+ class BootstrapCommand
7
+ attr_reader :config, :output
8
+
9
+ # @param config [Mammoth::Configuration] loaded configuration
10
+ # @param output [#puts] output stream
11
+ def initialize(config, output: $stdout)
12
+ @config = config
13
+ @output = output
14
+ end
15
+
16
+ # @return [Integer] process-style status code
17
+ def call
18
+ store = SQLiteStore.connect(config.dig("sqlite", "path")).bootstrap!
19
+ output.puts "SQLite database initialized"
20
+ output.puts "Path: #{store.path}"
21
+ output.puts "Tables: #{store.tables.join(", ")}"
22
+ 0
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ module Commands
5
+ # Reusable local command object for dead-letter inspection and replay.
6
+ class DeadLettersCommand
7
+ attr_reader :argv, :lifecycle_hooks
8
+
9
+ # @param argv [Array<String>] dead-letters CLI-style arguments
10
+ # @param lifecycle_hooks [Mammoth::LifecycleHooks, Hash] local lifecycle callbacks
11
+ def initialize(argv, lifecycle_hooks: LifecycleHooks.new)
12
+ @argv = argv
13
+ @lifecycle_hooks = lifecycle_hooks
14
+ end
15
+
16
+ # @return [Integer] process-style status code
17
+ def call
18
+ DeadLetterCommands.call(argv, lifecycle_hooks: lifecycle_hooks)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Mammoth
6
+ module Commands
7
+ # Delivers one JSON event file through the configured local application.
8
+ class DeliverSampleCommand
9
+ attr_reader :config, :event_path, :output, :lifecycle_hooks
10
+
11
+ # @param config [Mammoth::Configuration] loaded configuration
12
+ # @param event_path [String] event JSON path
13
+ # @param output [#puts] output stream
14
+ # @param lifecycle_hooks [Mammoth::LifecycleHooks, Hash] local lifecycle callbacks
15
+ def initialize(config, event_path:, output: $stdout, lifecycle_hooks: LifecycleHooks.new)
16
+ @config = config
17
+ @event_path = event_path
18
+ @output = output
19
+ @lifecycle_hooks = lifecycle_hooks
20
+ end
21
+
22
+ # @return [Integer] process-style status code
23
+ def call
24
+ raise ConfigurationError, "event JSON file not found: #{event_path}" unless File.file?(event_path)
25
+
26
+ event = JSON.parse(File.read(event_path))
27
+ processed = Application.new(config, source: [event], lifecycle_hooks: lifecycle_hooks).start
28
+ output.puts "Processed sample events: #{processed}"
29
+ 0
30
+ rescue JSON::ParserError => e
31
+ raise ConfigurationError, "invalid event JSON in #{event_path}: #{e.message}"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ module Commands
5
+ # Starts a Mammoth application and prints the processed work count.
6
+ class StartCommand
7
+ attr_reader :config, :source, :output, :lifecycle_hooks
8
+
9
+ # @param config [Mammoth::Configuration] loaded configuration
10
+ # @param source [#each, nil] optional injected source
11
+ # @param output [#puts] output stream
12
+ # @param lifecycle_hooks [Mammoth::LifecycleHooks, Hash] local lifecycle callbacks
13
+ def initialize(config, source: nil, output: $stdout, lifecycle_hooks: LifecycleHooks.new)
14
+ @config = config
15
+ @source = source
16
+ @output = output
17
+ @lifecycle_hooks = lifecycle_hooks
18
+ end
19
+
20
+ # @return [Integer] process-style status code
21
+ def call
22
+ processed = Application.new(config, source: source, lifecycle_hooks: lifecycle_hooks).start
23
+ output.puts "Processed events: #{processed}"
24
+ 0
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ # Local command objects shared by CLI and future control-plane agents.
5
+ module Commands
6
+ # Reusable local command object for status inspection.
7
+ class StatusCommand
8
+ attr_reader :config, :sqlite_store, :output
9
+
10
+ # @param config [Mammoth::Configuration] loaded configuration
11
+ # @param sqlite_store [Mammoth::SQLiteStore, nil] optional operational store
12
+ # @param output [#puts] output stream
13
+ def initialize(config, sqlite_store: nil, output: $stdout)
14
+ @config = config
15
+ @sqlite_store = sqlite_store
16
+ @output = output
17
+ end
18
+
19
+ # Execute the status command.
20
+ #
21
+ # @return [Integer] process-style status code
22
+ def call
23
+ Status.call(config, sqlite_store: sqlite_store, output: output)
24
+ 0
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ module Commands
5
+ # Validates a Mammoth configuration through a provider.
6
+ class ValidateCommand
7
+ attr_reader :provider, :output
8
+
9
+ # @param provider [#load] configuration provider
10
+ # @param output [#puts] output stream
11
+ def initialize(provider, output: $stdout)
12
+ @provider = provider
13
+ @output = output
14
+ end
15
+
16
+ # @return [Integer] process-style status code
17
+ def call
18
+ config = provider.load
19
+ output.puts "Configuration OK: #{config.path}"
20
+ 0
21
+ end
22
+ end
23
+ end
24
+ end