1
0
Fork 0
llm-ingestion-pipeline-secu.../src/llm_ingestion_guard/grounding.py
Kjell Tore Guttormsen 1e63643157 feat(grounding): SourceGroundingCheck protocol + pass-through default — the semantic-poisoning seam (TDD) [skip-docs]
Module 10 of the build order — interface only in the stdlib core (PLAN §67/§94,
BRIEF §7/§10). lexicon and entropy see surface signals; they are structurally
blind to semantic poisoning — a factually false or subtly biased claim, in clean
prose, carrying no suspicious token. Catching that needs grounding against a
source of truth (embedding classifier, retrieval check, LLM judge) — a model
call, and models never live in this core.

So this module ships exactly two things and no detector logic:
- SourceGroundingCheck: the text -> Report protocol a [judge] implementation
  must satisfy to plug in. runtime_checkable for a coarse callable-vs-not
  isinstance gate; a conforming impl may be a plain function or a stateful
  callable holding a retriever/client — the grounding source of truth is the
  impl's concern, never a seam parameter. Its findings flow through disposition
  like any other detector's, with zero grounding-specific plumbing.
- no_grounding_check (bound as DEFAULT_GROUNDING_CHECK): the pass-through
  default returning an empty Report. The honest scope note made structural —
  semantic poisoning is not solved at the text layer, and the core does not
  pretend to.

Design choice, tested: the default returns NO finding (not an INFO "unchecked"
marker). An INFO finding would flip every artifact to found and trip the
quarantine_default floor in disposition, quarantining every upload for a check
that never ran. The seam stays silent; the honesty lives in the contract.

8 new tests (pass-through empties, silence on semantic poison, protocol
conformance incl. a stateful judge stub, and a composition test proving a
plugged-in check's HIGH finding fails secure through disposition unchanged);
169 green total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyRCQMocjZ6SmSQ6JidJ2k
2026-07-04 22:44:41 +02:00

71 lines
3.4 KiB
Python

"""grounding — the seam for semantic / factual poisoning (BRIEF §7/§10, PLAN §67/§94).
Interface only in the stdlib core. :mod:`lexicon` and :mod:`entropy` see
*surface* signals — known injection phrasing, obfuscation, invisible carriers.
They are structurally blind to **semantic poisoning**: a factually false or
subtly biased claim, in clean prose, carrying no suspicious token. Catching that
means grounding the content against a source of truth — an embedding classifier,
a retrieval check, an LLM judge — which is a model call, and models never live
in this core (BRIEF §7: ML detectors are optional, pluggable extras behind the
``[judge]`` extra).
So this module ships exactly two things and no detector logic:
* :class:`SourceGroundingCheck` — the ``text -> Report`` protocol a ``[judge]``
implementation must satisfy to plug into a pipeline. Its findings then flow
through :mod:`disposition` like any other detector's, with no
grounding-specific plumbing.
* :func:`no_grounding_check` (bound as :data:`DEFAULT_GROUNDING_CHECK`) — the
shipped default: a pass-through returning an empty Report. The honest scope
note made structural — *semantic poisoning is not solved at the text layer*,
and the deterministic core does not pretend to. A pipeline that needs it
installs a ``[judge]`` implementation; one that does not gets a safe no-op that
never breaks composition.
The default returns *no* finding — not an INFO "unchecked" marker — on purpose.
An INFO finding would flip every artifact to :attr:`Report.found` and trip the
``quarantine_default`` floor in :mod:`disposition`, quarantining every upload for
a check that never ran. The seam stays silent; the honesty lives here in the
contract, not in a finding that pollutes the gate.
"""
from __future__ import annotations
from typing import Protocol, runtime_checkable
from .report import Report, Source
@runtime_checkable
class SourceGroundingCheck(Protocol):
"""A semantic-grounding detector: ``text -> Report`` (BRIEF §7/§10).
Structurally identical to the other detectors so its findings compose with
:mod:`disposition` unchanged. A conforming ``[judge]`` implementation may be
a plain function or a stateful callable (an object holding a model client or
a trusted-corpus retriever) — the grounding *source of truth* is the
implementation's concern, never a parameter of this seam.
Runtime-checkable for a coarse ``isinstance`` structural gate (callable vs
not); the intended ``source`` keyword and return type are enforced by a type
checker, not at runtime.
"""
def __call__(self, text: str, *, source: Source = Source.OUTPUT) -> Report:
...
def no_grounding_check(text: str, *, source: Source = Source.OUTPUT) -> Report:
"""The pass-through default (PLAN §94): return an empty :class:`Report`.
The stdlib core cannot judge semantics, so it reports nothing rather than
guessing. This is deliberate and **not** an assertion that ``text`` is
semantically clean — only that no grounding check ran. Install a ``[judge]``
:class:`SourceGroundingCheck` to actually verify factual grounding.
"""
return Report()
DEFAULT_GROUNDING_CHECK: SourceGroundingCheck = no_grounding_check
"""The grounding check the core ships: :func:`no_grounding_check`. A pipeline
rebinds this to a ``[judge]`` :class:`SourceGroundingCheck` when it needs
semantic grounding."""