Class: Bundler::Spinel::Verifier

Inherits:
Object
  • Object
show all
Defined in:
lib/bundler/spinel/verifier.rb

Overview

The ‘verified` rung: differential testing. Runs a smoke program that exercises the gem once under CRuby and once compiled by Spinel, and compares stdout. This is the only signal that catches Spinel’s silent miscompiles (local-var-name collapse, Int-0-as-nil) — they emit no warning and exit 0, so the cheap probe can’t see them, but a differential run does: CRuby and the miscompiled binary diverge.

match, behaviour smoke -> verified  (CRuby and Spinel agree on real use)
match, require-only     -> loaded    (loads+runs identically; logic untested)
mismatch                -> rejected  (reason: miscompile, with a short diff)
no build                -> rejected  (reason: build-error / run-error)

The smoke is the unit of trust. The require-only default smoke only proves the gem loads identically (‘loaded`); pass `–smoke FILE` (a snippet that drives the gem’s API and prints deterministic output) to actually verify behaviour (‘verified`). Verification is only as good as the smoke — which is why it’s opt-in and human-supplied. (A ‘loaded` gem can still silently miscompile in logic the require-only smoke never ran — observed in practice.)

Constant Summary collapse

HARNESS =
"__spinel_verify.rb".freeze

Instance Method Summary collapse

Constructor Details

#initialize(engine, ledger) ⇒ Verifier

Returns a new instance of Verifier.



26
27
28
29
# File 'lib/bundler/spinel/verifier.rb', line 26

def initialize(engine, ledger)
  @engine = engine
  @ledger = ledger
end

Instance Method Details

#verify(gem_name, version, dir, smoke: nil, full: false) ⇒ Object

full: when true, the harness force-requires every .rb under the gem’s lib/ (not just the entrypoint) before the smoke body. This defeats the ‘autoload`/lazy-`require` masking that lets a constant-only smoke pass `verified` while the gem’s real surface (client/transport/serialization) never compiled — the qdrant-ruby spike (spinelgems#4). The verdict vocabulary is unchanged; the probe is tagged ‘verify-full` so the whole-surface signal stays distinguishable in the ledger from the entrypoint-only `verify`.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/bundler/spinel/verifier.rb', line 39

def verify(gem_name, version, dir, smoke: nil, full: false)
  @engine.ensure!
  harness = File.join(dir, HARNESS)
  File.write(harness, harness_source(gem_name, dir, smoke, full))

  ruby_out, ruby_err, ruby_ok = run_ruby(harness, dir)
  spin_out, spin_err, spin_ok = run_spinel(harness)

  verdict, reasons = classify(ruby_ok, spin_ok, ruby_out, spin_out, spin_err, behavior: !smoke.nil?)
  # Tag *why* (the spinelgems#4 usability rubric) so a failure says what it'd
  # take, not just "rejected". Prepended to reasons as `rubric:<tag>`.
  unless verdict == "verified" || verdict == "loaded" || verdict == "clean"
    reasons = ["rubric:#{rubric(ruby_ok, ruby_err, spin_ok, spin_err, ruby_out, spin_out, gem_name)}"] + reasons
  end
  @ledger.record(@ledger.build(
    gem: gem_name, version: version, rev: @engine.rev,
    verdict: verdict, reasons: reasons, probe: full ? "verify-full" : "verify"
  ))
ensure
  File.delete(harness) if harness && File.exist?(harness)
end