asherah 0.9.0-aarch64-linux → 0.10.0-aarch64-linux

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.
data/lib/asherah.rb CHANGED
@@ -1,135 +1,321 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'asherah/version'
4
- require 'asherah/config'
5
- require 'asherah/error'
6
- require 'cobhan'
3
+ require "json"
7
4
 
8
- # Asherah is a Ruby wrapper around Asherah Go application-layer encryption SDK.
9
- module Asherah
10
- extend Cobhan
5
+ require_relative "asherah/version"
6
+ require_relative "asherah/error"
7
+ require_relative "asherah/config"
8
+ require_relative "asherah/native"
9
+ require_relative "asherah/session_factory"
10
+ require_relative "asherah/session"
11
+ require_relative "asherah/hooks"
11
12
 
12
- LIB_ROOT_PATH = File.expand_path('asherah/native', __dir__)
13
- load_library(LIB_ROOT_PATH, 'libasherah', [
14
- [:SetEnv, [:pointer], :int32],
15
- [:SetupJson, [:pointer], :int32],
16
- [:EncryptToJson, [:pointer, :pointer, :pointer], :int32],
17
- [:DecryptFromJson, [:pointer, :pointer, :pointer], :int32],
18
- [:Shutdown, [], :void]
19
- ].freeze)
13
+ module Asherah
14
+ DEFAULT_SESSION_CACHE_MAX_SIZE = 1000
20
15
 
21
- ESTIMATED_ENCRYPTION_OVERHEAD = 48
22
- ESTIMATED_ENVELOPE_OVERHEAD = 185
23
- BASE64_OVERHEAD = 1.34
16
+ @mutex = Mutex.new
17
+ @factory = nil
18
+ # @sessions is an LRU cache. Ruby Hash preserves insertion order, so we
19
+ # implement LRU by delete-and-reinsert on hit (moves the entry to the
20
+ # end), and `shift` on overflow (removes the oldest, i.e. least-recently
21
+ # used). Bounded by @session_cache_max_size, which honors the
22
+ # SessionCacheMaxSize config field — same default (1000) as the Rust
23
+ # core and the other bindings.
24
+ @sessions = {}
25
+ @initialized = false
26
+ @session_cache_enabled = true
27
+ @session_cache_max_size = DEFAULT_SESSION_CACHE_MAX_SIZE
28
+ @verbose = false
24
29
 
25
30
  class << self
26
- # Set environment variables needed by Asherah dependencies for when
27
- # Go os.Getenv() doesn't see variables set by C.setenv().
28
- # References:
29
- # https://github.com/golang/go/wiki/cgo#environmental-variables
30
- # https://github.com/golang/go/issues/44108
31
+ # Configure Asherah using a block with snake_case accessors.
32
+ # Compatible with the canonical godaddy/asherah-ruby gem API.
31
33
  #
32
- # @yield [Config]
33
- # @param env [Hash], Key-value pairs to set Asherah ENV
34
- # @return [void]
35
- def set_env(env = {})
36
- env_buffer = string_to_cbuffer(env.to_json)
34
+ # Asherah.configure do |config|
35
+ # config.service_name = "MyService"
36
+ # config.product_id = "MyProduct"
37
+ # config.kms = "static"
38
+ # config.metastore = "memory"
39
+ # end
40
+ def configure
41
+ @mutex.synchronize do
42
+ raise Error::AlreadyInitialized if @initialized
37
43
 
38
- result = SetEnv(env_buffer)
39
- Error.check_result!(result, 'SetEnv failed')
40
- ensure
41
- env_buffer&.free
44
+ config = Config.new
45
+ yield config
46
+ config.validate!
47
+
48
+ json = config.to_json
49
+ pointer = Native.asherah_factory_new_with_config(json)
50
+ @factory = SessionFactory.new(pointer)
51
+ @sessions = {}
52
+ @initialized = true
53
+ @session_cache_enabled = config.enable_session_caching != false
54
+ @session_cache_max_size = positive_int(config.session_cache_max_size) || DEFAULT_SESSION_CACHE_MAX_SIZE
55
+ @verbose = config.verbose == true
56
+ end
42
57
  end
43
58
 
