dry-validation-rust 0.1.0.pre5 → 0.1.0.pre6

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +13 -0
  3. data/.markdownlint.yml +9 -0
  4. data/.rubocop.yml +124 -0
  5. data/.ruby-version +1 -0
  6. data/.tool-versions +1 -0
  7. data/.yardopts +4 -0
  8. data/AGENTS.md +376 -0
  9. data/CHANGELOG.md +71 -0
  10. data/CODE_OF_CONDUCT.md +42 -0
  11. data/CONTRIBUTING.md +159 -0
  12. data/Cargo.lock +809 -0
  13. data/Cargo.toml +10 -0
  14. data/GOVERNANCE.md +68 -0
  15. data/Gemfile +15 -0
  16. data/Gemfile.lock +132 -0
  17. data/NOTICE.md +5 -5
  18. data/README.md +206 -181
  19. data/Rakefile +350 -0
  20. data/SECURITY.md +98 -0
  21. data/SECURITY_AUDIT.md +33 -0
  22. data/SUMMARY.md +30 -0
  23. data/SUPPORT.md +67 -0
  24. data/book.toml +9 -0
  25. data/codecov.yml +11 -0
  26. data/compatibility.yml +453 -0
  27. data/deny.toml +7 -0
  28. data/dry-validation-rust.gemspec +21 -23
  29. data/ext/dry_validation_rust/Cargo.toml +7 -7
  30. data/ext/dry_validation_rust/build.rs +5 -0
  31. data/ext/dry_validation_rust/extconf.rb +12 -0
  32. data/ext/dry_validation_rust/fuzz/.gitignore +5 -0
  33. data/ext/dry_validation_rust/fuzz/Cargo.toml +19 -0
  34. data/ext/dry_validation_rust/fuzz/corpus/parse_plan/basic_params.json +1 -0
  35. data/ext/dry_validation_rust/fuzz/fuzz_targets/parse_plan.rs +9 -0
  36. data/lib/dry/validation/rust/path_trie.rb +15 -8
  37. data/lib/dry/validation/rust/schema/dsl.rb +8 -0
  38. data/lib/dry/validation/rust/schema/field_definition.rb +3 -1
  39. data/lib/dry/validation/rust/schema/predicate_block.rb +2 -1
  40. data/lib/dry/validation/rust/schema/ruby_type_processor.rb +38 -23
  41. data/lib/dry/validation/rust/version.rb +1 -1
  42. data/supply-chain/audits.toml +4 -0
  43. data/supply-chain/config.toml +368 -0
  44. data/supply-chain/imports.lock +4 -0
  45. data/support_matrix.yml +9 -0
  46. metadata +64 -25
  47. data/docs/ARCHITECTURE.md +0 -256
  48. data/docs/COMPATIBILITY.md +0 -198
  49. data/docs/FEASIBILITY.md +0 -207
  50. data/docs/SUPPORT_MATRIX.md +0 -66
  51. data/docs/VERIFICATION.md +0 -128
