bparity 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +61 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +146 -0
  5. data/Rakefile +36 -0
  6. data/docs/application_example.md +25 -0
  7. data/docs/formal_assurance_limits.md +21 -0
  8. data/exe/bparity +7 -0
  9. data/fixtures/scenarios/01_pure_function/adapter.rb +13 -0
  10. data/fixtures/scenarios/01_pure_function/boundary.rb +17 -0
  11. data/fixtures/scenarios/01_pure_function/legacy/dead_gem.rb +9 -0
  12. data/fixtures/scenarios/01_pure_function/legacy/slugifier.rb +17 -0
  13. data/fixtures/scenarios/01_pure_function/replacement/broken.rb +15 -0
  14. data/fixtures/scenarios/01_pure_function/replacement/good.rb +19 -0
  15. data/fixtures/scenarios/01_pure_function/spec/slugifier_spec.rb +20 -0
  16. data/fixtures/scenarios/01_pure_function/test/slugifier_test.rb +10 -0
  17. data/fixtures/scenarios/02_stateful_client/adapter.rb +19 -0
  18. data/fixtures/scenarios/02_stateful_client/boundary.rb +10 -0
  19. data/fixtures/scenarios/02_stateful_client/legacy/client.rb +29 -0
  20. data/fixtures/scenarios/02_stateful_client/replacement/broken.rb +14 -0
  21. data/fixtures/scenarios/02_stateful_client/replacement/good.rb +23 -0
  22. data/fixtures/scenarios/02_stateful_client/spec/client_spec.rb +31 -0
  23. data/fixtures/scenarios/03_external_boundary/adapter.rb +13 -0
  24. data/fixtures/scenarios/03_external_boundary/boundary.rb +11 -0
  25. data/fixtures/scenarios/03_external_boundary/legacy/dead_formatter.rb +7 -0
  26. data/fixtures/scenarios/03_external_boundary/legacy/receipt.rb +11 -0
  27. data/fixtures/scenarios/03_external_boundary/replacement/broken.rb +11 -0
  28. data/fixtures/scenarios/03_external_boundary/replacement/good.rb +11 -0
  29. data/fixtures/scenarios/03_external_boundary/spec/receipt_spec.rb +10 -0
  30. data/fixtures/scenarios/04_intentional_divergence/adapter.rb +12 -0
  31. data/fixtures/scenarios/04_intentional_divergence/adapter_unwaived.rb +10 -0
  32. data/fixtures/scenarios/04_intentional_divergence/boundary.rb +8 -0
  33. data/fixtures/scenarios/04_intentional_divergence/legacy/dead_identity.rb +7 -0
  34. data/fixtures/scenarios/04_intentional_divergence/legacy/identity.rb +9 -0
  35. data/fixtures/scenarios/04_intentional_divergence/replacement/broken.rb +9 -0
  36. data/fixtures/scenarios/04_intentional_divergence/replacement/good.rb +9 -0
  37. data/fixtures/scenarios/04_intentional_divergence/spec/identity_spec.rb +10 -0
  38. data/fixtures/scenarios/05_formal_negative/adapter.rb +15 -0
  39. data/fixtures/scenarios/05_formal_negative/boundary.rb +16 -0
  40. data/fixtures/scenarios/05_formal_negative/legacy/dead_lock.rb +5 -0
  41. data/fixtures/scenarios/05_formal_negative/legacy/turnstile.rb +24 -0
  42. data/fixtures/scenarios/05_formal_negative/replacement/broken.rb +18 -0
  43. data/fixtures/scenarios/05_formal_negative/replacement/formal_broken.rb +24 -0
  44. data/fixtures/scenarios/05_formal_negative/replacement/good.rb +24 -0
  45. data/fixtures/scenarios/05_formal_negative/spec/turnstile_spec.rb +21 -0
  46. data/lib/bparity/adapter.rb +115 -0
  47. data/lib/bparity/adequacy.rb +78 -0
  48. data/lib/bparity/boundary.rb +97 -0
  49. data/lib/bparity/cli/formal_commands.rb +428 -0
  50. data/lib/bparity/cli/verification_commands.rb +242 -0
  51. data/lib/bparity/cli.rb +262 -0
  52. data/lib/bparity/corpus.rb +45 -0
  53. data/lib/bparity/errors.rb +12 -0
  54. data/lib/bparity/formal/assumptions.rb +131 -0
  55. data/lib/bparity/formal/bounded.rb +543 -0
  56. data/lib/bparity/formal/contract.rb +81 -0
  57. data/lib/bparity/formal/deductive.rb +481 -0
  58. data/lib/bparity/formal/lts.rb +344 -0
  59. data/lib/bparity/formal/result.rb +50 -0
  60. data/lib/bparity/formal.rb +8 -0
  61. data/lib/bparity/recording.rb +412 -0
  62. data/lib/bparity/reporting.rb +128 -0
  63. data/lib/bparity/spec_bundle.rb +208 -0
  64. data/lib/bparity/synthesis.rb +487 -0
  65. data/lib/bparity/verification.rb +310 -0
  66. data/lib/bparity/version.rb +5 -0
  67. data/lib/bparity.rb +42 -0
  68. metadata +125 -0