44
- # Configures Asherah
45
- #
46
- # @yield [Config]
47
- # @return [void]
48
- def configure
49
- raise Asherah::Error::AlreadyInitialized if @initialized
59
+ # Initialize Asherah with a PascalCase config hash.
60
+ # Also accepts snake_case string/symbol keys (auto-normalized).
61
+ def setup(config)
62
+ normalized = normalize_config(config)
63
+ json = JSON.generate(normalized)
50
64
 
51
- config = Config.new
52
- yield config
53
- config.validate!
54
- @intermediated_key_overhead_bytesize = config.product_id.bytesize + config.service_name.bytesize
65
+ pointer = Native.asherah_factory_new_with_config(json)
66
+ factory = SessionFactory.new(pointer)
55
67
 
56
- config_buffer = string_to_cbuffer(config.to_json)
68
+ @mutex.synchronize do
69
+ raise Error::AlreadyInitialized if @initialized
57
70
 
58
- result = SetupJson(config_buffer)
59
- Error.check_result!(result, 'SetupJson failed')
60
- @initialized = true
61
- ensure
62
- config_buffer&.free
71
+ @factory = factory
72
+ @sessions = {}
73
+ @initialized = true
74
+ @session_cache_enabled = truthy(normalized["EnableSessionCaching"], default: true)
75
+ @session_cache_max_size = positive_int(normalized["SessionCacheMaxSize"]) || DEFAULT_SESSION_CACHE_MAX_SIZE
76
+ @verbose = truthy(normalized["Verbose"], default: false)
77
+ end
78
+
79
+ nil
80
+ rescue StandardError
81
+ factory&.close if defined?(factory) && factory
82
+ raise
63
83
  end
64
84
 
65
- # Encrypts data for a given partition_id and returns DataRowRecord in JSON format.
66
- #
67
- # DataRowRecord contains the encrypted key and data, as well as the information
68
- # required to decrypt the key encryption key. This object data should be stored
69
- # in your data persistence as it's required to decrypt data.
70
- #
71
- # EnvelopeKeyRecord represents an encrypted key and is the data structure used
72
- # to persist the key in the key table. It also contains the meta data
73
- # of the key used to encrypt it.
74
- #
75
- # KeyMeta contains the `id` and `created` timestamp for an encryption key.
76
- #
77
- # @param partition_id [String]
78
- # @param data [String]
79
- # @return [String], DataRowRecord in JSON format
80
- def encrypt(partition_id, data)
81
- raise Asherah::Error::NotInitialized unless @initialized
85
+ # Run setup in a background Thread. Yields the result to +block+ on
86
+ # success. Exceptions raised by setup propagate via the Thread's
87
+ # joined error the previous implementation called the block with
88
+ # the result on success but silently dropped the Thread's error
89
+ # state on failure, leaving callers unable to distinguish completion
90
+ # from an unsetup factory. The Thread aborts on exception
91
+ # (`Thread.report_on_exception = true`), so a stack trace lands on
92
+ # stderr; callers that need programmatic access should
93
+ # `thread.join` to re-raise. T-finding "setup_async/shutdown_async/
94
+ # encrypt_async/decrypt_async swallow Thread exceptions" in
95
+ # `docs/review-2026-05-05-findings.md`.
96
+ def setup_async(config, &block)
97
+ Thread.new do
98
+ Thread.current.report_on_exception = true
99
+ result = setup(config)
100
+ block&.call(result)
101
+ result
102
+ end
103
+ end
82
104
 
83
- partition_id_buffer = string_to_cbuffer(partition_id)
84
- data_buffer = string_to_cbuffer(data)
85
- estimated_buffer_bytesize = estimate_buffer(data.bytesize, partition_id.bytesize)
86
- output_buffer = allocate_cbuffer(estimated_buffer_bytesize)
105
+ def shutdown
106
+ factory = nil
107
+ sessions = nil
108
+ @mutex.synchronize do
109
+ raise Error::NotInitialized unless @initialized
87
110
 
88
- result = EncryptToJson(partition_id_buffer, data_buffer, output_buffer)
89
- Error.check_result!(result, 'EncryptToJson failed')
111
+ factory = @factory
112
+ sessions = @sessions.values
113
+ @factory = nil
114
+ @sessions = {}
115
+ @initialized = false
116
+ end
90
117
 