data/Rakefile ADDED
@@ -0,0 +1,350 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'rake/testtask'
5
+ require 'rb_sys/extensiontask'
6
+ require 'rubygems/package_task'
7
+ require 'stringio'
8
+ require 'tmpdir'
9
+ require 'yaml'
10
+ require 'zlib'
11
+
12
+ EXTENSION_DIR = File.expand_path('ext/dry_validation_rust', __dir__)
13
+ GEMSPEC_PATH = File.expand_path('dry-validation-rust.gemspec', __dir__)
14
+ CROSS_COMPILE_PLATFORMS = %w[x86_64-linux aarch64-linux x86_64-darwin arm64-darwin].freeze
15
+ PREDICATE_MANIFEST_PATH = File.expand_path('predicates.yml', __dir__)
16
+ GENERATED_RUBY_PREDICATES_PATH = File.expand_path('lib/dry/validation/rust/generated_predicates.rb', __dir__)
17
+ GENERATED_RUST_PREDICATES_PATH = File.expand_path('ext/dry_validation_rust/src/generated_predicates.rs', __dir__)
18
+ PACKAGE_REQUIRED_FILES = %w[
19
+ CHANGELOG.md
20
+ LICENSE
21
+ NOTICE.md
22
+ predicates.yml
23
+ README.md
24
+ ext/dry_validation_rust/Cargo.lock
25
+ ext/dry_validation_rust/Cargo.toml
26
+ ext/dry_validation_rust/extconf.rb
27
+ ext/dry_validation_rust/src/lib.rs
28
+ lib/dry/validation/rust.rb
29
+ lib/dry/validation/rust/contract.rb
30
+ lib/dry/validation/rust/native.rb
31
+ lib/dry/validation/rust/version.rb
32
+ rust-toolchain.toml
33
+ ].freeze
34
+ PACKAGE_FORBIDDEN_PATTERNS = {
35
+ 'secret or credential files' => %r{(^|/)(?:\.env(?:\.|$)|.*\.(?:pem|key|p12|pfx)|id_(?:rsa|dsa|ed25519)|master\.key|credentials\.ya?ml\.enc)\z}i,
36
+ 'local build artifacts' => %r{\A(?:pkg|coverage|\.bundle|\.ruby-lsp)/|\Aext/dry_validation_rust/(?:target/|Makefile\z|mkmf\.log\z|native\.)|(?:\.gem|\.o|\.so|\.bundle|\.dylib|\.dll|\.log)\z},
37
+ 'editor files' => %r{(^|/)(?:\.DS_Store|.*~|#.*#|\.#.*|.*\.sw[op])\z|(^|/)\.(?:idea|vscode)/},
38
+ 'non-runtime project material' => %r{\A(?:benchmark|examples|docs/codex)/}
39
+ }.freeze
40
+
41
+ def package_file_list(gem_path)
42
+ data_tar_gz = nil
43
+
44
+ File.open(gem_path, 'rb') do |file|
45
+ Gem::Package::TarReader.new(file) do |gem_tar|
46
+ gem_tar.each do |entry|
47
+ data_tar_gz = entry.read if entry.full_name == 'data.tar.gz'
48
+ end
49
+ end
50
+ end
51
+
52
+ raise "Gem data archive missing from #{gem_path}" unless data_tar_gz
53
+
54
+ files = []
55
+ Zlib::GzipReader.wrap(StringIO.new(data_tar_gz)) do |gzip|
56
+ Gem::Package::TarReader.new(gzip) do |data_tar|
57
+ data_tar.each do |entry|
58
+ files << entry.full_name unless entry.directory?
59
+ end
60
+ end
61
+ end
62
+ files.sort
63
+ end
64
+
65
+ def validate_package_files(gem_path, expected_files)
66
+ files = package_file_list(gem_path)
67
+
68
+ puts 'Package contents:'
69
+ puts(files.map { |path| " #{path}" })
70
+
71
+ missing = PACKAGE_REQUIRED_FILES - files
72
+ raise "Package is missing required files: #{missing.join(', ')}" unless missing.empty?
73
+
74
+ unexpected = files - expected_files
75
+ raise "Package contains files outside spec.files: #{unexpected.join(', ')}" unless unexpected.empty?
76
+
77
+ omitted = expected_files - files
78
+ raise "Package omitted spec.files entries: #{omitted.join(', ')}" unless omitted.empty?
79
+
80
+ PACKAGE_FORBIDDEN_PATTERNS.each do |label, pattern|
81
+ matches = files.grep(pattern)
82
+ raise "Package contains #{label}: #{matches.join(', ')}" unless matches.empty?
83
+ end
84
+
85
+ native_sources = files.grep(%r{\Aext/dry_validation_rust/src/.*\.rs\z})
86
+ raise 'Package is missing native Rust source files' if native_sources.empty?
87
+
88
+ files
89
+ end
90
+
91
+ def with_unbundled_environment(&)
92
+ if defined?(Bundler)
93
+ Bundler.with_unbundled_env(&)
94
+ else
95
+ yield
96
+ end
97
+ end
98
+
99
+ def rb_sys_gem_lib_path
100
+ File.join(Gem::Specification.find_by_name('rb_sys').full_gem_path, 'lib')
101
+ end
102
+
103
+ def predicate_manifest
104
+ manifest = YAML.safe_load_file(PREDICATE_MANIFEST_PATH, permitted_classes: [], aliases: false)
105
+ predicates = manifest.fetch('predicates')
106
+ raise 'predicates.yml predicates must be an array' unless predicates.is_a?(Array)
107
+
108
+ names = []
109
+ predicates.each do |predicate|
110
+ raise 'each predicate must be a mapping' unless predicate.is_a?(Hash)
111
+
112
+ name = predicate.fetch('name')
113
+ owner = predicate.fetch('owner')
114
+ ruby_method = predicate.fetch('ruby_method')
115
+ supported_types = predicate.fetch('supported_types')
116
+ raise "invalid predicate name: #{name.inspect}" unless name.is_a?(String) && /\A[a-z][a-z0-9_]*\z/.match?(name)
117
+ raise "duplicate predicate name: #{name}" if names.include?(name)
118
+ raise "invalid owner for #{name}: #{owner.inspect}" unless %w[rust ruby].include?(owner)
119
+ raise "ruby_method for #{name} must be a non-empty string" unless ruby_method.is_a?(String) && !ruby_method.empty?
120
+ unless supported_types.is_a?(Array) && !supported_types.empty?
121
+ raise "supported_types for #{name} must be a non-empty array"
122
+ end
123
+
124
+ if owner == 'rust'
125
+ rust_op = predicate.fetch('rust_op')
126
+ unless rust_op.is_a?(String) && /\A[A-Z][A-Za-z0-9]*\z/.match?(rust_op)
127
+ raise "invalid rust_op for #{name}: #{rust_op.inspect}"
128
+ end
129
+ elsif predicate.key?('rust_op')
130
+ raise "Ruby-owned predicate #{name} must not declare rust_op"
131
+ end
132
+ names << name
133
+ end
134
+ predicates
135
+ end
136
+
137
+ def generated_predicate_files(predicates)
138
+ native = predicates.select { |predicate| predicate.fetch('owner') == 'rust' }
139
+ ruby = predicates.select { |predicate| predicate.fetch('owner') == 'ruby' }
140
+ ruby_symbols = ->(items) { items.map { |predicate| predicate.fetch('name') }.join(' ') }
141
+
142
+ ruby_source = <<~RUBY
143
+ # frozen_string_literal: true
144
+
145
+ # This file is generated by `bundle exec rake generate:predicates`.
146
+ # Do not edit it directly; update predicates.yml instead.
147
+ module Dry
148
+ module Validation
149
+ module Rust
150
+ class Schema
151
+ NATIVE_PREDICATES = %i[#{ruby_symbols.call(native)}].freeze
152
+ RUBY_PREDICATES = %i[#{ruby_symbols.call(ruby)}].freeze
153
+ end
154
+ end
155
+ end
156
+ end
157
+ RUBY
158
+ enum_variants = native.map { |predicate| " #{predicate.fetch('rust_op')}," }.join("\n")
159
+ name_mapping = native.map { |predicate| " \"#{predicate.fetch('name')}\" => Self::#{predicate.fetch('rust_op')}," }.join("\n")
160
+ rust_source = <<~RUST
161
+ // This file is generated by `bundle exec rake generate:predicates`.
162
+ // Do not edit it directly; update predicates.yml instead.
163
+
164
+ #[derive(Clone, Copy, Debug, Eq, PartialEq)]
165
+ pub(crate) enum PredicateOp {
166
+ #{enum_variants}
167
+ Unsupported,
168
+ }
169
+
170
+ impl PredicateOp {
171
+ fn from_name(name: &str) -> Self {
172
+ match name {
173
+ #{name_mapping}
174
+ _ => Self::Unsupported,
175
+ }
176
+ }
177
+ }
178
+ RUST
179
+ { GENERATED_RUBY_PREDICATES_PATH => ruby_source, GENERATED_RUST_PREDICATES_PATH => rust_source }
180
+ end
181
+
182
+ namespace :generate do
183
+ desc 'Generate predicate ownership sources from predicates.yml'
184
+ task :predicates do
185
+ generated_predicate_files(predicate_manifest).each { |path, source| File.write(path, source) }
186
+ end
187
+
188
+ namespace :predicates do
189
+ desc 'Verify generated predicate ownership sources are in sync with predicates.yml'
190
+ task :check do
191
+ generated_predicate_files(predicate_manifest).each do |path, source|
192
+ unless File.exist?(path) && File.read(path) == source
193
+ raise "#{File.basename(path)} is out of sync; run bundle exec rake generate:predicates"
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ def smoke_installed_package(gem_path)
201
+ ruby_code = <<~'RUBY'
202
+ gem "dry-validation-rust"
203
+ require "dry/validation/rust"
204
+
205
+ loaded = Gem.loaded_specs.fetch("dry-validation-rust")
206
+ gem_home = ENV.fetch("GEM_HOME")
207
+ loaded_path = File.realpath(loaded.full_gem_path)
208
+ expected_path = File.realpath(gem_home)
209
+ loaded_from_gem_home = loaded_path == expected_path || loaded_path.start_with?("#{expected_path}#{File::SEPARATOR}")
210
+ abort "loaded gem from #{loaded_path}, expected #{expected_path}" unless loaded_from_gem_home
211
+ abort "loaded upstream dry-validation" if Gem.loaded_specs.key?("dry-validation")
212
+
213
+ contract = Class.new(Dry::Validation::Rust::Contract) do
214
+ params do
215
+ required(:age).value(:integer)
216
+ required(:name).filled(:string)
217
+ end
218
+
219
+ rule(:age) do
220
+ key.failure("must be an adult") if value < 18
221
+ end
222
+ end
223
+
224
+ success = contract.new.call("age" => "21", "name" => "Jane")
225
+ abort success.errors.to_h.inspect unless success.success? && success.to_h == {age: 21, name: "Jane"}
226
+
227
+ failure = contract.new.call("age" => "17", "name" => "Jane")
228
+ abort failure.errors.to_h.inspect unless failure.failure? && failure.errors.to_h == {age: ["must be an adult"]}
229
+ RUBY
230
+
231
+ Dir.mktmpdir('dry-validation-rust-gem-home') do |gem_home|
232
+ Dir.mktmpdir('dry-validation-rust-package-smoke') do |workdir|
233
+ env = {
234
+ 'GEM_HOME' => gem_home,
235
+ 'GEM_PATH' => ([gem_home] + Gem.path).uniq.join(File::PATH_SEPARATOR),
236
+ 'RB_SYS_GEM_LIB' => rb_sys_gem_lib_path
237
+ }
238
+
239
+ with_unbundled_environment do
240
+ sh env, 'gem', 'install', '--local', gem_path, '--no-document'
241
+ Dir.chdir(workdir) do
242
+ sh env, 'ruby', '-e', ruby_code
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ desc 'Compile the Rust extension'
250
+ task compile: 'generate:predicates' do
251
+ Dir.chdir(EXTENSION_DIR) do
252
+ ruby 'extconf.rb' if !File.exist?('Makefile') || File.mtime('extconf.rb') > File.mtime('Makefile')
253
+ sh 'make'
254
+ end
255
+ end
256
+
257
+ Rake::TestTask.new(test: :compile) do |task|
258
+ task.libs << 'lib' << 'test'
259
+ task.pattern = 'test/**/*_test.rb'
260
+ task.warning = true
261
+ end
262
+
263
+ spec = Gem::Specification.load(GEMSPEC_PATH)
264
+
265
+ spec.extensions.clear if ENV.key?('RUBY_TARGET')
266
+
267
+ Gem::PackageTask.new(spec)
268
+
269
+ Dir.chdir(EXTENSION_DIR) do
270
+ RbSys::ExtensionTask.new('dry_validation_rust_native', spec) do |ext|
271
+ # Cargo's package name locates the manifest; the shared-library name is
272
+ # `native`, matching `[lib] name` in Cargo.toml and the Ruby require path.
273
+ ext.name = 'native'
274
+ ext.lib_dir = 'lib/dry/validation/rust'
275
+ def ext.source_files
276
+ super.exclude("#{ext_dir}/fuzz/**/*", '**/fuzz/**/*')
277
+ end
278
+ unless ENV.key?('RUBY_TARGET')
279
+ ext.cross_compile = true
280
+ ext.cross_platform = CROSS_COMPILE_PLATFORMS
281
+ end
282
+ end
283
+ end
284
+
285
+ if ENV.key?('RUBY_TARGET')
286
+ # rb-sys-dock injects 'gem' task execution, but rake-compiler's ExtensionTask
287
+ # hooks the host 'native' compilation to the 'gem' task.
288
+ # This causes host compilation with a cross-compile target, breaking linking.
289
+ # We clear the host 'native' task from 'gem' to prevent this.
290
+ if Rake::Task.task_defined?('gem')
291
+ Rake::Task['gem'].prerequisites.delete('native')
292
+ Rake::Task['gem'].prerequisites.delete("pkg/#{spec.full_name}.gem")
293
+ end
294
+
295
+ # Remove host extension file dependencies so they are never triggered
296
+ # during a cross-compile.
297
+ Rake.application.tasks.each do |t|
298
+ t.prerequisites.delete('lib/dry/validation/rust/native.so')
299
+ end
300
+ Rake::Task['lib/dry/validation/rust/native.so'].clear if Rake::Task.task_defined?('lib/dry/validation/rust/native.so')
301
+ end
302
+
303
+ file 'Cargo.lock' => File.join(EXTENSION_DIR, 'Cargo.lock')
304
+ file 'Cargo.toml' => File.join(EXTENSION_DIR, 'Cargo.toml')
305
+
306
+ namespace :package do
307
+ desc 'Build and audit the source gem package'
308
+ task :audit do
309
+ FileUtils.mkdir_p(File.expand_path('pkg', __dir__))
310
+ gem_path = File.expand_path("pkg/#{spec.full_name}.gem", __dir__)
311
+
312
+ sh 'gem', 'build', GEMSPEC_PATH, '--output', gem_path
313
+ validate_package_files(gem_path, spec.files.sort)
314
+ smoke_installed_package(gem_path)
315
+ end
316
+ end
317
+
318
+ namespace :dependency do
319
+ desc 'Print dependency and tool versions for verification logs'
320
+ task :versions do
321
+ puts "Ruby: #{RUBY_DESCRIPTION}"
322
+ puts "RubyGems: #{Gem::VERSION}"
323
+ puts "Bundler: #{Bundler::VERSION}" if defined?(Bundler)
324
+
325
+ puts "\nBundled Ruby gems:"
326
+ Gem.loaded_specs.values
327
+ .select { |loaded_spec| loaded_spec.full_gem_path.start_with?(File.expand_path(__dir__)) || loaded_spec.name == 'dry-validation-rust' }
328
+ .sort_by(&:name)
329
+ .each { |loaded_spec| puts " #{loaded_spec.name} #{loaded_spec.version}" }
330
+
331
+ puts "\nLocked Ruby gems:"
332
+ Bundler.load.specs.sort_by(&:name).each { |locked_spec| puts " #{locked_spec.name} #{locked_spec.version}" }
333
+
334
+ puts "\nRust toolchain:"
335
+ sh 'rustc', '--version'
336
+ sh 'cargo', '--version'
337
+
338
+ puts "\nRust dependency tree:"
339
+ sh 'cargo', 'tree', '--locked', '--manifest-path', 'ext/dry_validation_rust/Cargo.toml', '--depth', '1'
340
+ end
341
+ end
342
+
343
+ namespace :compatibility do
344
+ desc 'Run the pinned upstream differential corpus in isolated Ruby processes'
345
+ task differential: :compile do
346
+ sh 'bundle', 'exec', 'ruby', '-Ilib', '-Itest', 'test/differential_compatibility_test.rb'
347
+ end
348
+ end
349
+
350
+ task default: :test
data/SECURITY.md ADDED
@@ -0,0 +1,98 @@
1
+ # Security policy
2
+
3
+ ## Supported release lines
4
+
5
+ `dry-validation-rust` is currently an alpha project. Security fixes are
6
+ considered for the latest `0.1.x` prerelease and the current `main` branch on a
7
+ best-effort basis.
8
+
9
+ | Release line | Security support |
10
+ | --- | --- |
11
+ | Latest `0.1.x` prerelease | Best effort |
12
+ | Older prereleases | Upgrade required |
13
+ | Unreleased development branches | No separate backport promise |
14
+
15
+ The project does not yet promise long-term support or security backports.
16
+ Platform and runtime targets are listed in
17
+ [docs/SUPPORT_MATRIX.md](docs/SUPPORT_MATRIX.md).
18
+
19
+ ## Reporting a vulnerability
20
+
21
+ Do not open a public issue, discussion, or pull request for a suspected
22
+ vulnerability.
23
+
24
+ Use
25
+ [GitHub private vulnerability reporting](https://github.com/alex-tomilov/dry-validation-rust/security/advisories/new)
26
+ to send the report to the maintainer. If GitHub shows that private reporting is
27
+ unavailable, do not disclose the report publicly; contact the maintainer
28
+ through a private contact method listed on the maintainer's GitHub profile and
29
+ ask for a secure reporting channel.
30
+
31
+ Include:
32
+
33
+ - the affected gem version, commit, and loading mode;
34
+ - Ruby, Rust, OS, architecture, and source/native build details;
35
+ - a minimal reproducer or proof of concept;
36
+ - the expected and observed security boundary;
37
+ - impact, prerequisites, and known mitigations;
38
+ - whether the issue is already public or shared with anyone else;
39
+ - any preferred disclosure or credit details.
40
+
41
+ Do not include real credentials, private production data, or unnecessary
42
+ personal information.
43
+
44
+ ## What to expect
45
+
46
+ The maintainer aims to acknowledge a complete report within seven calendar
47
+ days, but this is a target rather than an SLA. Triage may request additional
48
+ information or determine that the report is a correctness bug without a
49
+ security impact.
50
+
51
+ For an accepted vulnerability, the reporter and maintainer will coordinate on
52
+ impact, remediation, release timing, advisory text, and credit. Disclosure
53
+ should wait until a fix or practical mitigation is available, unless active
54
+ exploitation or another overriding public-interest concern requires a
55
+ different timeline.
56
+
57
+ ## Coordinated disclosure and embargo
58
+
59
+ The project uses a 90-calendar-day embargo for public technical details after
60
+ a fixed version is released. During that period, the maintainer may publish a
61
+ minimal advisory and upgrade guidance, but will not publish a proof of concept
62
+ or detailed exploitation steps without coordinating with the reporter.
63
+
64
+ The embargo may end earlier only with the reporter's agreement, or when active
65
+ exploitation, an already-public disclosure, or another overriding
66
+ public-interest concern makes earlier disclosure necessary. At the end of the
67
+ embargo, the maintainer will publish the advisory through GitHub Security
68
+ Advisories when practical and credit the reporter if requested.
69
+
70
+ ## Dependency-audit schedule
71
+
72
+ The [Security workflow](.github/workflows/security.yml) runs on pull requests,
73
+ pushes to `main` and `develop`, and every Monday. It runs `bundler-audit` for
74
+ Ruby dependencies and `cargo audit --deny warnings` for Rust dependencies.
75
+ Audit failures are handled under
76
+ [the dependency-security policy](docs/DEPENDENCY_SECURITY.md).
77
+
78
+ Security releases and advisories remain subject to maintainer approval. No
79
+ report grants permission to publish a gem, create a tag, or disclose private
80
+ project information.
81
+
82
+ ## Gem signing and publication
83
+
84
+ Release gems are built only by the protected `rubygems:push` workflow. The
85
+ workflow signs every source and native gem with GitHub Actions OIDC and
86
+ Sigstore, then attaches the resulting `.sigstore.json` bundle alongside the
87
+ gem to the GitHub release.
88
+
89
+ RubyGems.org publication uses RubyGems Trusted Publishing through the same
90
+ OIDC identity; the repository does not keep a long-lived RubyGems API key for
91
+ this workflow. RubyGems Trusted Publishing must be configured on RubyGems.org
92
+ for `alex-tomilov/dry-validation-rust`, the `rubygems:push` workflow, and the
93
+ GitHub `release` environment before a release can publish.
94
+
95
+ The `release` environment is an approval boundary. Maintainers must review the
96
+ tag and generated artifacts before approving it. A test publication, when
97
+ needed, must use a separate RubyGems test-host trusted-publisher configuration
98
+ and must not change the production publisher or release environment.
data/SECURITY_AUDIT.md ADDED
@@ -0,0 +1,33 @@
1
+ # Rust supply-chain audit guide
2
+
3
+ `cargo vet --locked` is a required check in the security workflow. Its
4
+ repository-managed records are in `supply-chain/`; `imports.lock` pins the
5
+ Mozilla audit set so CI does not fetch or silently change its trust inputs.
6
+
7
+ ## Attack surface
8
+
9
+ The native extension processes untrusted validation plans and values. Review
10
+ changes around these areas especially carefully:
11
+
12
+ - `serde_json` parsing, including deeply nested or unexpectedly shaped JSON;
13
+ - bindgen and its build-time `libclang` dependency;
14
+ - the Magnus and rb-sys Ruby bridge, including all `unsafe` code and Ruby
15
+ object lifetime or exception boundaries.
16
+
17
+ ## Dependency audit policy
18
+
19
+ Lockfile and `supply-chain/` changes are reviewed together. A new third-party
20
+ crate must have a `safe-to-run` or `safe-to-deploy` audit path from a reviewed
21
+ local audit or a pinned trusted import before it can merge. Do not add an
22
+ exemption for a new dependency as a substitute for that review.
23
+
24
+ Existing exemptions are the audited baseline recorded when cargo-vet was
25
+ introduced. Reduce them when a suitable trusted audit becomes available.
26
+
27
+ ## CVE response
28
+
29
+ Treat a new advisory as a security issue: identify affected supported builds,
30
+ assess exploitability at the attack surfaces above, then update, remove, or
31
+ replace the dependency. If immediate remediation is impossible, record a
32
+ dated exception with mitigation and an owner in
33
+ `docs/DEPENDENCY_SECURITY.md`; remove it once resolved.
data/SUMMARY.md ADDED
@@ -0,0 +1,30 @@
1
+ # Summary
2
+
3
+ - [Overview](README.md)
4
+ - [Roadmap](docs/ROADMAP.md)
5
+
6
+ ## Using dry-validation-rust
7
+
8
+ - [Compatibility](docs/COMPATIBILITY.md)
9
+ - [Support matrix](docs/SUPPORT_MATRIX.md)
10
+ - [API stability](docs/API_STABILITY.md)
11
+ - [Migration guide](docs/migration.md)
12
+ - [Migration recipes](docs/MIGRATION_RECIPES.md)
13
+ - [Windows](docs/WINDOWS.md)
14
+
15
+ ## Reference
16
+
17
+ - [Ruby API reference](docs/ruby-api-reference.md)
18
+ - [Architecture](docs/ARCHITECTURE.md)
19
+ - [API boundary](docs/API_BOUNDARY.md)
20
+ - [Benchmarking](docs/BENCHMARKING.md)
21
+ - [Memory benchmarking](docs/MEMORY_BENCHMARKING.md)
22
+ - [Profiling](docs/PROFILING.md)
23
+ - [Verification](docs/VERIFICATION.md)
24
+ - [Dependency security](docs/DEPENDENCY_SECURITY.md)
25
+ - [Feasibility](docs/FEASIBILITY.md)
26
+
27
+ ## Project
28
+
29
+ - [Project management](docs/PROJECT_MANAGEMENT.md)
30
+ - [Release checklist](docs/RELEASE_CHECKLIST.md)
data/SUPPORT.md ADDED
@@ -0,0 +1,67 @@
1
+ # Support
2
+
3
+ `dry-validation-rust` is an alpha, single-maintainer project. Support is
4
+ best-effort and no response-time or resolution SLA is offered.
5
+
6
+ Before opening an issue, check the
7
+ [support matrix](docs/SUPPORT_MATRIX.md),
8
+ [compatibility matrix](docs/COMPATIBILITY.md), and existing issues.
9
+
10
+ ## Bugs
11
+
12
+ Use the bug report form for behavior that appears incorrect within the
13
+ documented supported surface. Include a minimal contract and input, exact
14
+ versions, loading mode, build type, platform, expected and actual output, and a
15
+ full backtrace where applicable.
16
+
17
+ Native crashes, installation failures, and unexpected Ruby exceptions are
18
+ bugs even when they occur around unsupported input. Unsupported DSL behavior
19
+ should fail loudly rather than silently succeed.
20
+
21
+ ## Compatibility mismatches
22
+
23
+ Use the compatibility mismatch form when the Rust implementation and pinned
24
+ upstream `dry-validation`/`dry-schema` releases produce different values,
25
+ errors, metadata, exceptions, or loading behavior for a documented compatible
26
+ feature. Run the two implementations in separate processes and include both
27
+ outputs and exact upstream versions.
28
+
29
+ Not every upstream feature is in scope. Check `docs/COMPATIBILITY.md` before
30
+ filing.
31
+
32
+ ## Feature requests
33
+
34
+ Use the feature request form to describe the user problem, desired behavior,
35
+ alternatives, and compatibility implications. New DSL surface requires
36
+ explicit design and tests; it will not be added as a side effect of another
37
+ change.
38
+
39
+ ## Usage questions
40
+
41
+ Use GitHub Discussions when that repository feature is available. If it is not
42
+ available, consult the README, examples, compatibility matrix, and existing
43
+ issues. Open an issue only when the question identifies a likely documentation
44
+ gap, bug, or focused feature request.
45
+
46
+ General application debugging, contract design consulting, and upstream
47
+ `dry-validation` support are outside this project's support scope.
48
+
49
+ ## Performance reports
50
+
51
+ Use the performance report form. Reports must include a runnable reproducer,
52
+ warmup, iterations, hardware/software details, allocations or RSS where
53
+ relevant, and raw results from multiple runs. A single benchmark number without
54
+ semantic equivalence evidence is not actionable.
55
+
56
+ ## Security reports
57
+
58
+ Do not use public issues or discussions for suspected vulnerabilities. Follow
59
+ [SECURITY.md](SECURITY.md) and use GitHub private vulnerability reporting.
60
+
61
+ ## Triage expectations
62
+
63
+ The maintainer targets initial triage of complete bug and compatibility reports
64
+ within two weeks when capacity permits. Feature requests, performance
65
+ investigations, and usage questions may take longer or receive no immediate
66
+ response. Incomplete or out-of-scope reports may be closed with a request for
67
+ the missing information.
data/book.toml ADDED
@@ -0,0 +1,9 @@
1
+ [book]
2
+ title = "dry-validation-rust"
3
+ src = "."
4
+
5
+ [output.html]
6
+ git-repository-url = "https://github.com/alex-tomilov/dry-validation-rust"
7
+
8
+ [output.html.search]
9
+ enable = true
data/codecov.yml ADDED
@@ -0,0 +1,11 @@
1
+ coverage:
2
+ status:
3
+ project:
4
+ rust:
5
+ target: 80%
6
+ threshold: 2%
7
+ ruby:
8
+ target: 70%
9
+ threshold: 2%
10
+ flags:
11
+ - ruby