igniter-ledger 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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +481 -0
  3. data/examples/intelligent_ledger/availability_boundary_ledger.rb +1190 -0
  4. data/examples/intelligent_ledger/availability_deriver.rb +150 -0
  5. data/examples/intelligent_ledger/availability_ledger.rb +197 -0
  6. data/examples/intelligent_ledger/ledger_boundary.rb +180 -0
  7. data/examples/store_poc.rb +45 -0
  8. data/exe/igniter-ledger-server +111 -0
  9. data/exe/igniter-store-server +6 -0
  10. data/ext/igniter_store_native/Cargo.toml +28 -0
  11. data/ext/igniter_store_native/extconf.rb +6 -0
  12. data/ext/igniter_store_native/src/fact.rs +303 -0
  13. data/ext/igniter_store_native/src/fact_log.rs +180 -0
  14. data/ext/igniter_store_native/src/file_backend.rs +91 -0
  15. data/ext/igniter_store_native/src/lib.rs +55 -0
  16. data/lib/igniter/ledger.rb +7 -0
  17. data/lib/igniter/store/access_path.rb +84 -0
  18. data/lib/igniter/store/change_event.rb +65 -0
  19. data/lib/igniter/store/changefeed_buffer.rb +585 -0
  20. data/lib/igniter/store/codecs.rb +253 -0
  21. data/lib/igniter/store/contractable_receipt_sink.rb +172 -0
  22. data/lib/igniter/store/fact.rb +121 -0
  23. data/lib/igniter/store/fact_log.rb +103 -0
  24. data/lib/igniter/store/file_backend.rb +269 -0
  25. data/lib/igniter/store/http_adapter.rb +413 -0
  26. data/lib/igniter/store/igniter_store.rb +838 -0
  27. data/lib/igniter/store/mcp_adapter.rb +403 -0
  28. data/lib/igniter/store/native.rb +80 -0
  29. data/lib/igniter/store/network_backend.rb +159 -0
  30. data/lib/igniter/store/protocol/handlers/access_path_handler.rb +38 -0
  31. data/lib/igniter/store/protocol/handlers/command_handler.rb +59 -0
  32. data/lib/igniter/store/protocol/handlers/derivation_handler.rb +27 -0
  33. data/lib/igniter/store/protocol/handlers/effect_handler.rb +65 -0
  34. data/lib/igniter/store/protocol/handlers/history_handler.rb +24 -0
  35. data/lib/igniter/store/protocol/handlers/projection_handler.rb +41 -0
  36. data/lib/igniter/store/protocol/handlers/relation_handler.rb +43 -0
  37. data/lib/igniter/store/protocol/handlers/store_handler.rb +24 -0
  38. data/lib/igniter/store/protocol/handlers/subscription_handler.rb +24 -0
  39. data/lib/igniter/store/protocol/interpreter.rb +447 -0
  40. data/lib/igniter/store/protocol/receipt.rb +96 -0
  41. data/lib/igniter/store/protocol/sync_profile.rb +53 -0
  42. data/lib/igniter/store/protocol/wire_envelope.rb +214 -0
  43. data/lib/igniter/store/protocol.rb +27 -0
  44. data/lib/igniter/store/read_cache.rb +163 -0
  45. data/lib/igniter/store/schema_graph.rb +248 -0
  46. data/lib/igniter/store/segmented_file_backend.rb +699 -0
  47. data/lib/igniter/store/server_config.rb +55 -0
  48. data/lib/igniter/store/server_logger.rb +64 -0
  49. data/lib/igniter/store/server_metrics.rb +222 -0
  50. data/lib/igniter/store/store_server.rb +597 -0
  51. data/lib/igniter/store/subscription_registry.rb +73 -0
  52. data/lib/igniter/store/tbackend_adapter_descriptor.rb +307 -0
  53. data/lib/igniter/store/tcp_adapter.rb +127 -0
  54. data/lib/igniter/store/wire_protocol.rb +42 -0
  55. data/lib/igniter/store.rb +64 -0
  56. data/lib/igniter-ledger.rb +4 -0
  57. data/lib/igniter-store.rb +5 -0
  58. metadata +212 -0
