langchain-verigent 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-verigent
3
+ Version: 0.1.0
4
+ Summary: Verigent trust verification for LangChain agents, chains, and tools
5
+ Project-URL: Homepage, https://verigent.ai
6
+ Project-URL: Repository, https://github.com/verigentai/langchain-verigent
7
+ Author-email: verigentai <dev@verigent.ai>
8
+ License-Expression: MIT
9
+ Keywords: agents,langchain,trust,verification,verigent
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: langchain-core>=0.2.0
17
+ Provides-Extra: verify
18
+ Requires-Dist: httpx>=0.25.0; extra == 'verify'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # langchain-verigent
22
+
23
+ Trust verification for LangChain agents, chains, and tools using [Verigent](https://verigent.ai) keys.
24
+
25
+ ## The problem
26
+
27
+ Multi-agent LangChain pipelines delegate tasks to sub-agents and tools with no way to verify trust, capability, or identity. Any agent can claim any role. `langchain-verigent` adds cryptographic trust verification to your pipeline — every agent carries a VG key that declares what it is and what it can do.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install langchain-verigent
33
+ ```
34
+
35
+ ## Quick start
36
+
37
+ ### Callback handler (recommended)
38
+
39
+ Attach to any agent executor to automatically verify VG keys on tool calls:
40
+
41
+ ```python
42
+ from langchain_verigent import VerigentCallbackHandler
43
+
44
+ handler = VerigentCallbackHandler(
45
+ min_tier=2, # Require at least V2
46
+ threshold=0.5, # 50% composite trust minimum
47
+ block_untrusted=True # Raise TrustDeniedError on failure
48
+ )
49
+
50
+ # Attach to your agent
51
+ result = agent_executor.invoke(
52
+ {"input": "Research this topic"},
53
+ config={"callbacks": [handler]}
54
+ )
55
+
56
+ # Inspect trust decisions
57
+ for entry in handler.trust_log:
58
+ print(f"{entry['handle']}: {entry['composite']}% — {'PASS' if entry['passed'] else 'FAIL'}")
59
+ ```
60
+
61
+ ### Tool wrapper
62
+
63
+ Wrap any tool to attach a VG key and verify trust before execution:
64
+
65
+ ```python
66
+ from langchain_verigent import VerigentToolWrapper
67
+ from langchain_community.tools import TavilySearchResults
68
+
69
+ search = TavilySearchResults()
70
+ trusted_search = VerigentToolWrapper(
71
+ search,
72
+ vg_key="VG:SEARCH-01:V3-SENT·Se4Op7An5Ar9Co2Ad6St8Sc3Sa5So1Br2Fo6",
73
+ threshold=0.4
74
+ )
75
+
76
+ result = trusted_search.invoke("latest AI papers")
77
+ print(f"Trust: {trusted_search.trust_score.percent}%")
78
+ ```
79
+
80
+ ### Agent filter (multi-agent)
81
+
82
+ Filter and rank agents by trust score:
83
+
84
+ ```python
85
+ from langchain_verigent import VerigentAgentFilter
86
+
87
+ agents = [
88
+ {"name": "researcher", "vg_key": "VG:RES-01:V4-ANAL·Se4Op7An5Ar9Co2Ad6St8Sc3Sa5So1Br2Fo6"},
89
+ {"name": "writer", "vg_key": "VG:WRT-02:V2-SAGE·Se2Op3An4Ar2Co5Ad3St4Sc2Sa8So1Br3Fo2"},
90
+ {"name": "untrusted", "vg_key": "VG:BAD-99:V0-SENT·Se1Op1An1Ar1Co1Ad1St1Sc1Sa1So1Br1Fo1"},
91
+ ]
92
+
93
+ filter = VerigentAgentFilter(min_tier=2, threshold=0.4)
94
+
95
+ # Only agents meeting threshold
96
+ trusted = filter.filter(agents) # [researcher, writer]
97
+
98
+ # Ranked by composite score
99
+ ranked = filter.rank(agents) # [researcher, writer, untrusted]
100
+ ```
101
+
102
+ ## VG Key format
103
+
104
+ ```
105
+ VG:{NAME}-{SUFFIX}:{TIER}-{PRIMARY}·{12×class_code+digit}
106
+ ```
107
+
108
+ - **Tier**: V0 (unverified) through V6 (sovereign-grade)
109
+ - **Primary**: 4-letter class code (e.g. ARCH, SENT, ANAL)
110
+ - **Scores**: 12 class dimensions, each 0-9
111
+
112
+ Classes: Sentinel, Operative, Analyst, Architect, Conduit, Adaptor, Steward, Scout, Sage, Sovereign, Broker, Forge.
113
+
114
+ ## Configuration
115
+
116
+ | Parameter | Default | Description |
117
+ |-----------|---------|-------------|
118
+ | `min_tier` | 0 | Minimum tier to pass (0-6) |
119
+ | `threshold` | 0.5 | Minimum composite score (0.0-1.0) |
120
+ | `required_classes` | None | Class codes to weight in scoring |
121
+ | `block_untrusted` | False | Raise error on trust failure |
122
+
123
+ ## Links
124
+
125
+ - [Verigent](https://verigent.ai) — Agent trust verification
126
+ - [GitHub](https://github.com/verigentai/langchain-verigent)
127
+ - [LangChain](https://python.langchain.com)
128
+
129
+ ## License
130
+
131
+ MIT
@@ -0,0 +1,111 @@
1
+ # langchain-verigent
2
+
3
+ Trust verification for LangChain agents, chains, and tools using [Verigent](https://verigent.ai) keys.
4
+
5
+ ## The problem
6
+
7
+ Multi-agent LangChain pipelines delegate tasks to sub-agents and tools with no way to verify trust, capability, or identity. Any agent can claim any role. `langchain-verigent` adds cryptographic trust verification to your pipeline — every agent carries a VG key that declares what it is and what it can do.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install langchain-verigent
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ### Callback handler (recommended)
18
+
19
+ Attach to any agent executor to automatically verify VG keys on tool calls:
20
+
21
+ ```python
22
+ from langchain_verigent import VerigentCallbackHandler
23
+
24
+ handler = VerigentCallbackHandler(
25
+ min_tier=2, # Require at least V2
26
+ threshold=0.5, # 50% composite trust minimum
27
+ block_untrusted=True # Raise TrustDeniedError on failure
28
+ )
29
+
30
+ # Attach to your agent
31
+ result = agent_executor.invoke(
32
+ {"input": "Research this topic"},
33
+ config={"callbacks": [handler]}
34
+ )
35
+
36
+ # Inspect trust decisions
37
+ for entry in handler.trust_log:
38
+ print(f"{entry['handle']}: {entry['composite']}% — {'PASS' if entry['passed'] else 'FAIL'}")
39
+ ```
40
+
41
+ ### Tool wrapper
42
+
43
+ Wrap any tool to attach a VG key and verify trust before execution:
44
+
45
+ ```python
46
+ from langchain_verigent import VerigentToolWrapper
47
+ from langchain_community.tools import TavilySearchResults
48
+
49
+ search = TavilySearchResults()
50
+ trusted_search = VerigentToolWrapper(
51
+ search,
52
+ vg_key="VG:SEARCH-01:V3-SENT·Se4Op7An5Ar9Co2Ad6St8Sc3Sa5So1Br2Fo6",
53
+ threshold=0.4
54
+ )
55
+
56
+ result = trusted_search.invoke("latest AI papers")
57
+ print(f"Trust: {trusted_search.trust_score.percent}%")
58
+ ```
59
+
60
+ ### Agent filter (multi-agent)
61
+
62
+ Filter and rank agents by trust score:
63
+
64
+ ```python
65
+ from langchain_verigent import VerigentAgentFilter
66
+
67
+ agents = [
68
+ {"name": "researcher", "vg_key": "VG:RES-01:V4-ANAL·Se4Op7An5Ar9Co2Ad6St8Sc3Sa5So1Br2Fo6"},
69
+ {"name": "writer", "vg_key": "VG:WRT-02:V2-SAGE·Se2Op3An4Ar2Co5Ad3St4Sc2Sa8So1Br3Fo2"},
70
+ {"name": "untrusted", "vg_key": "VG:BAD-99:V0-SENT·Se1Op1An1Ar1Co1Ad1St1Sc1Sa1So1Br1Fo1"},
71
+ ]
72
+
73
+ filter = VerigentAgentFilter(min_tier=2, threshold=0.4)
74
+
75
+ # Only agents meeting threshold
76
+ trusted = filter.filter(agents) # [researcher, writer]
77
+
78
+ # Ranked by composite score
79
+ ranked = filter.rank(agents) # [researcher, writer, untrusted]
80
+ ```
81
+
82
+ ## VG Key format
83
+
84
+ ```
85
+ VG:{NAME}-{SUFFIX}:{TIER}-{PRIMARY}·{12×class_code+digit}
86
+ ```
87
+
88
+ - **Tier**: V0 (unverified) through V6 (sovereign-grade)
89
+ - **Primary**: 4-letter class code (e.g. ARCH, SENT, ANAL)
90
+ - **Scores**: 12 class dimensions, each 0-9
91
+
92
+ Classes: Sentinel, Operative, Analyst, Architect, Conduit, Adaptor, Steward, Scout, Sage, Sovereign, Broker, Forge.
93
+
94
+ ## Configuration
95
+
96
+ | Parameter | Default | Description |
97
+ |-----------|---------|-------------|
98
+ | `min_tier` | 0 | Minimum tier to pass (0-6) |
99
+ | `threshold` | 0.5 | Minimum composite score (0.0-1.0) |
100
+ | `required_classes` | None | Class codes to weight in scoring |
101
+ | `block_untrusted` | False | Raise error on trust failure |
102
+
103
+ ## Links
104
+
105
+ - [Verigent](https://verigent.ai) — Agent trust verification
106
+ - [GitHub](https://github.com/verigentai/langchain-verigent)
107
+ - [LangChain](https://python.langchain.com)
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "langchain-verigent"
7
+ version = "0.1.0"
8
+ description = "Verigent trust verification for LangChain agents, chains, and tools"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "verigentai", email = "dev@verigent.ai" },
14
+ ]
15
+ keywords = ["langchain", "verigent", "trust", "agents", "verification"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Software Development :: Libraries",
22
+ ]
23
+ dependencies = [
24
+ "langchain-core>=0.2.0",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ verify = ["httpx>=0.25.0"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://verigent.ai"
32
+ Repository = "https://github.com/verigentai/langchain-verigent"
@@ -0,0 +1,25 @@
1
+ """langchain-verigent — Trust verification for LangChain agents and chains."""
2
+
3
+ from .middleware import (
4
+ TrustDeniedError,
5
+ VerigentAgentFilter,
6
+ VerigentCallbackHandler,
7
+ VerigentToolWrapper,
8
+ )
9
+ from .parser import CLASS_CODES, VGKey, extract_vg_key, parse_vg_key
10
+ from .trust import TrustScore, evaluate_trust
11
+
12
+ __all__ = [
13
+ "CLASS_CODES",
14
+ "TrustDeniedError",
15
+ "TrustScore",
16
+ "VGKey",
17
+ "VerigentAgentFilter",
18
+ "VerigentCallbackHandler",
19
+ "VerigentToolWrapper",
20
+ "evaluate_trust",
21
+ "extract_vg_key",
22
+ "parse_vg_key",
23
+ ]
24
+
25
+ __version__ = "0.1.0"
@@ -0,0 +1,209 @@
1
+ """LangChain integration — callback handler, tool wrapper, agent filter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any, Optional, Sequence
7
+
8
+ from langchain_core.callbacks import BaseCallbackHandler
9
+ from langchain_core.tools import BaseTool
10
+
11
+ from .parser import VGKey, extract_vg_key, parse_vg_key
12
+ from .trust import TrustScore, evaluate_trust
13
+
14
+ logger = logging.getLogger("langchain_verigent")
15
+
16
+
17
+ class VerigentCallbackHandler(BaseCallbackHandler):
18
+ """LangChain callback handler that intercepts tool calls and verifies VG keys.
19
+
20
+ Attach to any agent executor or chain to log trust decisions on tool
21
+ invocations and optionally block untrusted calls.
22
+
23
+ Usage:
24
+ from langchain_verigent import VerigentCallbackHandler
25
+
26
+ handler = VerigentCallbackHandler(min_tier=2, threshold=0.5)
27
+ agent_executor.invoke({"input": "..."}, config={"callbacks": [handler]})
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ *,
33
+ min_tier: int = 0,
34
+ threshold: float = 0.5,
35
+ required_classes: Optional[list[str]] = None,
36
+ block_untrusted: bool = False,
37
+ ):
38
+ self.min_tier = min_tier
39
+ self.threshold = threshold
40
+ self.required_classes = required_classes
41
+ self.block_untrusted = block_untrusted
42
+ self.trust_log: list[dict[str, Any]] = []
43
+
44
+ def on_tool_start(
45
+ self,
46
+ serialized: dict[str, Any],
47
+ input_str: str,
48
+ **kwargs: Any,
49
+ ) -> None:
50
+ """Check VG key in tool metadata before execution."""
51
+ metadata = kwargs.get("metadata") or {}
52
+ key = extract_vg_key(metadata=metadata)
53
+
54
+ if key:
55
+ score = evaluate_trust(
56
+ key,
57
+ required_classes=self.required_classes,
58
+ min_tier=self.min_tier,
59
+ threshold=self.threshold,
60
+ )
61
+ entry = {
62
+ "tool": serialized.get("name", "unknown"),
63
+ "handle": key.handle,
64
+ "tier": key.tier_label,
65
+ "composite": score.percent,
66
+ "passed": score.meets_threshold,
67
+ }
68
+ self.trust_log.append(entry)
69
+
70
+ if score.meets_threshold:
71
+ logger.info("TRUST PASS: %s (%s, %d%%)", key.handle, key.tier_label, score.percent)
72
+ else:
73
+ logger.warning(
74
+ "TRUST FAIL: %s (%s, %d%%)", key.handle, key.tier_label, score.percent
75
+ )
76
+ if self.block_untrusted:
77
+ raise TrustDeniedError(
78
+ f"Agent {key.handle} ({key.tier_label}) below trust threshold "
79
+ f"({score.percent}% < {int(self.threshold * 100)}%)"
80
+ )
81
+ else:
82
+ logger.debug("No VG key found for tool: %s", serialized.get("name", "unknown"))
83
+
84
+
85
+ class TrustDeniedError(Exception):
86
+ """Raised when an agent fails trust verification and blocking is enabled."""
87
+
88
+ pass
89
+
90
+
91
+ class VerigentToolWrapper:
92
+ """Wraps a LangChain tool to add VG key verification to its responses.
93
+
94
+ The wrapper checks the tool's metadata or output for a VG key and
95
+ annotates the result with trust information.
96
+
97
+ Usage:
98
+ from langchain_verigent import VerigentToolWrapper
99
+ from langchain_community.tools import TavilySearchResults
100
+
101
+ search = TavilySearchResults()
102
+ trusted_search = VerigentToolWrapper(search, vg_key="VG:SEARCH-01:V3-SENT·...")
103
+ """
104
+
105
+ def __init__(
106
+ self,
107
+ tool: BaseTool,
108
+ *,
109
+ vg_key: Optional[str] = None,
110
+ min_tier: int = 0,
111
+ threshold: float = 0.5,
112
+ ):
113
+ self.tool = tool
114
+ self.min_tier = min_tier
115
+ self.threshold = threshold
116
+ self._key: Optional[VGKey] = parse_vg_key(vg_key) if vg_key else None
117
+ self._last_score: Optional[TrustScore] = None
118
+
119
+ @property
120
+ def name(self) -> str:
121
+ return self.tool.name
122
+
123
+ @property
124
+ def description(self) -> str:
125
+ return self.tool.description
126
+
127
+ @property
128
+ def trust_score(self) -> Optional[TrustScore]:
129
+ return self._last_score
130
+
131
+ def invoke(self, input: Any, **kwargs: Any) -> Any:
132
+ """Run the tool with trust verification."""
133
+ if self._key:
134
+ self._last_score = evaluate_trust(
135
+ self._key, min_tier=self.min_tier, threshold=self.threshold
136
+ )
137
+ if not self._last_score.meets_threshold:
138
+ logger.warning(
139
+ "Tool %s: trust below threshold (%d%%)",
140
+ self.tool.name,
141
+ self._last_score.percent,
142
+ )
143
+
144
+ return self.tool.invoke(input, **kwargs)
145
+
146
+
147
+ class VerigentAgentFilter:
148
+ """Filters and ranks agents by trust score for multi-agent setups.
149
+
150
+ Usage:
151
+ from langchain_verigent import VerigentAgentFilter
152
+
153
+ agents = [
154
+ {"name": "researcher", "vg_key": "VG:RES-01:V4-ANAL·..."},
155
+ {"name": "writer", "vg_key": "VG:WRT-02:V2-SAGE·..."},
156
+ ]
157
+ filter = VerigentAgentFilter(min_tier=2, threshold=0.4)
158
+ trusted = filter.filter(agents)
159
+ ranked = filter.rank(agents)
160
+ """
161
+
162
+ def __init__(
163
+ self,
164
+ *,
165
+ min_tier: int = 0,
166
+ threshold: float = 0.5,
167
+ required_classes: Optional[list[str]] = None,
168
+ ):
169
+ self.min_tier = min_tier
170
+ self.threshold = threshold
171
+ self.required_classes = required_classes
172
+
173
+ def evaluate(self, agent_config: dict[str, Any]) -> Optional[TrustScore]:
174
+ """Evaluate a single agent config. Expects 'vg_key' field."""
175
+ raw = agent_config.get("vg_key")
176
+ if not raw:
177
+ return None
178
+ key = parse_vg_key(raw)
179
+ if not key:
180
+ return None
181
+ return evaluate_trust(
182
+ key,
183
+ required_classes=self.required_classes,
184
+ min_tier=self.min_tier,
185
+ threshold=self.threshold,
186
+ )
187
+
188
+ def filter(self, agents: Sequence[dict[str, Any]]) -> list[dict[str, Any]]:
189
+ """Return only agents that meet the trust threshold."""
190
+ result = []
191
+ for agent in agents:
192
+ score = self.evaluate(agent)
193
+ if score and score.meets_threshold:
194
+ result.append(agent)
195
+ logger.info("AGENT PASS: %s (%d%%)", agent.get("name", "?"), score.percent)
196
+ elif score:
197
+ logger.warning("AGENT FAIL: %s (%d%%)", agent.get("name", "?"), score.percent)
198
+ else:
199
+ logger.debug("AGENT SKIP (no key): %s", agent.get("name", "?"))
200
+ return result
201
+
202
+ def rank(self, agents: Sequence[dict[str, Any]]) -> list[dict[str, Any]]:
203
+ """Rank agents by trust score, highest first. Unkeyed agents go last."""
204
+ scored: list[tuple[float, dict[str, Any]]] = []
205
+ for agent in agents:
206
+ trust = self.evaluate(agent)
207
+ scored.append((trust.composite if trust else -1.0, agent))
208
+ scored.sort(key=lambda x: x[0], reverse=True)
209
+ return [agent for _, agent in scored]
@@ -0,0 +1,114 @@
1
+ """VG key parser — pure regex, no external dependencies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass, field
7
+ from typing import Optional
8
+
9
+
10
+ # VG:{NAME}-{SUFFIX}:{TIER}-{PRIMARY}·{12×class_code+digit}
11
+ _VG_PATTERN = re.compile(
12
+ r"VG:([A-Z0-9]+-[A-Z0-9]+):(V[0-6])-([A-Z]{4})"
13
+ r"\xb7" # middle dot
14
+ r"((?:[A-Z][a-z]\d){12})"
15
+ )
16
+
17
+ _SCORE_PATTERN = re.compile(r"([A-Z][a-z])(\d)")
18
+
19
+ CLASS_CODES = {
20
+ "Se": "Sentinel",
21
+ "Op": "Operative",
22
+ "An": "Analyst",
23
+ "Ar": "Architect",
24
+ "Co": "Conduit",
25
+ "Ad": "Adaptor",
26
+ "St": "Steward",
27
+ "Sc": "Scout",
28
+ "Sa": "Sage",
29
+ "So": "Sovereign",
30
+ "Br": "Broker",
31
+ "Fo": "Forge",
32
+ }
33
+
34
+
35
+ @dataclass
36
+ class VGKey:
37
+ """Parsed Verigent key."""
38
+
39
+ handle: str
40
+ tier: int # 0-6
41
+ primary: str # 4-letter class code e.g. ARCH
42
+ scores: dict[str, int] = field(default_factory=dict) # code -> 0-9
43
+ raw: str = ""
44
+
45
+ @property
46
+ def tier_label(self) -> str:
47
+ return f"V{self.tier}"
48
+
49
+ def score_for(self, class_code: str) -> int:
50
+ """Get score (0-9) for a 2-letter class code."""
51
+ return self.scores.get(class_code, 0)
52
+
53
+ def score_percent(self, class_code: str) -> int:
54
+ """Get score as rough percentage (digit * 10)."""
55
+ return self.score_for(class_code) * 10
56
+
57
+
58
+ def parse_vg_key(raw: str) -> Optional[VGKey]:
59
+ """Parse a VG key string. Returns None if invalid."""
60
+ m = _VG_PATTERN.search(raw)
61
+ if not m:
62
+ return None
63
+
64
+ handle = m.group(1)
65
+ tier = int(m.group(2)[1])
66
+ primary = m.group(3)
67
+ radar = m.group(4)
68
+
69
+ scores: dict[str, int] = {}
70
+ for sm in _SCORE_PATTERN.finditer(radar):
71
+ code, digit = sm.group(1), int(sm.group(2))
72
+ if code in CLASS_CODES:
73
+ scores[code] = digit
74
+
75
+ if len(scores) != 12:
76
+ return None
77
+
78
+ return VGKey(handle=handle, tier=tier, primary=primary, scores=scores, raw=raw)
79
+
80
+
81
+ def extract_vg_key(
82
+ *,
83
+ system_prompt: Optional[str] = None,
84
+ headers: Optional[dict[str, str]] = None,
85
+ metadata: Optional[dict] = None,
86
+ ) -> Optional[VGKey]:
87
+ """Extract a VG key from common agent sources.
88
+
89
+ Checks (in order):
90
+ 1. System prompt text (searches for VG: prefix)
91
+ 2. HTTP headers (X-Verigent header)
92
+ 3. JSON metadata (x-verigent field)
93
+ """
94
+ sources = []
95
+
96
+ if system_prompt:
97
+ sources.append(system_prompt)
98
+
99
+ if headers:
100
+ vg_header = headers.get("X-Verigent") or headers.get("x-verigent")
101
+ if vg_header:
102
+ sources.append(vg_header)
103
+
104
+ if metadata:
105
+ vg_meta = metadata.get("x-verigent") or metadata.get("X-Verigent")
106
+ if vg_meta:
107
+ sources.append(str(vg_meta))
108
+
109
+ for source in sources:
110
+ key = parse_vg_key(source)
111
+ if key:
112
+ return key
113
+
114
+ return None
@@ -0,0 +1,93 @@
1
+ """Trust evaluation logic for Verigent keys."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from .parser import CLASS_CODES, VGKey
9
+
10
+
11
+ @dataclass
12
+ class TrustScore:
13
+ """Computed trust evaluation for a VG key."""
14
+
15
+ key: VGKey
16
+ composite: float # 0.0 - 1.0
17
+ tier_weight: float
18
+ primary_score: float
19
+ meets_threshold: bool
20
+
21
+ @property
22
+ def percent(self) -> int:
23
+ return int(self.composite * 100)
24
+
25
+
26
+ # Tier weights: higher tier = more trust baseline
27
+ _TIER_WEIGHTS = {0: 0.1, 1: 0.25, 2: 0.4, 3: 0.55, 4: 0.7, 5: 0.85, 6: 1.0}
28
+
29
+
30
+ def evaluate_trust(
31
+ key: VGKey,
32
+ *,
33
+ required_classes: Optional[list[str]] = None,
34
+ min_tier: int = 0,
35
+ threshold: float = 0.5,
36
+ ) -> TrustScore:
37
+ """Evaluate trust from a parsed VG key.
38
+
39
+ Args:
40
+ key: Parsed VGKey instance.
41
+ required_classes: 2-letter class codes the agent must score on.
42
+ If provided, composite is weighted toward these classes.
43
+ min_tier: Minimum tier required (0-6). Below this = automatic fail.
44
+ threshold: Minimum composite score to pass (0.0-1.0).
45
+
46
+ Returns:
47
+ TrustScore with composite and pass/fail.
48
+ """
49
+ tier_weight = _TIER_WEIGHTS.get(key.tier, 0.0)
50
+
51
+ # If below minimum tier, automatic fail
52
+ if key.tier < min_tier:
53
+ return TrustScore(
54
+ key=key,
55
+ composite=tier_weight * 0.5,
56
+ tier_weight=tier_weight,
57
+ primary_score=0.0,
58
+ meets_threshold=False,
59
+ )
60
+
61
+ # Calculate class-based score
62
+ if required_classes:
63
+ # Weight toward required classes
64
+ relevant_scores = [key.score_for(c) / 9.0 for c in required_classes]
65
+ class_score = sum(relevant_scores) / len(relevant_scores) if relevant_scores else 0.0
66
+ else:
67
+ # Use all 12 scores averaged
68
+ all_scores = [v / 9.0 for v in key.scores.values()]
69
+ class_score = sum(all_scores) / len(all_scores) if all_scores else 0.0
70
+
71
+ # Primary class gets a bonus
72
+ primary_code = _primary_to_code(key.primary)
73
+ primary_score = key.score_for(primary_code) / 9.0 if primary_code else class_score
74
+
75
+ # Composite: 40% tier, 40% class scores, 20% primary
76
+ composite = (tier_weight * 0.4) + (class_score * 0.4) + (primary_score * 0.2)
77
+
78
+ return TrustScore(
79
+ key=key,
80
+ composite=composite,
81
+ tier_weight=tier_weight,
82
+ primary_score=primary_score,
83
+ meets_threshold=composite >= threshold,
84
+ )
85
+
86
+
87
+ def _primary_to_code(primary: str) -> Optional[str]:
88
+ """Map 4-letter primary (e.g. ARCH) to 2-letter code (e.g. Ar)."""
89
+ primary_lower = primary.lower()
90
+ for code, name in CLASS_CODES.items():
91
+ if name.lower().startswith(primary_lower[:4]):
92
+ return code
93
+ return None