@@ -0,0 +1,310 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bparity
4
+ module Verification
5
+ Result = Struct.new(:id, :status, :description, :differences, :provenance, :waiver, keyword_init: true) do
6
+ def to_h
7
+ { "id" => id, "status" => status.to_s.upcase, "description" => description,
8
+ "differences" => differences, "provenance" => provenance,
9
+ "waiver" => waiver&.to_h&.transform_keys(&:to_s) }
10
+ end
11
+ end
12
+
13
+ module Differ
14
+ MISSING = Object.new.freeze
15
+
16
+ module_function
17
+
18
+ def call(expected, actual, path = "$")
19
+ return [] if expected == actual
20
+ return hash_diff(expected, actual, path) if expected.is_a?(Hash) && actual.is_a?(Hash)
21
+ return array_diff(expected, actual, path) if expected.is_a?(Array) && actual.is_a?(Array)
22
+
23
+ [{ "path" => path, "expected" => expected, "actual" => actual }]
24
+ end
25
+
26
+ def hash_diff(expected, actual, path)
27
+ (expected.keys | actual.keys).sort_by(&:to_s).flat_map do |key|
28
+ if !expected.key?(key) || !actual.key?(key)
29
+ expected_value = expected.fetch(key, MISSING)
30
+ actual_value = actual.fetch(key, MISSING)
31
+ [{ "path" => "#{path}.#{key}", "expected" => display(expected_value),
32
+ "actual" => display(actual_value) }]
33
+ else
34
+ call(expected[key], actual[key], "#{path}.#{key}")
35
+ end
36
+ end
37
+ end
38
+ private_class_method :hash_diff
39
+
40
+ def display(value) = value.equal?(MISSING) ? "<missing>" : value
41
+ private_class_method :display
42
+
43
+ def array_diff(expected, actual, path)
44
+ length = [expected.length, actual.length].max
45
+ length.times.flat_map do |index|
46
+ expected_value = expected.fetch(index, MISSING)
47
+ actual_value = actual.fetch(index, MISSING)
48
+ if expected_value.equal?(MISSING) || actual_value.equal?(MISSING)
49
+ [{ "path" => "#{path}[#{index}]", "expected" => display(expected_value),
50
+ "actual" => display(actual_value) }]
51
+ else
52
+ call(expected_value, actual_value, "#{path}[#{index}]")
53
+ end
54
+ end
55
+ end
56
+ private_class_method :array_diff
57
+ end
58
+
59
+ class Comparator
60
+ MODES = %i[strict refinement contract].freeze
61
+
62
+ def initialize(mode: :refinement, float_tolerance: Float::EPSILON)
63
+ @mode = mode.to_sym
64
+ @float_tolerance = float_tolerance
65
+ return if MODES.include?(@mode)
66
+
67
+ raise ConfigurationError,
68
+ "Unknown conformance mode: #{mode}. Use strict, refinement, or contract."
69
+ end
70
+
71
+ def compare(expected, actual)
72
+ if @mode == :contract
73
+ raise ConfigurationError,
74
+ "Contract comparison requires operation contracts. Run it through Verification::Runner."
75
+ end
76
+
77
+ compare_values(expected, actual)
78
+ end
79
+
80
+ def contract? = @mode == :contract
81
+
82
+ private
83
+
84
+ def compare_values(expected, actual, path = "$")
85
+ if expected.is_a?(Float) && actual.is_a?(Float) && ((expected - actual).abs <= @float_tolerance * [
86
+ expected.abs, actual.abs, 1
87
+ ].max)
88
+ return []
89
+ end
90
+ return refinement_diff(expected, actual, path) if @mode == :refinement && expected.is_a?(Hash)
91
+ return Differ.call(expected, actual, path) unless expected.is_a?(Hash) && actual.is_a?(Hash)
92
+
93
+ Differ.call(expected, actual, path)
94
+ end
95
+
96
+ def refinement_diff(expected, actual, path)
97
+ return Differ.call(expected, actual, path) unless actual.is_a?(Hash)
98
+
99
+ expected.flat_map do |key, value|
100
+ next [{ "path" => "#{path}.#{key}", "expected" => value, "actual" => "<missing>" }] unless actual.key?(key)
101
+
102
+ compare_values(value, actual[key], "#{path}.#{key}")
103
+ end
104
+ end
105
+ end
106
+
107
+ class TraceReplay
108
+ def initialize(bundle:, adapter:, mode: nil)
109
+ @bundle = bundle
110
+ @adapter = adapter
111
+ @canonicalization = bundle.fetch("canonicalization", {}).transform_keys(&:to_sym)
112
+ @canonicalizer = Recording::Canonicalizer.new(@canonicalization)
113
+ @comparator = Comparator.new(mode: mode || bundle.fetch("conformance_mode", "refinement"),
114
+ float_tolerance: @canonicalization.fetch(:float_tolerance, Float::EPSILON))
115
+ @refinement_comparator = Comparator.new(mode: :refinement)
116
+ @contract_checker = Formal::ContractChecker.new
117
+ @external_probe = ExternalProbe.new(adapter.externals).install!
118
+ end
119
+
120
+ def run
121
+ Recording::Determinism.apply(@canonicalization)
122
+ results = @bundle.fetch("subjects").flat_map { |subject| replay_subject(subject) }
123
+ unused = @adapter.waivers.keys - results.map(&:id)
124
+ unless unused.empty?
125
+ raise ConfigurationError,
126
+ "Waiver IDs were not found in the Spec Bundle: #{unused.join(', ')}. Remove or correct them."
127
+ end
128
+ results
129
+ ensure
130
+ Recording::Determinism.clear
131
+ end
132
+
133
+ private
134
+
135
+ def replay_subject(spec_subject)
136
+ binding = @adapter.subjects[spec_subject["name"]]
137
+ unless binding
138
+ raise ConfigurationError, "Adapter subject #{spec_subject['name']} is missing. Add it to the adapter file."
139
+ end
140
+
141
+ subjects = {}
142
+ calls = spec_subject.fetch("operations").flat_map do |operation|
143
+ operation.fetch("examples").map { |example| [operation, example] }
144
+ end
145
+ calls.sort_by { |_operation, example| example.fetch("id") }.map do |operation, example|
146
+ provenance = example.dig("provenance", "example_id") || example.fetch("id")
147
+ subject = subjects[provenance] ||= binding.build({})
148
+ replay_example(binding, subject, operation, example)
149
+ end
150
+ end
151
+
152
+ def replay_example(binding, subject, operation_spec, example)
153
+ waiver = @adapter.waivers[example["id"]]
154
+ actual = @canonicalizer.call(invoke(binding, subject, operation_spec["name"], example.fetch("given")))
155
+ differences = if @comparator.contract?
156
+ contract_differences(operation_spec, example, actual)
157
+ else
158
+ @comparator.compare(example.fetch("expect"), actual)
159
+ end
160
+ status = if differences.empty? then :pass
161
+ elsif waiver then :waived
162
+ else :fail
163
+ end
164
+ Result.new(id: example["id"], status:, description: example.dig("provenance", "description"),
165
+ differences:, provenance: example["provenance"], waiver: status == :waived ? waiver : nil)
166
+ end
167
+
168
+ def invoke(binding, subject, operation_name, given)
169
+ operation = binding.operations[operation_name] || Adapter::Operation.new(name: operation_name)
170
+ args = Recording::Serializer.load(given.fetch("args", []))
171
+ kwargs = Recording::Serializer.load(given.fetch("kwargs", {}))
172
+ before_args = Recording::Serializer.dump(args)
173
+ yields = []
174
+ external_calls = @external_probe.capture do
175
+ value = operation.invoke(subject, args, kwargs) { |*items| yields << Recording::Serializer.dump(items) }
176
+ Recording::Serializer.dump(operation.map_return(value))
177
+ end
178
+ {
179
+ "outcome" => { "kind" => "return", "value" => external_calls.fetch(:value) },
180
+ "post_state" => Recording::Serializer.dump(binding.state_projection&.call(subject)),
181
+ "yields" => yields, "external_calls" => external_calls.fetch(:calls),
182
+ "mutated_args" => mutated_indices(before_args, Recording::Serializer.dump(args))
183
+ }
184
+ rescue StandardError => e
185
+ mapped = stringify(operation&.map_error(e) || { class: e.class.name, message: Bparity.exception_message(e) })
186
+ mapped["cause"] = e.cause&.class&.name unless mapped.key?("cause")
187
+ post_state = Recording::Serializer.dump(binding.state_projection&.call(subject))
188
+ { "outcome" => { "kind" => "raise", **mapped }, "post_state" => post_state,
189
+ "yields" => yields || [], "external_calls" => e.instance_variable_get(:@bparity_external_calls) || [],
190
+ "mutated_args" => mutated_indices(before_args || [], Recording::Serializer.dump(args || [])) }
191
+ end
192
+
193
+ def stringify(hash) = hash.to_h { |key, value| [key.to_s, value] }
194
+
195
+ def mutated_indices(before, after)
196
+ [before.length, after.length].max.times.reject { |index| before[index] == after[index] }
197
+ end
198
+
199
+ def contract_differences(operation_spec, example, actual)
200
+ preconditions = operation_spec.fetch("preconditions", [])
201
+ constraints = operation_spec.fetch("postconditions", []) + operation_spec.fetch("invariants", [])
202
+ return @refinement_comparator.compare(example.fetch("expect"), actual) if constraints.empty?
203
+
204
+ context = contract_context(example, actual)
205
+ precondition_violations = @contract_checker.check(preconditions, context)
206
+ errors = precondition_violations.select { |violation| violation["error"] }
207
+ unless errors.empty?
208
+ return errors.map do |violation|
209
+ { "path" => "$.contracts.#{violation['id']}", "expected" => violation["expression"],
210
+ "actual" => violation["error"] }
211
+ end
212
+ end
213
+ return @refinement_comparator.compare(example.fetch("expect"), actual) unless precondition_violations.empty?
214
+
215
+ @contract_checker.check(constraints, context).map do |violation|
216
+ { "path" => "$.contracts.#{violation['id']}", "expected" => violation["expression"],
217
+ "actual" => violation["error"] || false }
218
+ end
219
+ end
220
+
221
+ def contract_context(example, actual)
222
+ outcome = actual.fetch("outcome")
223
+ { result: outcome["kind"] == "return" ? Recording::Serializer.load(outcome["value"]) : nil,
224
+ args: Recording::Serializer.load(example.dig("given", "args") || []),
225
+ kwargs: Recording::Serializer.load(example.dig("given", "kwargs") || {}),
226
+ pre_state: Recording::Serializer.load(example.dig("given", "pre_state")),
227
+ post_state: Recording::Serializer.load(actual["post_state"]) }
228
+ end
229
+ end
230
+
231
+ class ExternalProbe
232
+ THREAD_KEY = :bparity_verification_external_calls
233
+
234
+ def initialize(bindings)
235
+ @bindings = bindings
236
+ @bindings_by_target = bindings.to_h { |binding| [Bparity.constantize(binding.target), binding] }
237
+ end
238
+
239
+ def install!
240
+ @bindings.each { |binding| install(binding) }
241
+ self
242
+ end
243
+
244
+ def capture
245
+ previous = Thread.current[THREAD_KEY]
246
+ context = Thread.current[THREAD_KEY] = { calls: [], bindings: @bindings_by_target }
247
+ value = yield
248
+ { value:, calls: context.fetch(:calls) }
249
+ rescue StandardError => e
250
+ e.instance_variable_set(:@bparity_external_calls, context.fetch(:calls))
251
+ raise
252
+ ensure
253
+ Thread.current[THREAD_KEY] = previous
254
+ end
255
+
256
+ private
257
+
258
+ def install(binding)
259
+ target = Bparity.constantize(binding.target)
260
+ installed = target.instance_variable_get(:@bparity_external_probe_methods) || []
261
+ methods = target.public_instance_methods(false) - installed
262
+ return if methods.empty?
263
+
264
+ target.instance_variable_set(:@bparity_external_probe_methods, installed | methods)
265
+ mod = Module.new
266
+ methods.each do |method_name|
267
+ mod.define_method(method_name) do |*args, **kwargs, &block|
268
+ value = super(*args, **kwargs, &block)
269
+ context = Thread.current[THREAD_KEY]
270
+ current_binding = context&.dig(:bindings, target)
271
+ if current_binding
272
+ raw = { "target" => current_binding.source, "method" => method_name.to_s,
273
+ "args" => Recording::Serializer.dump(args), "kwargs" => Recording::Serializer.dump(kwargs),
274
+ "outcome" => { "kind" => "return", "value" => Recording::Serializer.dump(value) } }
275
+ mapper = current_binding.call_mappers[method_name.to_s]
276
+ context.fetch(:calls) << (mapper ? mapper.call(raw) : raw)
277
+ end
278
+ value
279
+ rescue StandardError => e
280
+ context = Thread.current[THREAD_KEY]
281
+ current_binding = context&.dig(:bindings, target)
282
+ if current_binding
283
+ context.fetch(:calls) << { "target" => current_binding.source, "method" => method_name.to_s,
284
+ "args" => Recording::Serializer.dump(args),
285
+ "kwargs" => Recording::Serializer.dump(kwargs),
286
+ "outcome" => { "kind" => "raise", "class" => e.class.name,
287
+ "message" => Bparity.exception_message(e) } }
288
+ end
289
+ raise
290
+ end
291
+ end
292
+ target.prepend(mod)
293
+ end
294
+ end
295
+
296
+ class Runner
297
+ attr_reader :results
298
+
299
+ def initialize(bundle:, adapter:, mode: nil)
300
+ @replay = TraceReplay.new(bundle:, adapter:, mode:)
301
+ end
302
+
303
+ def run
304
+ @results = @replay.run
305
+ end
306
+
307
+ def success? = results&.none? { |result| result.status == :fail }
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bparity
4
+ VERSION = "0.1.0"
5
+ end
data/lib/bparity.rb ADDED
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "bparity/version"
4
+ require_relative "bparity/errors"
5
+ require_relative "bparity/boundary"
6
+ require_relative "bparity/recording"
7
+ require_relative "bparity/corpus"
8
+ require_relative "bparity/adapter"
9
+ require_relative "bparity/spec_bundle"
10
+ require_relative "bparity/synthesis"
11
+ require_relative "bparity/verification"
12
+ require_relative "bparity/reporting"
13
+ require_relative "bparity/formal"
14
+ require_relative "bparity/adequacy"
15
+
16
+ module Bparity
17
+ class << self
18
+ attr_reader :boundary_definition, :adapter_definition
19
+
20
+ # Defines the observable legacy API.
21
+ def boundary(&block)
22
+ @boundary_definition = Boundary::Definition.new.tap { |definition| definition.instance_eval(&block) }
23
+ end
24
+
25
+ # Maps a specification bundle onto a replacement API.
26
+ def adapter(spec: nil, &block)
27
+ @adapter_definition = Adapter::Definition.new(spec: spec).tap { |definition| definition.instance_eval(&block) }
28
+ end
29
+
30
+ def reset!
31
+ @boundary_definition = @adapter_definition = nil
32
+ end
33
+
34
+ def constantize(name)
35
+ name.split("::").reject(&:empty?).inject(Object) { |scope, part| scope.const_get(part, false) }
36
+ rescue NameError
37
+ raise ConfigurationError, "Cannot find #{name}. Require the target implementation first."
38
+ end
39
+ end
40
+ end
41
+
42
+ BehaviorParity = Bparity unless defined?(BehaviorParity)
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bparity
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: prism
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.0'
26
+ description: Records legacy Ruby behavior as a portable specification bundle and verifies
27
+ replacement implementations.
28
+ email:
29
+ - t.yudai92@gmail.com
30
+ executables:
31
+ - bparity
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".rubocop.yml"
36
+ - LICENSE.txt
37
+ - README.md
38
+ - Rakefile
39
+ - docs/application_example.md
40
+ - docs/formal_assurance_limits.md
41
+ - exe/bparity
42
+ - fixtures/scenarios/01_pure_function/adapter.rb
43
+ - fixtures/scenarios/01_pure_function/boundary.rb
44
+ - fixtures/scenarios/01_pure_function/legacy/dead_gem.rb
45
+ - fixtures/scenarios/01_pure_function/legacy/slugifier.rb
46
+ - fixtures/scenarios/01_pure_function/replacement/broken.rb
47
+ - fixtures/scenarios/01_pure_function/replacement/good.rb
48
+ - fixtures/scenarios/01_pure_function/spec/slugifier_spec.rb
49
+ - fixtures/scenarios/01_pure_function/test/slugifier_test.rb
50
+ - fixtures/scenarios/02_stateful_client/adapter.rb
51
+ - fixtures/scenarios/02_stateful_client/boundary.rb
52
+ - fixtures/scenarios/02_stateful_client/legacy/client.rb
53
+ - fixtures/scenarios/02_stateful_client/replacement/broken.rb
54
+ - fixtures/scenarios/02_stateful_client/replacement/good.rb
55
+ - fixtures/scenarios/02_stateful_client/spec/client_spec.rb
56
+ - fixtures/scenarios/03_external_boundary/adapter.rb
57
+ - fixtures/scenarios/03_external_boundary/boundary.rb
58
+ - fixtures/scenarios/03_external_boundary/legacy/dead_formatter.rb
59
+ - fixtures/scenarios/03_external_boundary/legacy/receipt.rb
60
+ - fixtures/scenarios/03_external_boundary/replacement/broken.rb
61
+ - fixtures/scenarios/03_external_boundary/replacement/good.rb
62
+ - fixtures/scenarios/03_external_boundary/spec/receipt_spec.rb
63
+ - fixtures/scenarios/04_intentional_divergence/adapter.rb
64
+ - fixtures/scenarios/04_intentional_divergence/adapter_unwaived.rb
65
+ - fixtures/scenarios/04_intentional_divergence/boundary.rb
66
+ - fixtures/scenarios/04_intentional_divergence/legacy/dead_identity.rb
67
+ - fixtures/scenarios/04_intentional_divergence/legacy/identity.rb
68
+ - fixtures/scenarios/04_intentional_divergence/replacement/broken.rb
69
+ - fixtures/scenarios/04_intentional_divergence/replacement/good.rb
70
+ - fixtures/scenarios/04_intentional_divergence/spec/identity_spec.rb
71
+ - fixtures/scenarios/05_formal_negative/adapter.rb
72
+ - fixtures/scenarios/05_formal_negative/boundary.rb
73
+ - fixtures/scenarios/05_formal_negative/legacy/dead_lock.rb
74
+ - fixtures/scenarios/05_formal_negative/legacy/turnstile.rb
75
+ - fixtures/scenarios/05_formal_negative/replacement/broken.rb
76
+ - fixtures/scenarios/05_formal_negative/replacement/formal_broken.rb
77
+ - fixtures/scenarios/05_formal_negative/replacement/good.rb
78
+ - fixtures/scenarios/05_formal_negative/spec/turnstile_spec.rb
79
+ - lib/bparity.rb
80
+ - lib/bparity/adapter.rb
81
+ - lib/bparity/adequacy.rb
82
+ - lib/bparity/boundary.rb
83
+ - lib/bparity/cli.rb
84
+ - lib/bparity/cli/formal_commands.rb
85
+ - lib/bparity/cli/verification_commands.rb
86
+ - lib/bparity/corpus.rb
87
+ - lib/bparity/errors.rb
88
+ - lib/bparity/formal.rb
89
+ - lib/bparity/formal/assumptions.rb
90
+ - lib/bparity/formal/bounded.rb
91
+ - lib/bparity/formal/contract.rb
92
+ - lib/bparity/formal/deductive.rb
93
+ - lib/bparity/formal/lts.rb
94
+ - lib/bparity/formal/result.rb
95
+ - lib/bparity/recording.rb
96
+ - lib/bparity/reporting.rb
97
+ - lib/bparity/spec_bundle.rb
98
+ - lib/bparity/synthesis.rb
99
+ - lib/bparity/verification.rb
100
+ - lib/bparity/version.rb
101
+ homepage: https://github.com/ydah/bparity
102
+ licenses:
103
+ - MIT
104
+ metadata:
105
+ allowed_push_host: https://rubygems.org
106
+ homepage_uri: https://github.com/ydah/bparity
107
+ rubygems_mfa_required: 'true'
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: 3.1.0
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubygems_version: 4.0.19
123
+ specification_version: 4
124
+ summary: Capture and verify observable Ruby behavior
125
+ test_files: []