Features · Installation · Quick Start · How It Works · Formal Assurance · Commands
bparity records the observable behavior of a legacy Ruby API as a portable Specification Bundle, then checks a replacement through the same behavioral boundary. The replacement may use completely different classes, methods, and argument shapes, and verification does not need the retired dependency.
Features
- Records arguments, returns, errors, yields, mutations, state, and external calls
- Extracts specification evidence from RSpec, Minitest, and Ruby source
- Maps structurally different replacements through a small Adapter DSL
- Replays recorded traces and checks generated property inputs
- Compares finite-state models and emits distinguishing sequences
- Performs bounded exhaustive checks and opt-in Z3 proofs
- Reports structured diffs, provenance, waivers, assumptions, and residual risks
- Exports Markdown, JSON, JUnit XML, and HTML reports
Installation
Add the GitHub source to your Gemfile:
gem "bparity", github: "ydah/bparity", branch: "main"Then install:
bundle installRequirements
- Ruby 3.1+
- RSpec or Minitest in the legacy environment
- Z3 only when running F4 proofs
-
mutantonly when requesting mutation analysis
Quick Start
1. Preserve the legacy environment
Do this first while the retired dependency still runs:
bundle package --all
bundle exec bparity init --timecapsuleCommit vendor/cache, .bparity/timecapsule, the behavior corpus, and the generated Specification Bundle.
2. Define the observation boundary
Edit .bparity/boundary.rb:
Bparity.boundary do
observe "Legacy::Slugifier" do
methods :call
end
driver :rspec, files: "spec/**/*_spec.rb"
endTo discover candidate public methods from an exercise script:
bundle exec bparity discover \
--require lib/legacy.rb \
--target Legacy::Slugifier \
script/exercise_legacy.rb3. Record and synthesize
Run the legacy test suite once, then freeze the result as a checked bundle:
bundle exec bparity record --require lib/legacy.rb
bundle exec bparity synthesize \
--coverage .bparity/coverage.json \
--tests 'spec/**/*_spec.rb' \
--source 'lib/**/*.rb'If the legacy runtime is already unavailable, extract static assertions instead:
bundle exec bparity synthesize --static-only \
--tests 'spec/**/*_spec.rb' \
--source 'lib/**/*.rb'Static-only bundles mark the missing runtime evidence as a gap.
4. Map the replacement
Edit .bparity/adapter.rb when the new API differs from the legacy API:
Bparity.adapter(spec: ".bparity/spec_bundle.yml") do
subject "Slugifier" do
construct { NewSlugService }
operation "#call" do
invoke { |service, args, _kwargs| service.generate(text: args.fetch(0)) }
end
end
end5. Verify
The replacement-side command needs only the bundle, Adapter, and replacement:
bundle exec bparity verify --require lib/new_slug_service.rbRun additional conformance runners when their inputs are available:
bundle exec bparity verify \
--require lib/new_slug_service.rb \
--runners replay,property,model \
--format html \
--out tmp/bparity.html \
--fail-under 100For same-named classes with compatible public methods, direct replay works without an Adapter file. An explicitly supplied but missing Adapter path is always an error.
How It Works
- Discovery identifies the public legacy boundary.
- Recording captures observable behavior while the original tests run.
- Synthesis combines runtime traces, static assertions, invariants, coverage gaps, and finite-state models.
- Adaptation maps abstract operations to the replacement API.
- Verification replays examples and generated inputs against the replacement.
- Reporting shows differences, provenance, formal scope, assumptions, waivers, and remaining risk.
The Specification Bundle is implementation-independent and protected by a checksum. Intentional incompatibilities must be declared as Adapter waivers; editing the bundle to hide a difference is rejected.
Formal Assurance
| Level | Check | Claim |
|---|---|---|
| F0 | Trace replay | Recorded examples match |
| F1 | Contract and property checks | Executed inputs satisfy declared predicates |
| F2 | Bounded exhaustive comparison | No difference exists in the reported finite domain |
| F3 | Finite-state model comparison | Projected learned models conform |
| F4 | Translation-validated Z3 proof | No difference exists in the supported pure fragment under reported assumptions |
Formal results use no_difference_found, difference_found, or inconclusive. They always include scope, assumptions, and excluded behavior. Timeouts, solver unknown, truncated domains, and translation mismatches never become PASS results.
Bounded exhaustive comparison
bundle exec bparity prove --level f2 --scope size=3,depth=2 \
--require lib/legacy.rb \
--require lib/replacement.rb \
--counterexample-out spec/f2_counterexample_spec.rb \
--promote-invariantsIf the legacy class is unavailable, F2 can check the replacement against declared postconditions and invariants. Case or time limits make the result inconclusive, never exhaustive.
Finite-state comparison
bundle exec bparity prove --level f3 --equivalence trace \
--require lib/replacement.rb \
--export-lts tmp/client \
--counterexample-out spec/f3_counterexample_spec.rbF3 reports model sizes, the learned alphabet, exploration completeness, and the shortest distinguishing sequence. Its claim applies to the projected models, not the complete implementations.
Z3 proof
bundle exec bparity prove --level f4 --solver z3 --validate-translation \
--old-source lib/legacy_math.rb --old-method double \
--new-source lib/new_math.rb --new-method twice \
--types Integer \
--require lib/legacy_math.rb \
--require lib/new_math.rb \
--counterexample-out spec/f4_counterexample_spec.rbTranslation validation is mandatory. The supported fragment covers pure Integer, Boolean, and String expressions with conditionals. See Formal assurance limits for the exact boundaries.
Commands
| Command | Purpose |
|---|---|
bparity init |
Generate boundary and Adapter templates |
bparity discover |
Suggest a boundary from observed Ruby calls |
bparity record |
Capture behavior from the legacy test suite |
bparity synthesize |
Build a checked Specification Bundle |
bparity verify |
Run replay, property, model, or differential checks |
bparity prove |
Run F2, F3, or F4 formal checks |
bparity adequacy |
Report coverage, formal reach, and optional mutation strength |
bparity diff |
Compare two Specification Bundles |
bparity explain |
Show the provenance of one specification item |
bparity assumptions |
List verification assumptions and enforcement |
Run the five-point adequacy assessment with:
bundle exec bparity adequacy --require lib/replacement.rb
bundle exec bparity adequacy --require lib/replacement.rb --mutantDevelopment
bundle install
bundle exec rakeThe acceptance suite records, synthesizes, and verifies five fixtures with both correct and intentionally broken replacements:
bundle exec rspec spec/acceptance_spec.rbContributing
Bug reports and pull requests are welcome at https://github.com/ydah/bparity.
Landing page
The GitHub Pages site lives in site/. Build and preview it locally:
cd site
npm ci
npm run build
python3 -m http.server 8000 --directory distOpen http://localhost:8000. The page is static HTML with compiled Tailwind CSS; it does not need JavaScript in the browser.
In the repository's Settings → Pages, select GitHub Actions as the build
source. The GitHub Pages workflow validates pull requests and deploys site
changes on main to https://ydah.github.io/bparity/. It can also be run manually.
License
Released under the MIT License.