Class: Insika::Safety::GroundingValidator

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/safety/grounding_validator.rb

Overview

— the :flag half of grounding. A reply claiming a product whose reference is not in the evidence ledger is FLAGGED (audit via the existing :guardrail_flagged channel) — the text is already out, so flagging is audit, exactly like every other output flag.

Runs as the FIRST step of the OutputValidator's after_task chain, BEFORE the config.output gate (D9): grounding is evidence integrity, independent of the guardrails opt-in — an agent with guardrails off and grounding.mode: :flag still gets the check. Fail-open on any error (grounding must never break a completed turn).

Instance Method Summary collapse

Instance Method Details

#call(state) ⇒ Object

-> state (the after_task contract). Appends :ungrounded flags.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/insika/safety/grounding_validator.rb', line 17

def call(state)
  grounding = grounding_of(state)
  # :flag only — :enforce is the enforcer's job, :off / absent is nothing.
  return state if grounding.nil? || grounding.off? || grounding.enforce?

  ledger = state.respond_to?(:evidence_ledger) ? state.evidence_ledger : nil
  return state unless ledger

  text = state.response_content.to_s
  return state if text.empty?

  refs = grounding.matcher.references(text)
  claims = grounding.matcher.ungrounded(refs, evidence_ids: ledger.ids)
  claims.each { |c| ledger.ungrounded_count(c) }
  unless claims.empty?
    state.guardrail_flags = Array(state.guardrail_flags) + [{
      category: "ungrounded", source: "evidence",
      detail: "product claim without tool evidence: #{claims.join(', ')}" }]
  end
  state
rescue StandardError
  state # fail-open
end