91
- cbuffer_to_string(output_buffer)
92
- ensure
93
- [partition_id_buffer, data_buffer, output_buffer].compact.each(&:free)
118
+ Array(sessions).each do |session|
119
+ begin
120
+ session.close unless session.closed?
121
+ rescue StandardError => e
122
+ warn "asherah: error closing session during shutdown: #{e.message}"
123
+ end
124
+ end
125
+ factory&.close unless factory&.closed?
126
+ nil
94
127
  end
95
128
 
96
- # Decrypts a DataRowRecord in JSON format for a partition_id and returns decrypted data.
97
- #
98
- # @param partition_id [String]
99
- # @param json [String], DataRowRecord in JSON format
100
- # @return [String], Decrypted data
101
- def decrypt(partition_id, json)
102
- raise Asherah::Error::NotInitialized unless @initialized
129
+ # Run shutdown in a background Thread; see `setup_async` for the
130
+ # exception-propagation contract.
131
+ def shutdown_async(&block)
132
+ Thread.new do
133
+ Thread.current.report_on_exception = true
134
+ result = shutdown
135
+ block&.call(result)
136
+ result
137
+ end
138
+ end
103
139
 
104
- partition_id_buffer = string_to_cbuffer(partition_id)
105
- data_buffer = string_to_cbuffer(json)
106
- output_buffer = allocate_cbuffer(json.bytesize)
140
+ def get_setup_status
141
+ @mutex.synchronize { @initialized }
142
+ end
107
143
 
108
- result = DecryptFromJson(partition_id_buffer, data_buffer, output_buffer)
109
- Error.check_result!(result, 'DecryptFromJson failed')
144
+ def setenv(env = {})
145
+ data = case env
146
+ when String
147
+ JSON.parse(env)
148
+ else
149
+ env
150
+ end
151
+ unless data.respond_to?(:each_pair)
152
+ raise ArgumentError, "environment payload must be a Hash or JSON object"
153
+ end
154
+ data.each_pair do |k, v|
155
+ if v.nil?
156
+ ENV.delete(String(k))
157
+ else
158
+ ENV[String(k)] = v.to_s
159
+ end
160
+ end
161
+ nil
162
+ end
163
+ alias_method :set_env, :setenv
110
164
 
111
- cbuffer_to_string(output_buffer)
112
- ensure
113
- [partition_id_buffer, data_buffer, output_buffer].compact.each(&:free)
165
+ def encrypt(partition_id, payload)
166
+ raise ArgumentError, "payload cannot be nil" if payload.nil?
167
+ session = resolve_session(partition_id)
168
+ session.encrypt_bytes(payload)
114
169
  end
115
170
 
116
- # Stop the Asherah instance
117
- def shutdown
118
- raise Asherah::Error::NotInitialized unless @initialized
171
+ def encrypt_string(partition_id, text)
172
+ raise ArgumentError, "text cannot be nil" if text.nil?
173
+ encrypt(partition_id, text)
174
+ end
175
+
176
+ def decrypt(partition_id, data_row_record)
177
+ raise ArgumentError, "data_row_record cannot be nil" if data_row_record.nil?
178
+ session = resolve_session(partition_id)
179
+ session.decrypt_bytes(data_row_record).force_encoding(Encoding::UTF_8)
180
+ end
181
+
182
+ def decrypt_string(partition_id, data_row_record)
183
+ raise ArgumentError, "data_row_record cannot be nil" if data_row_record.nil?
184
+ decrypt(partition_id, data_row_record).force_encoding(Encoding::UTF_8)
185
+ end
186
+
187
+ # Run encrypt in a background Thread; see `setup_async` for the
188
+ # exception-propagation contract.
189
+ def encrypt_async(partition_id, payload, &block)
190
+ Thread.new do
191
+ Thread.current.report_on_exception = true
192
+ result = encrypt(partition_id, payload)
193
+ block&.call(result)
194
+ result
195
+ end
196
+ end
197
+
198
+ # Run decrypt in a background Thread; see `setup_async` for the
199
+ # exception-propagation contract.
200
+ def decrypt_async(partition_id, data_row_record, &block)
201
+ Thread.new do
202
+ Thread.current.report_on_exception = true
203
+ result = decrypt(partition_id, data_row_record)
204
+ block&.call(result)
205
+ result
206
+ end
207
+ end
208
+
209
+ # Install a log hook. Yields a +Hash+ +{level:, target:, message:}+ for
210
+ # every log record emitted by the underlying Rust crates. The block may
211
+ # fire from any thread; implementations must be thread-safe and
212
+ # non-blocking. Pass +nil+ to clear (equivalent to {clear_log_hook}).
213
+ #
214
+ # Replaces any previously installed log hook. Exceptions raised from the
215
+ # callback are caught and silently swallowed.
216
+ def set_log_hook(callback = nil, &block)
217
+ Hooks.set_log_hook(callback, &block)
218
+ end
119
219
 
