Module: ActsAsTbackend::ShadowComparison

Defined in:
lib/acts_as_tbackend/shadow_comparison.rb

Class Method Summary collapse

Class Method Details

.execute_comparison(contract:, inputs:, result:, **opts) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/acts_as_tbackend/shadow_comparison.rb', line 38

def execute_comparison(contract:, inputs:, result:, **opts)
  return unless ActsAsTbackend.enabled?

  host = opts[:host] || "127.0.0.1"
  port = opts[:port] || 7401

  begin
    # 1. Locate the compiled contract JSON under igniter-compiler/out/
    contract_filename = "#{contract.gsub(/(.)([A-Z])/, '\1_\2').downcase}.json"
    search_pattern = File.expand_path("../../../../igniter-compiler/out/*/contracts/#{contract_filename}", __FILE__)
    contract_path = Dir.glob(search_pattern).first

    unless contract_path && File.exist?(contract_path)
      # Also search fallback in Out conformance test directory or igniter-vm test fixtures
      fallback_pattern = File.expand_path("../../../../igniter-compiler/out_conformance_test/*/contracts/#{contract_filename}", __FILE__)
      contract_path = Dir.glob(fallback_pattern).first
    end

    raise "Could not locate compiled contract JSON for #{contract}" unless contract_path

    # 2. Write inputs to a temp file
    temp_inputs = Tempfile.new(["inputs", ".json"])
    temp_inputs.write(JSON.generate(inputs))
    temp_inputs.close

    # 3. Locate VM CLI binary and prepare command
    vm_bin = File.expand_path("../../../../igniter-vm/target/release/igniter-vm", __FILE__)
    unless File.exist?(vm_bin)
      # Check debug target fallback
      vm_bin = File.expand_path("../../../../igniter-vm/target/debug/igniter-vm", __FILE__)
    end

    cmd = [
      vm_bin, "run",
      "--contract", contract_path,
      "--inputs", temp_inputs.path,
      "--json",
      "-b", "#{host}:#{port}"
    ]

    # 4. Execute VM CLI
    start_time = Time.now
    stdout, stderr, status = Open3.capture3(*cmd)
    latency_ms = ((Time.now - start_time) * 1000).round(2)

    temp_inputs.unlink # Clean up temp inputs file

    # 5. Parse output and perform comparison
    if status.success?
      response = JSON.parse(stdout, symbolize_names: true)
      if response[:status] == "success"
        vm_result = response[:result]
        matched = results_match?(result, vm_result)
        delta = matched ? nil : compute_delta(result, vm_result)

        # Commit result fact to TBackend
        payload = {
          contract_name: contract,
          inputs_hash: Digest::SHA256.hexdigest(JSON.generate(inputs)),
          crm_result: result,
          igniter_result: vm_result,
          matched: matched,
          delta_json: delta,
          latency_ms: latency_ms,
          executed_at: Time.now.iso8601
        }

        client = ActsAsTbackend.client(host, port)
        client.write_fact(
          store: "shadow_results",
          key: SecureRandom.uuid,
          value: payload
        )
      else
        log_error("VM reported execution failure: #{response[:error]}", stderr)
      end
    else
      log_error("VM CLI exited with status #{status.exitstatus}", stderr)
    end
  rescue => e
    log_error("Error in ShadowComparison: #{e.message}\n#{e.backtrace.join("\n")}")
  end
end

.submit_crm_result(contract:, inputs:, result:, **opts) ⇒ Object

Submits a CRM result to be asynchronously compared against the Igniter VM execution.

contract - String name of the contract (e.g., "BidSummary") inputs - Hash of input values (matching the contract's inputs signature) result - The result computed by the CRM (to compare against) opts - Hash of options (e.g., :host, :port)



23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/acts_as_tbackend/shadow_comparison.rb', line 23

def submit_crm_result(contract:, inputs:, result:, **opts)
  return unless ActsAsTbackend.enabled?

  host = opts[:host] || "127.0.0.1"
  port = opts[:port] || 7401

  job_args = {
    contract: contract,
    inputs: inputs,
    result: result,
    opts: { host: host, port: port }
  }
  ActsAsTbackend.enqueue_job("shadow_comparison", job_args)
end