Skip to main content

A deterministic trust gate for LLM systems

Project description

jingu-trust-gate

LLM output is untrusted input. jingu-trust-gate decides what is allowed to become trusted system state.

Python SDK for jingu-trust-gate — deterministic admission control layer for LLM systems.

Install

pip install jingu-trust-gate

Requires Python 3.11+. Zero runtime dependencies.

The problem

LLMs do not distinguish between what is known and what is guessed. In a RAG pipeline, this means the LLM can assert facts not in your data, over-specify beyond what evidence supports, or silently resolve conflicting sources — with no way to detect or audit it.

jingu-trust-gate inserts a deterministic gate between LLM output and your trusted context. Only claims provably supported by evidence are allowed through. Every decision is written to an audit log.

Quick start

import asyncio
from dataclasses import dataclass
from jingu_trust_gate import (
    create_trust_gate, GatePolicy, Proposal, SupportRef,
    UnitWithSupport, UnitEvaluationResult, AdmittedUnit,
    VerifiedContext, VerifiedContextSummary, VerifiedBlock,
    StructureValidationResult, ConflictAnnotation,
    RetryFeedback, RenderContext, RetryContext,
    AuditEntry, AuditWriter,
)

@dataclass
class MyClaim:
    id: str
    text: str
    grade: str
    evidence_refs: list[str]

class MyPolicy(GatePolicy[MyClaim]):
    def validate_structure(self, proposal):
        return StructureValidationResult(valid=len(proposal.units) > 0, errors=[])

    def bind_support(self, unit, pool):
        matched = [s for s in pool if s.source_id in unit.evidence_refs]
        return UnitWithSupport(unit=unit, support_ids=[s.id for s in matched], support_refs=matched)

    def evaluate_unit(self, uws, ctx):
        if uws.unit.grade == "proven" and not uws.support_ids:
            return UnitEvaluationResult(unit_id=uws.unit.id, decision="reject", reason_code="MISSING_EVIDENCE")
        return UnitEvaluationResult(unit_id=uws.unit.id, decision="approve", reason_code="OK")

    def detect_conflicts(self, units, pool):
        return []

    def render(self, admitted_units, pool, ctx):
        blocks = [VerifiedBlock(source_id=u.unit_id, content=u.unit.text) for u in admitted_units]
        return VerifiedContext(
            admitted_blocks=blocks,
            summary=VerifiedContextSummary(admitted=len(blocks), rejected=0, conflicts=0),
        )

    def build_retry_feedback(self, unit_results, ctx):
        failed = [r for r in unit_results if r.decision == "reject"]
        return RetryFeedback(
            summary=f"{len(failed)} claim(s) rejected",
            errors=[],
        )

class NoopAuditWriter(AuditWriter):
    async def append(self, entry: AuditEntry) -> None:
        pass

async def main():
    gate = create_trust_gate(policy=MyPolicy(), audit_writer=NoopAuditWriter())

    support_pool = [
        SupportRef(id="ref-1", source_id="doc-1", source_type="observation", attributes={}),
    ]
    proposal = Proposal(
        id="prop-1", kind="response",
        units=[
            MyClaim(id="u1", text="Fact with evidence", grade="proven", evidence_refs=["doc-1"]),
            MyClaim(id="u2", text="Hallucinated fact",  grade="proven", evidence_refs=[]),
        ],
    )

    result  = await gate.admit(proposal, support_pool)
    context = gate.render(result)   # VerifiedContext → pass to LLM API
    summary = gate.explain(result)  # GateExplanation(approved, downgraded, rejected, ...)

    print(f"approved={summary.approved}, rejected={summary.rejected}")
    # approved=1, rejected=1

asyncio.run(main())

GatePolicy interface

Implement all six methods. None may call an LLM.

Method What it does
validate_structure Is the proposal well-formed? (required fields, non-empty, etc.)
bind_support Which evidence from the pool applies to this claim?
evaluate_unit Should this claim be approved, downgraded, or rejected?
detect_conflicts Do any claims contradict each other?
render Serialize admitted claims into VerifiedContext.
build_retry_feedback When gate rejects, what structured feedback should the LLM receive?