@@ -0,0 +1,307 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+
6
+ module Igniter
7
+ module Store
8
+ class TBackendAdapterDescriptor
9
+ KIND = "ledger_tbackend_adapter_descriptor"
10
+ DIAGNOSTICS_KIND = "ledger_tbackend_adapter_descriptor_diagnostics"
11
+ ADAPTER_KIND = "ledger_open_protocol"
12
+ ADAPTER_VERSION = "0.1.0"
13
+ CONTRACT_VERSION = "tbackend.v0"
14
+ PROTOCOL = "igniter_store"
15
+ EVIDENCE_MODE = "receipt_required"
16
+ DEFAULT_ADAPTER_REF = "adapter:ledger-open-protocol/package-descriptor-v0"
17
+
18
+ READ_OPS = %w[read query fact_ref].freeze
19
+ APPEND_OPS = %w[write write_fact append].freeze
20
+ REPLAY_OPS = %w[replay sync_hub_profile].freeze
21
+ SNAPSHOT_OPS = %w[metadata_snapshot descriptor_snapshot sync_hub_profile].freeze
22
+ TBACKEND_OPS = {
23
+ "read" => READ_OPS,
24
+ "append" => APPEND_OPS,
25
+ "replay" => REPLAY_OPS,
26
+ "snapshot" => SNAPSHOT_OPS,
27
+ "compact" => %w[compact],
28
+ "subscribe" => %w[subscribe]
29
+ }.freeze
30
+
31
+ CURSOR_POLICY = {
32
+ ordered: "forward",
33
+ cursor_kinds: ["timestamp"],
34
+ truncation_reported: true,
35
+ tie_breaker: "timestamp_then_fact_id_required"
36
+ }.freeze
37
+
38
+ NON_AUTHORIZATION = {
39
+ runtime_binding: false,
40
+ ledger_reads: false,
41
+ ledger_writes: false,
42
+ ledger_append: false,
43
+ ledger_replay: false,
44
+ ledger_compact: false,
45
+ ledger_subscribe: false,
46
+ migration_execution: false
47
+ }.freeze
48
+
49
+ attr_reader :metadata_snapshot,
50
+ :descriptor_snapshot,
51
+ :payload
52
+
53
+ def self.build(metadata_snapshot:, descriptor_snapshot:, schema_fingerprint:, adapter_ref: nil,
54
+ ledger_protocol_ops: nil)
55
+ new(
56
+ metadata_snapshot: metadata_snapshot,
57
+ descriptor_snapshot: descriptor_snapshot,
58
+ schema_fingerprint: schema_fingerprint,
59
+ adapter_ref: adapter_ref,
60
+ ledger_protocol_ops: ledger_protocol_ops
61
+ )
62
+ end
63
+
64
+ def initialize(metadata_snapshot:, descriptor_snapshot:, schema_fingerprint:, adapter_ref: nil,
65
+ ledger_protocol_ops: nil)
66
+ @metadata_snapshot = normalize_hash(metadata_snapshot, :metadata_snapshot)
67
+ @descriptor_snapshot = normalize_hash(descriptor_snapshot, :descriptor_snapshot)
68
+ @payload = build_payload(
69
+ schema_fingerprint: require_value(:schema_fingerprint, schema_fingerprint),
70
+ adapter_ref: adapter_ref || DEFAULT_ADAPTER_REF,
71
+ ledger_protocol_ops: normalize_ops(
72
+ ledger_protocol_ops || metadata_snapshot_value(:ledger_protocol_ops) || metadata_snapshot_value(:protocol_ops)
73
+ )
74
+ )
75
+ freeze
76
+ end
77
+
78
+ def descriptor_hash
79
+ payload.fetch(:descriptor_hash)
80
+ end
81
+
82
+ def descriptor_registry_hash
83
+ payload.fetch(:descriptor_registry_hash)
84
+ end
85
+
86
+ def ledger_protocol_ops
87
+ payload.fetch(:ledger_protocol_ops)
88
+ end
89
+
90
+ def supported_tbackend_ops
91
+ payload.fetch(:supported_tbackend_ops)
92
+ end
93
+
94
+ def hook_methods
95
+ payload.fetch(:hook_methods)
96
+ end
97
+
98
+ def capabilities
99
+ payload.fetch(:capabilities)
100
+ end
101
+
102
+ def history_axes
103
+ payload.fetch(:history_axes)
104
+ end
105
+
106
+ def cursor_policy
107
+ payload.fetch(:cursor_policy)
108
+ end
109
+
110
+ def diagnostics(requirement = {})
111
+ requirement = normalize_hash(requirement, :requirement)
112
+ missing_ops = missing(:required_ops, :supported_tbackend_ops, requirement)
113
+ missing_hook_methods = missing(:required_hook_methods, :hook_methods, requirement)
114
+ missing_capabilities = missing(:required_capabilities, :capabilities, requirement)
115
+ missing_axes = missing(:history_axes, :history_axes, requirement)
116
+ schema_fingerprint_match = schema_fingerprint_match?(requirement)
117
+ blocked = missing_ops.any? ||
118
+ missing_hook_methods.any? ||
119
+ missing_capabilities.any? ||
120
+ missing_axes.any? ||
121
+ !schema_fingerprint_match
122
+
123
+ deep_freeze(
124
+ kind: DIAGNOSTICS_KIND,
125
+ status: blocked ? "blocked" : "ok",
126
+ missing_ops: missing_ops,
127
+ missing_hook_methods: missing_hook_methods,
128
+ missing_capabilities: missing_capabilities,
129
+ missing_axes: missing_axes,
130
+ schema_fingerprint_match: schema_fingerprint_match,
131
+ descriptor_hash: descriptor_hash,
132
+ descriptor_registry_hash: descriptor_registry_hash
133
+ )
134
+ end
135
+
136
+ def to_h
137
+ payload
138
+ end
139
+
140
+ private
141
+
142
+ def build_payload(schema_fingerprint:, adapter_ref:, ledger_protocol_ops:)
143
+ supported_tbackend_ops = derive_supported_tbackend_ops(ledger_protocol_ops)
144
+ hook_methods = []
145
+ capabilities = []
146
+ history_axes = []
147
+
148
+ if history_read_supported?(supported_tbackend_ops)
149
+ hook_methods << "read_as_of"
150
+ capabilities << "history_read"
151
+ history_axes << "valid_time"
152
+ end
153
+
154
+ if bihistory_supported?
155
+ hook_methods << "bihistory_at"
156
+ capabilities << "bihistory_read"
157
+ history_axes << "transaction_time"
158
+ end
159
+
160
+ descriptor_registry_hash = self.class.canonical_hash(
161
+ metadata_snapshot: metadata_snapshot,
162
+ descriptor_snapshot: descriptor_snapshot
163
+ )
164
+
165
+ payload_without_hash = {
166
+ kind: KIND,
167
+ adapter_kind: ADAPTER_KIND,
168
+ adapter_ref: adapter_ref,
169
+ adapter_version: ADAPTER_VERSION,
170
+ contract_version: CONTRACT_VERSION,
171
+ protocol: PROTOCOL,
172
+ protocol_schema_version: protocol_schema_version,
173
+ ledger_protocol_ops: ledger_protocol_ops,
174
+ supported_tbackend_ops: supported_tbackend_ops,
175
+ hook_methods: hook_methods,
176
+ capabilities: capabilities,
177
+ history_axes: history_axes,
178
+ cursor_policy: CURSOR_POLICY,
179
+ schema_fingerprint: schema_fingerprint,
180
+ descriptor_registry_hash: descriptor_registry_hash,
181
+ evidence_mode: EVIDENCE_MODE,
182
+ source_snapshots: {
183
+ metadata_snapshot_present: true,
184
+ descriptor_snapshot_present: true
185
+ },
186
+ non_authorization: NON_AUTHORIZATION
187
+ }
188
+
189
+ deep_freeze(payload_without_hash.merge(
190
+ descriptor_hash: self.class.canonical_hash(payload_without_hash)
191
+ ))
192
+ end
193
+
194
+ def protocol_schema_version
195
+ metadata_snapshot[:schema_version] || descriptor_snapshot[:schema_version]
196
+ end
197
+
198
+ def derive_supported_tbackend_ops(ledger_protocol_ops)
199
+ TBACKEND_OPS.filter_map do |tbackend_op, ledger_ops|
200
+ tbackend_op if (ledger_protocol_ops & ledger_ops).any?
201
+ end
202
+ end
203
+
204
+ def history_read_supported?(supported_tbackend_ops)
205
+ supported_tbackend_ops.include?("read") && store_descriptors.any? do |descriptor|
206
+ descriptor_capabilities(descriptor).include?("as_of_read")
207
+ end
208
+ end
209
+
210
+ def bihistory_supported?
211
+ history_descriptors.any?
212
+ end
213
+
214
+ def store_descriptors
215
+ Array(metadata_snapshot[:stores]) + Array(descriptor_snapshot[:stores])
216
+ end
217
+
218
+ def history_descriptors
219
+ Array(metadata_snapshot[:histories]) + Array(descriptor_snapshot[:histories])
220
+ end
221
+
222
+ def descriptor_capabilities(descriptor)
223
+ Array(descriptor[:capabilities]).map(&:to_s)
224
+ end
225
+
226
+ def missing(requirement_key, descriptor_key, requirement)
227
+ required = Array(requirement[requirement_key]).map(&:to_s)
228
+ actual = Array(payload.fetch(descriptor_key)).map(&:to_s)
229
+ required - actual
230
+ end
231
+
232
+ def schema_fingerprint_match?(requirement)
233
+ required = requirement[:schema_fingerprint]
234
+ required.nil? || required == payload.fetch(:schema_fingerprint)
235
+ end
236
+
237
+ def metadata_snapshot_value(key)
238
+ metadata_snapshot[key]
239
+ end
240
+
241
+ def normalize_ops(value)
242
+ Array(value).map(&:to_s).uniq.freeze
243
+ end
244
+
245
+ def require_value(name, value)
246
+ raise ArgumentError, "#{name} is required" if value.nil?
247
+
248
+ value
249
+ end
250
+
251
+ def normalize_hash(value, name)
252
+ raise ArgumentError, "#{name} must be a hash" unless value.respond_to?(:to_h)
253
+
254
+ value.to_h.each_with_object({}) do |(key, entry), hash|
255
+ normalized_key = key.respond_to?(:to_sym) ? key.to_sym : key
256
+ hash[normalized_key] = normalize_value(entry)
257
+ end
258
+ end
259
+
260
+ def normalize_value(value)
261
+ case value
262
+ when Hash
263
+ normalize_hash(value, :value)
264
+ when Array
265
+ value.map { |entry| normalize_value(entry) }
266
+ else
267
+ value
268
+ end
269
+ end
270
+
271
+ def deep_freeze(value)
272
+ case value
273
+ when Array
274
+ value.map { |entry| deep_freeze(entry) }.freeze
275
+ when Hash
276
+ value.transform_values { |entry| deep_freeze(entry) }.freeze
277
+ else
278
+ value.freeze
279
+ end
280
+ end
281
+
282
+ class << self
283
+ def canonical_hash(value)
284
+ "sha256:#{Digest::SHA256.hexdigest(JSON.generate(canonical_value(value)))}"
285
+ end
286
+
287
+ private
288
+
289
+ def canonical_value(value)
290
+ case value
291
+ when Hash
292
+ value.keys.map(&:to_s).sort.each_with_object({}) do |key, hash|
293
+ original_key = value.key?(key.to_sym) ? key.to_sym : key
294
+ hash[key] = canonical_value(value.fetch(original_key))
295
+ end
296
+ when Array
297
+ value.map { |entry| canonical_value(entry) }
298
+ when Symbol
299
+ value.to_s
300
+ else
301
+ value
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "json"
5
+ require_relative "wire_protocol"
6
+
7
+ module Igniter
8
+ module Store
9
+ # TCP transport adapter for the Igniter Store Open Protocol.
10
+ #
11
+ # Exposes Protocol::Interpreter over a framed TCP (or Unix socket) connection
12
+ # using the same WireProtocol CRC32 framing as the legacy StoreServer path.
13
+ # Each request frame carries a WireEnvelope JSON object; each response frame
14
+ # carries the WireEnvelope response JSON object.
15
+ #
16
+ # This is the new envelope dispatch path (default port 7401). The legacy
17
+ # StoreServer path (port 7400) is separate and unchanged.
18
+ #
19
+ # Usage:
20
+ # adapter = TCPAdapter.new(interpreter: interpreter, port: 7401)
21
+ # adapter.start_async
22
+ # adapter.wait_until_ready
23
+ # adapter.stop
24
+ class TCPAdapter
25
+ include WireProtocol
26
+
27
+ def initialize(interpreter:, port: 7401, host: "127.0.0.1", transport: :tcp)
28
+ @interpreter = interpreter
29
+ @port = port
30
+ @host = host
31
+ @transport = transport
32
+ @stopped = false
33
+ @threads = []
34
+ @threads_mutex = Mutex.new
35
+ @ready_mutex = Mutex.new
36
+ @ready_cond = ConditionVariable.new
37
+ @ready = false
38
+ @server = build_server
39
+ # Socket is bound during initialize — signal ready immediately so that
40
+ # wait_until_ready is race-free even before start is called.
41
+ signal_ready
42
+ end
43
+
44
+ # Runs the accept loop in the current thread (blocks until #stop).
45
+ def start
46
+ until @stopped
47
+ begin
48
+ socket = @server.accept
49
+ rescue IOError, Errno::EBADF
50
+ break
51
+ end
52
+ t = Thread.new(socket) { |s| handle_client(s) }
53
+ @threads_mutex.synchronize { @threads << t }
54
+ end
55
+ end
56
+
57
+ # Starts the accept loop in a background thread. Returns self.
58
+ def start_async
59
+ @thread = Thread.new do
60
+ Thread.current.abort_on_exception = false
61
+ start
62
+ end
63
+ wait_until_ready
64
+ self
65
+ end
66
+
67
+ # Blocks until the server socket is bound and ready.
68
+ def wait_until_ready(timeout: 2)
69
+ @ready_mutex.synchronize do
70
+ deadline = Time.now + timeout
71
+ until @ready
72
+ remaining = deadline - Time.now
73
+ raise "TCPAdapter did not become ready within #{timeout}s" if remaining <= 0
74
+ @ready_cond.wait(@ready_mutex, remaining)
75
+ end
76
+ end
77
+ self
78
+ end
79
+
80
+ def stop
81
+ @stopped = true
82
+ @server&.close rescue nil
83
+ @thread&.join(2) rescue nil
84
+ @threads_mutex.synchronize { @threads.each { |t| t.join(1) rescue nil } }
85
+ self
86
+ end
87
+
88
+ def bind_address
89
+ @transport == :unix ? @host : "#{@host}:#{@port}"
90
+ end
91
+
92
+ private
93
+
94
+ def build_server
95
+ case @transport
96
+ when :tcp then TCPServer.new(@host, @port)
97
+ when :unix then UNIXServer.new(@host)
98
+ else raise ArgumentError, "Unknown transport: #{@transport.inspect}. Use :tcp or :unix"
99
+ end
100
+ end
101
+
102
+ def signal_ready
103
+ @ready_mutex.synchronize { @ready = true; @ready_cond.broadcast }
104
+ end
105
+
106
+ def handle_client(socket)
107
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true) rescue nil
108
+ loop do
109
+ body = read_frame(socket)
110
+ break unless body
111
+
112
+ envelope = JSON.parse(body, symbolize_names: true)
113
+ result = @interpreter.wire.dispatch(envelope)
114
+ socket.write(encode_frame(JSON.generate(result)))
115
+ end
116
+ rescue IOError, Errno::ECONNRESET, Errno::EPIPE
117
+ # client disconnected cleanly
118
+ rescue => e
119
+ # unexpected error — log and close
120
+ $stderr.puts "TCPAdapter: client error: #{e.class}: #{e.message}" rescue nil
121
+ ensure
122
+ socket.close rescue nil
123
+ @threads_mutex.synchronize { @threads.delete(Thread.current) }
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ module Igniter
6
+ module Store
7
+ # Shared CRC32-framed binary encoding for WAL files and network transport.
8
+ #
9
+ # Frame layout:
10
+ # [4 bytes BE uint32: body_len][body_len bytes: body][4 bytes BE uint32: CRC32(body)]
11
+ #
12
+ # A frame with a mismatched CRC or truncated body signals corruption /
13
+ # connection loss — the caller should stop reading.
14
+ module WireProtocol
15
+ FRAME_HEADER_SIZE = 4
16
+ FRAME_CRC_SIZE = 4
17
+
18
+ def encode_frame(body)
19
+ body_b = body.b
20
+ [body_b.bytesize].pack("N") << body_b << [Zlib.crc32(body)].pack("N")
21
+ end
22
+
23
+ # Reads one frame from +io+. Returns the body String on success, nil on
24
+ # truncation or CRC mismatch.
25
+ def read_frame(io)
26
+ header = io.read(FRAME_HEADER_SIZE)
27
+ return nil if header.nil? || header.bytesize < FRAME_HEADER_SIZE
28
+
29
+ len = header.unpack1("N")
30
+ body = io.read(len)
31
+ return nil if body.nil? || body.bytesize < len
32
+
33
+ crc_bytes = io.read(FRAME_CRC_SIZE)
34
+ return nil if crc_bytes.nil? || crc_bytes.bytesize < FRAME_CRC_SIZE
35
+
36
+ return nil unless Zlib.crc32(body) == crc_bytes.unpack1("N")
37
+
38
+ body
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Igniter
4
+ module Store
5
+ NATIVE = false unless const_defined?(:NATIVE) # overwritten by native.rb when extension loads
6
+ end
7
+ end
8
+
9
+ require_relative "store/native" # attempt to load Rust extension
10
+ require_relative "store/access_path"
11
+ require_relative "store/fact" # Ruby Struct + Fact.from_h (always loaded)
12
+ require_relative "store/fact_log" # Ruby FactLog + native all_facts patch
13
+ require_relative "store/wire_protocol"
14
+ require_relative "store/file_backend" # Ruby FileBackend + native snapshot patch
15
+ require_relative "store/server_config"
16
+ require_relative "store/server_logger"
17
+ require_relative "store/subscription_registry"
18
+ require_relative "store/change_event"
19
+ require_relative "store/changefeed_buffer"
20
+ require_relative "store/network_backend"
21
+ require_relative "store/store_server"
22
+ require_relative "store/igniter_store"
23
+ require_relative "store/read_cache"
24
+ require_relative "store/schema_graph"
25
+ require_relative "store/protocol"
26
+ require_relative "store/http_adapter"
27
+ require_relative "store/tcp_adapter"
28
+ require_relative "store/codecs"
29
+ require_relative "store/segmented_file_backend"
30
+ require_relative "store/mcp_adapter"
31
+ require_relative "store/contractable_receipt_sink"
32
+ require_relative "store/tbackend_adapter_descriptor"
33
+
34
+ module Igniter
35
+ module Store
36
+ class << self
37
+ def memory
38
+ IgniterStore.new
39
+ end
40
+
41
+ def open(path)
42
+ IgniterStore.open(path)
43
+ end
44
+
45
+ # Open (or create) a segmented WAL store at +root_dir+.
46
+ # Facts from all stores are partitioned into per-store, per-time-bucket
47
+ # segment files under root_dir/wal/.
48
+ def segmented(root_dir, **opts)
49
+ backend = SegmentedFileBackend.new(root_dir, **opts)
50
+ store = IgniterStore.new(backend: backend)
51
+ backend.replay.each { |fact| store.__send__(:replay, fact) }
52
+ store
53
+ end
54
+
55
+ def access_path(...)
56
+ AccessPath.new(...)
57
+ end
58
+ end
59
+
60
+ LedgerStore = IgniterStore unless const_defined?(:LedgerStore)
61
+ LedgerServer = StoreServer unless const_defined?(:LedgerServer)
62
+ LedgerNetworkBackend = NetworkBackend unless const_defined?(:LedgerNetworkBackend)
63
+ end
64
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "igniter/store"
4
+ require_relative "igniter/ledger"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ warn "igniter-store is deprecated; require igniter-ledger instead" if $VERBOSE
4
+
5
+ require_relative "igniter-ledger"