120
- Shutdown()
121
- @initialized = false
220
+ # Remove the active log hook, if any. Idempotent.
221
+ def clear_log_hook
222
+ Hooks.clear_log_hook
223
+ end
224
+
225
+ # Install a metrics hook. Yields a +Hash+ +{type:, duration_ns:, name:}+
226
+ # for every metrics event. Timing events ({:encrypt, :decrypt, :store,
227
+ # :load}) carry a positive +duration_ns+ and a +nil+ +name+; cache events
228
+ # ({:cache_hit, :cache_miss, :cache_stale}) carry +duration_ns+ == 0 and
229
+ # the cache identifier in +name+.
230
+ #
231
+ # Installing a hook implicitly enables the global metrics gate; clearing
232
+ # it disables the gate. Replaces any previously installed metrics hook.
233
+ # Pass +nil+ to clear (equivalent to {clear_metrics_hook}).
234
+ def set_metrics_hook(callback = nil, &block)
235
+ Hooks.set_metrics_hook(callback, &block)
236
+ end
237
+
238
+ # Remove the active metrics hook and disable metrics. Idempotent.
239
+ def clear_metrics_hook
240
+ Hooks.clear_metrics_hook
122
241
  end
123
242
 
124
243
  private
125
244
 
126
- def estimate_buffer(data_bytesize, partition_bytesize)
127
- est_data_len = (((data_bytesize + ESTIMATED_ENCRYPTION_OVERHEAD) * BASE64_OVERHEAD).to_i + 1)
245
+ REQUIRED_KEYS = %w[ServiceName ProductID Metastore].freeze
246
+
247
+ def normalize_config(config)
248
+ unless config.respond_to?(:each_pair)
249
+ raise ArgumentError, "config must be a Hash-like object"
250
+ end
251
+ normalized = {}
252
+ config.each_pair do |key, value|
253
+ normalized[String(key)] = value
254
+ end
255
+ REQUIRED_KEYS.each do |key|
256
+ raise ArgumentError, "#{key} is required" if normalized[key].nil? || normalized[key].to_s.strip.empty?
257
+ end
258
+ normalized
259
+ end
260
+
261
+ def truthy(value, default: false)
262
+ return default if value.nil?
263
+
264
+ case value
265
+ when true, "1", "true", "TRUE", "yes", "on" then true
266
+ when false, "0", "false", "FALSE", "no", "off" then false
267
+ else
268
+ default
269
+ end
270
+ end
271
+
272
+ def resolve_session(partition_id)
273
+ raise ArgumentError, "partition_id cannot be empty" if String(partition_id).empty?
274
+
275
+ evicted = nil
276
+ session = @mutex.synchronize do
277
+ raise Error::NotInitialized unless @initialized
278
+
279
+ unless @session_cache_enabled
280
+ break @factory.get_session(partition_id)
281
+ end
282
+
283
+ # LRU: on hit, delete + reinsert to move the entry to the
284
+ # most-recently-used end of the Hash (Ruby Hash is insertion-
285
+ # ordered). On miss + overflow, `shift` removes the oldest entry,
286
+ # which is the least-recently-used.
287
+ if (existing = @sessions.delete(partition_id))
288
+ @sessions[partition_id] = existing
289
+ break existing
290
+ end
291
+
292
+ fresh = @factory.get_session(partition_id)
293
+ @sessions[partition_id] = fresh
294
+ if @sessions.size > @session_cache_max_size
295
+ _, evicted = @sessions.shift
296
+ end
297
+ fresh
298
+ end
299
+
300
+ # Close evicted session outside the lock — close hits the FFI and
301
+ # we don't want to serialize all encrypts behind one eviction.
302
+ if evicted
303
+ begin
304
+ evicted.close unless evicted.closed?
305
+ rescue StandardError => e
306
+ warn "asherah: error closing evicted session: #{e.message}"
307
+ end
308
+ end
309
+
310
+ session
311
+ end
128
312
 
