Class: Insika::Safety::GroundingEnforcer
- Inherits:
-
Object
- Object
- Insika::Safety::GroundingEnforcer
- Defined in:
- lib/insika/safety/grounding_enforcer.rb
Overview
— the :enforce half of grounding: the claim's SENTENCE is
cut from the content the turn persists and delivers. A NEW stage-8 boundary
step (a plain executor call, NOT an after_task hook — after_task fires too
late to change what is persisted).
The cut removes the SENTENCE that contains an ungrounded claim (never a
surgical span — removing half a sentence produces broken prose). What the
cut CANNOT do is unsay a streamed delta — for a streaming surface :enforce
still delivers the cut text via channel delivery and flags the turn; that
honesty is why the own default is :flag (ship :enforce only after
the matcher audit). Runs ONLY in :enforce mode — inert otherwise.
Instance Method Summary collapse
-
#call(task, state, content) ⇒ Object
-> [new_content, state].
Instance Method Details
#call(task, state, content) ⇒ Object
-> [new_content, state]. The stage-8 boundary call. Fail-open: grounding must never break a completed turn.
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_enforcer.rb', line 19 def call(task, state, content) grounding = grounding_of(state) return [content, state] unless grounding&.enforce? ledger = state.respond_to?(:evidence_ledger) ? state.evidence_ledger : nil return [content, state] unless ledger text = content.to_s refs = grounding.matcher.references(text) claims = grounding.matcher.ungrounded(refs, evidence_ids: ledger.ids) return [content, state] if claims.empty? cut = cut_sentences(text, claims) claims.each { |c| ledger.ungrounded_count(c) } state.guardrail_flags = Array(state.guardrail_flags) + [{ category: "ungrounded", source: "evidence", action: "cut", detail: "cut claim(s): #{claims.join(', ')}" }] [cut, state] rescue StandardError [content, state] # fail-open: grounding must never break a completed turn end |