Unit status

Status Meaning Gate action
approved Claim has evidence, nothing over-asserted Passes through
downgraded Claim more specific than evidence supports Admitted with reduced grade + unsupported_attributes
rejected No evidence, or categorically unsafe Blocked — never reaches LLM context
approved_with_conflict Has evidence but contradicts another claim Admitted with conflict_note

blocking conflicts force-reject all involved units — admitted_blocks is empty, LLM receives only instructions. informational conflicts admit both with conflict_note.

Adapters

VerifiedContext is abstract. Implement ContextAdapter[T] to convert it to your LLM API's wire format:

from jingu_trust_gate import ContextAdapter, VerifiedContext

class MyAdapter(ContextAdapter[list[dict]]):
    def adapt(self, context: VerifiedContext) -> list[dict]:
        return [{"role": "user", "content": b.content} for b in context.admitted_blocks]

Reference implementations for Claude, OpenAI, and Gemini are in examples/adapter_examples.py.

Examples

Five runnable domain policies in examples/. Each shows a real scenario and what the gate catches that a plain RAG pipeline would not.

File Domain Key reason codes
medical_symptom_policy.py Health assistant DIAGNOSIS_UNCONFIRMED, TREATMENT_NOT_ADVISED, OVER_CERTAIN
legal_contract_policy.py Contract review TERM_NOT_IN_EVIDENCE, OVER_SPECIFIC_FIGURE, SCOPE_EXCEEDED
hpc_diagnostic_policy.py GPU cluster SRE UNSUPPORTED_SEVERITY, UNSUPPORTED_SCOPE, OVER_SPECIFIC_METRIC
ecommerce_catalog_policy.py Product chatbot UNSUPPORTED_FEATURE, OVER_SPECIFIC_STOCK, STOCK_CONFLICT
bi_analytics_policy.py BI assistant VALUE_MISMATCH, PERIOD_MISMATCH, DIMENSION_MISMATCH, METRIC_CONFLICT
python examples/medical_symptom_policy.py
python examples/legal_contract_policy.py
python examples/hpc_diagnostic_policy.py
python examples/ecommerce_catalog_policy.py
python examples/bi_analytics_policy.py

Three iron laws

  1. Gate Engine: zero LLM calls — all four gate steps are deterministic code. No AI judging AI.
  2. Policy is injected — the gate core has zero business logic. Domain rules live entirely in GatePolicy.
  3. Every admission is audited — append-only JSONL at .jingu-trust-gate/audit.jsonl.

TypeScript SDK

The TypeScript SDK (npm install jingu-trust-gate) is the reference implementation. Both SDKs are API-compatible — the same GatePolicy design, same pipeline, same type names.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jingu_trust_gate-0.1.5.tar.gz (25.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jingu_trust_gate-0.1.5-py3-none-any.whl (15.5 kB view details)

Uploaded Python 3

File details

Details for the file jingu_trust_gate-0.1.5.tar.gz.

File metadata

  • Download URL: jingu_trust_gate-0.1.5.tar.gz
  • Upload date:
  • Size: 25.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for jingu_trust_gate-0.1.5.tar.gz
Algorithm Hash digest
SHA256 e1519080aa2d563c7543c531e28c5dd3142ac6d8070e4b9340c758d9a4ebc670
MD5 f2cd00ad32609f7349f40f135d9dbd7a
BLAKE2b-256 e156d1b128cc53c953ad9f112db02f92732b1889a9124040e4985a7855e67b3a

See more details on using hashes here.

File details

Details for the file jingu_trust_gate-0.1.5-py3-none-any.whl.

File metadata

File hashes

Hashes for jingu_trust_gate-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 c2dbea166b610caec8b6d4b3e69b0433e68fe0bb25c0e78f2982b993bde12fd8
MD5 0344e0d2cef75396196bbeb43319676b
BLAKE2b-256 0eb99b66609475074ea4cb58194fd6c710d7a48c9f3ce6d4a397447c5949473e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page