129
- ESTIMATED_ENVELOPE_OVERHEAD +
130
- @intermediated_key_overhead_bytesize +
131
- partition_bytesize +
132
- est_data_len
313
+ def positive_int(value)
314
+ return nil if value.nil?
315
+ n = Integer(value)
316
+ n.positive? ? n : nil
317
+ rescue ArgumentError, TypeError
318
+ nil
133
319
  end
134
320
  end
135
321
  end
metadata CHANGED
@@ -1,68 +1,72 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asherah
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
- - GoDaddy
7
+ - Jay Gowdy
8
+ - Bo Thompson
9
+ - Michael Micco
10
+ - Dalibor Nasevic
8
11
  autorequire:
9
- bindir: exe
12
+ bindir: bin
10
13
  cert_chain: []
11
- date: 2026-03-23 00:00:00.000000000 Z
14
+ date: 2026-06-05 00:00:00.000000000 Z
12
15
  dependencies:
13
16
  - !ruby/object:Gem::Dependency
14
- name: cobhan
17
+ name: ffi
15
18
  requirement: !ruby/object:Gem::Requirement
16
19
  requirements:
17
20
  - - "~>"
18
21
  - !ruby/object:Gem::Version
19
- version: 0.2.0
22
+ version: '1.15'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - "~>"
25
28
  - !ruby/object:Gem::Version
26
- version: 0.2.0
27
- force_ruby_platform: false
28
- description: |
29
- Asherah is an application-layer encryption SDK that provides advanced
30
- encryption features and defense in depth against compromise.
31
- email:
32
- - oss@godaddy.com
29
+ version: '1.15'
30
+ - !ruby/object:Gem::Dependency
31
+ name: logger
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - "~>"
35
+ - !ruby/object:Gem::Version
36
+ version: '1.6'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '1.6'
44
+ description: Asherah application-layer encryption for Ruby with automatic key rotation,
45
+ powered by the native Rust implementation.
46
+ email: oss@godaddy.com
33
47
  executables: []
34
- extensions:
35
- - ext/asherah/extconf.rb
48
+ extensions: []
36
49
  extra_rdoc_files: []
37
50
  files:
38
- - ".env.secrets.example"
39
- - ".rspec"
40
- - ".rubocop.yml"
41
- - ".ruby-version"
42
- - CHANGELOG.md
43
- - CODE_OF_CONDUCT.md
44
- - CONTRIBUTING.md
45
- - Gemfile
46
- - LICENSE.txt
47
51
  - README.md
48
- - Rakefile
49
- - SECURITY.md
50
- - asherah.gemspec
51
- - ext/asherah/checksums.yml
52
- - ext/asherah/extconf.rb
53
- - ext/asherah/native_file.rb
54
52
  - lib/asherah.rb
55
53
  - lib/asherah/config.rb
56
54
  - lib/asherah/error.rb
57
- - lib/asherah/native/libasherah-arm64.so
55
+ - lib/asherah/hooks.rb
56
+ - lib/asherah/native.rb
57
+ - lib/asherah/native/libasherah_ffi.so
58
+ - lib/asherah/session.rb
59
+ - lib/asherah/session_factory.rb
58
60
  - lib/asherah/version.rb
59
- homepage: https://github.com/godaddy/asherah-ruby
61
+ homepage: https://github.com/godaddy/asherah-ffi
60
62
  licenses:
61
- - MIT
63
+ - Apache-2.0
62
64
  metadata:
63
- homepage_uri: https://github.com/godaddy/asherah-ruby
64
- source_code_uri: https://github.com/godaddy/asherah-ruby
65
- changelog_uri: https://github.com/godaddy/asherah-ruby/blob/main/CHANGELOG.md
65
+ homepage_uri: https://github.com/godaddy/asherah-ffi
66
+ source_code_uri: https://github.com/godaddy/asherah-ffi
67
+ github_repo: https://github.com/godaddy/asherah-ffi
68
+ changelog_uri: https://github.com/godaddy/asherah-ffi/releases
69
+ bug_tracker_uri: https://github.com/godaddy/asherah-ffi/issues
66
70
  rubygems_mfa_required: 'true'
67
71
  post_install_message:
68
72
  rdoc_options: []
@@ -72,15 +76,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
72
76
  requirements:
