Module: RailVerdict::Repair::ContextAssembler

Defined in:
lib/rail_verdict/repair/context_assembler.rb

Defined Under Namespace

Classes: StaleFindingError

Constant Summary collapse

MAX_PACKET_BYTES =
256 * 1024

Class Method Summary collapse

Class Method Details

.build(outcome:, finding_ref:, repository_root: nil, runner: nil, ai_analysis: nil, clock: Time.now.utc) ⇒ Object

Raises:

  • (ArgumentError)


13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/rail_verdict/repair/context_assembler.rb', line 13

def self.build(outcome:, finding_ref:, repository_root: nil, runner: nil, ai_analysis: nil, clock: Time.now.utc)
  raise ArgumentError, "outcome required" unless outcome
  raise ArgumentError, "finding_ref required" if finding_ref.to_s.strip.empty?

  findings = outcome.findings || []
  target = findings.find { |f| f.id == finding_ref || f.fingerprint == finding_ref }
  raise StaleFindingError, "finding not found: #{finding_ref}" unless target

  configuration = outcome.configuration
  result = outcome.result
  context = outcome.context

  root = repository_root || context&.repository_root || Dir.pwd
  runner ||= ProcessRunner.new

  blocking = blocking_for(result, target)
  finding_state = target.state

  analyzer_result = analyzer_result_for(result, target)
  evidence = build_evidence(target, analyzer_result)
  git_ctx = build_git_context(result, context, target)
  rails = build_rails_context(result)
  source_ctx = build_source_context(root, target)
  diff = build_diff(root, runner, target, context)

  verification = build_verification(result, configuration)
  policy = {
    "mode" => configuration&.mode || "strict",
    "configuration_digest" => configuration&.digest || Digest::SHA256.hexdigest("")
  }
  baseline_state = build_baseline_state(root, configuration)
  waivers_state = build_waivers_state(root, configuration)
  boundary = Boundary.snapshot(repository_root: root, configuration: configuration, outcome: outcome)
  base_for_plan = begin; result&.git&.fetch("base", nil); rescue StandardError; nil; end
  verification_plan = VerificationPlan.build(outcome: outcome, base_revision: base_for_plan)
  constraints = Constraints.default
  instructions = Constraints.instructions

  source_revision = context&.revision
  base_revision = begin; result&.git&.fetch("base", nil); rescue StandardError; nil; end
  merge_base = begin; result&.git&.fetch("merge_base", nil); rescue StandardError; nil; end

  analyzer_versions = context&.analyzer_versions || {}
  baseline_digest = boundary["baseline_digest"]
  packet_id = Packet.packet_id_for(
    fingerprint: target.fingerprint,
    source_revision: source_revision,
    base_revision: base_revision,
    configuration_digest: policy["configuration_digest"],
    baseline_digest: baseline_digest,
    analyzer_versions: analyzer_versions
  )

  truncated = source_ctx["truncated"] || diff["truncated"] || rails["truncated"] || false
  completeness = {
    "deterministic" => "complete",
    "repair_context" => truncated ? "partial" : "complete",
    "truncated" => truncated
  }
  gate = result&.gate || "FAIL"
  success = {
    "description" => "Target finding no longer blocking and gate is PASS/WARN with complete verification and unchanged boundary.",
    "gate_must_be" => %w[PASS WARN],
    "target_must_be_fixed" => true,
    "completion_must_be" => "complete",
    "boundary_must_be_unchanged" => true
  }

  packet_hash = {
    "schema_version" => Packet::SCHEMA_VERSION,
    "packet_id" => packet_id,
    "created_at" => clock.utc.iso8601,
    "railverdict_version" => RailVerdict::VERSION,
    "source_revision" => source_revision,
    "target" => {
      "finding" => target.to_schema_h,
      "finding_state" => finding_state,
      "blocking" => blocking,
      "severity" => target.severity
    },
    "verification" => verification,
    "evidence" => evidence,
    "git_context" => git_ctx,
    "diff_context" => diff,
    "rails_context" => rails,
    "source_context" => source_ctx,
    "policy" => policy,
    "baseline_state" => baseline_state,
    "waivers_state" => waivers_state,
    "verification_plan" => verification_plan,
    "constraints" => constraints,
    "instructions" => instructions,
    "completeness" => completeness,
    "success_criteria" => success,
    "boundary" => boundary
  }
  packet_hash["base_revision"] = base_revision unless base_revision.nil?
  packet_hash["merge_base"] = merge_base unless merge_base.nil?
  packet_hash["ai_analysis"] = ai_analysis.to_h if ai_analysis

  errors = SchemaValidator.validate_repair_packet(packet_hash)
  raise ArgumentError, "invalid packet: #{errors.join('; ')}" unless errors.empty?

  if JSON.generate(packet_hash).bytesize > MAX_PACKET_BYTES
    packet_hash["source_context"] = { "snippets" => packet_hash["source_context"]["snippets"].first(1), "truncated" => true }
    packet_hash["diff_context"] = { "hunk" => packet_hash["diff_context"]["hunk"].byteslice(0, 4096).to_s, "truncated" => true }
    packet_hash["completeness"] = { "deterministic" => "complete", "repair_context" => "partial", "truncated" => true }
  end

  Packet.new(
    packet_id: packet_hash["packet_id"],
    created_at: packet_hash["created_at"],
    railverdict_version: packet_hash["railverdict_version"],
    source_revision: packet_hash["source_revision"],
    base_revision: packet_hash["base_revision"],
    merge_base: packet_hash["merge_base"],
    target: packet_hash["target"],
    verification: packet_hash["verification"],
    evidence: packet_hash["evidence"],
    git_context: packet_hash["git_context"],
    diff_context: packet_hash["diff_context"],
    rails_context: packet_hash["rails_context"],
    source_context: packet_hash["source_context"],
    policy: packet_hash["policy"],
    baseline_state: packet_hash["baseline_state"],
    waivers_state: packet_hash["waivers_state"],
    ai_analysis: packet_hash["ai_analysis"],
    verification_plan: packet_hash["verification_plan"],
    constraints: packet_hash["constraints"],
    instructions: packet_hash["instructions"],
    completeness: packet_hash["completeness"],
    success_criteria: packet_hash["success_criteria"],
    boundary: packet_hash["boundary"]
  )
end