73
77
  - - ">="
74
78
  - !ruby/object:Gem::Version
75
- version: 2.7.0
79
+ version: '3.0'
76
80
  required_rubygems_version: !ruby/object:Gem::Requirement
77
81
  requirements:
78
82
  - - ">="
79
83
  - !ruby/object:Gem::Version
80
84
  version: '0'
81
85
  requirements: []
82
- rubygems_version: 3.4.19
86
+ rubygems_version: 3.5.22
83
87
  signing_key:
84
88
  specification_version: 4
85
- summary: Application Layer Encryption SDK
89
+ summary: Asherah application-layer encryption for Ruby with automatic key rotation.
86
90
  test_files: []
data/.env.secrets.example DELETED
@@ -1,9 +0,0 @@
1
- # Example secrets file for KMS integration tests
2
- # Copy this file to .env.secrets and fill in actual values
3
- #
4
- # IMPORTANT: Never commit .env.secrets to version control
5
- # The .env.secrets file is already in .gitignore
6
-
7
- # AWS KMS Key ARN for integration tests (optional)
8
- # Only needed if running spec/kms_spec.rb
9
- # KMS_KEY_ARN=arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,112 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 2.7
3
- NewCops: enable
4
- SuggestExtensions: false
5
- Exclude:
6
- - 'vendor/**/*' # Github Actions
7
- - 'tmp/**/*'
8
-
9
- Layout/LineLength:
10
- Max: 120
11
-
12
- # Metrics cops with reasonable limits
13
- Metrics/BlockLength:
14
- Max: 25
15
- Exclude:
16
- - 'spec/**/*'
17
- - '*.gemspec'
18
- - 'Rakefile'
19
-
20
- Metrics/MethodLength:
21
- Max: 15
22
- Exclude:
23
- - 'spec/**/*'
24
- - 'tasks/**/*'
25
-
26
- Metrics/AbcSize:
27
- Max: 20
28
- Exclude:
29
- - 'spec/**/*'
30
- - 'tasks/**/*'
31
-
32
- Metrics/CyclomaticComplexity:
33
- Max: 10
34
- Exclude:
35
- - 'spec/**/*'
36
-
37
- Metrics/PerceivedComplexity:
38
- Max: 10
39
- Exclude:
40
- - 'spec/**/*'
41
-
42
- Metrics/ClassLength:
43
- Max: 150
44
- Exclude:
45
- - 'spec/**/*'
46
-
47
- Metrics/ModuleLength:
48
- Max: 150
49
- Exclude:
50
- - 'spec/**/*'
51
-
52
- # Style cops that were disabled but should be enabled
53
- Style/WordArray:
54
- MinSize: 3
55
- EnforcedStyle: brackets
56
-
57
- Style/SymbolArray:
58
- MinSize: 3
59
- EnforcedStyle: brackets
60
-
61
- Style/MultilineBlockChain:
62
- Enabled: true
63
- Exclude:
64
- - 'spec/**/*'
65
-
66
- Style/BlockDelimiters:
67
- EnforcedStyle: semantic
68
- FunctionalMethods:
69
- - let
70
- - let!
71
- - subject
72
- - before
73
- - after
74
- Exclude:
75
- - 'asherah.gemspec'
76
- - 'ext/asherah/native_file.rb'
77
-
78
- Style/GuardClause:
79
- MinBodyLength: 3
80
- Exclude:
81
- - 'ext/asherah/native_file.rb'
82
-
83
- # Naming cop adjustment
84
- Naming/AccessorMethodName:
85
- Exclude:
86
- - 'lib/asherah.rb' # set_env is intentionally named
87
-
88
- # Documentation cops
89
- Style/Documentation:
90
- Enabled: true
91
- Exclude:
92
- - 'spec/**/*'
93
- - 'features/**/*'
94
-
95
- Style/DocumentationMethod:
96
- Enabled: false # YARD comments are optional
97
-
98
- Style/EmptyClassDefinition:
99
- Enabled: false
100
-
101
- # Additional cops for code quality
102
- Lint/UnusedMethodArgument:
103
- Enabled: true
104
-
105
- Lint/UnusedBlockArgument:
106
- Enabled: true
107
-
108
- Security/Eval:
109
- Enabled: true
110
-
111
- Security/JSONLoad:
112
- Enabled: true
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 3.2.2