kyvvu-engine 0.3.0__py3-none-any.whl
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.
- kyvvu_engine/__init__.py +58 -0
- kyvvu_engine/__version__.py +4 -0
- kyvvu_engine/_cli.py +58 -0
- kyvvu_engine/aggregation.py +103 -0
- kyvvu_engine/engine.py +516 -0
- kyvvu_engine/io/__init__.py +18 -0
- kyvvu_engine/io/disk_cache.py +87 -0
- kyvvu_engine/io/exceptions.py +34 -0
- kyvvu_engine/io/http.py +4 -0
- kyvvu_engine/io/instance.py +30 -0
- kyvvu_engine/io/runner.py +892 -0
- kyvvu_engine/io/settings.py +261 -0
- kyvvu_engine/logging.py +103 -0
- kyvvu_engine/path_tracker.py +120 -0
- kyvvu_engine/policy_store.py +153 -0
- kyvvu_engine/rules/__init__.py +33 -0
- kyvvu_engine/rules/_context.py +172 -0
- kyvvu_engine/rules/_helpers.py +142 -0
- kyvvu_engine/rules/_registry.py +121 -0
- kyvvu_engine/rules/classification.py +103 -0
- kyvvu_engine/rules/content.py +75 -0
- kyvvu_engine/rules/count.py +126 -0
- kyvvu_engine/rules/field.py +83 -0
- kyvvu_engine/rules/flow.py +254 -0
- kyvvu_engine/rules/path.py +330 -0
- kyvvu_engine/schemas.py +498 -0
- kyvvu_engine/serve/__init__.py +4 -0
- kyvvu_engine/serve/app.py +70 -0
- kyvvu_engine/serve/deps.py +26 -0
- kyvvu_engine/serve/routes.py +145 -0
- kyvvu_engine-0.3.0.dist-info/METADATA +1144 -0
- kyvvu_engine-0.3.0.dist-info/RECORD +35 -0
- kyvvu_engine-0.3.0.dist-info/WHEEL +5 -0
- kyvvu_engine-0.3.0.dist-info/licenses/LICENSE +112 -0
- kyvvu_engine-0.3.0.dist-info/top_level.txt +1 -0
kyvvu_engine/__init__.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
# Copyright 2026 Kyvvu B.V.
|
|
3
|
+
|
|
4
|
+
"""kyvvu-engine — standalone policy evaluation engine for AI governance.
|
|
5
|
+
|
|
6
|
+
Public API
|
|
7
|
+
----------
|
|
8
|
+
Core evaluation (zero-I/O)::
|
|
9
|
+
|
|
10
|
+
from kyvvu_engine import PolicyEngine
|
|
11
|
+
|
|
12
|
+
I/O wrapper (policy fetch + batch logging)::
|
|
13
|
+
|
|
14
|
+
from kyvvu_engine import KyvvuRunner, KyvvuSettings
|
|
15
|
+
|
|
16
|
+
Exceptions::
|
|
17
|
+
|
|
18
|
+
from kyvvu_engine import KyvvuBlockedError, KyvvuConfigError
|
|
19
|
+
|
|
20
|
+
Everything else (:mod:`~kyvvu_engine.schemas`, :mod:`~kyvvu_engine.rules`,
|
|
21
|
+
etc.) is importable for advanced use but not part of the stable public interface.
|
|
22
|
+
|
|
23
|
+
Note
|
|
24
|
+
----
|
|
25
|
+
Template mapping (framework events → v0.05 behaviors) has moved to the
|
|
26
|
+
``kyvvu`` SDK package::
|
|
27
|
+
|
|
28
|
+
from kyvvu.templates import load_builtin, load_template, BehaviorTemplate
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from kyvvu_engine.__version__ import __version__
|
|
32
|
+
from kyvvu_engine.aggregation import (
|
|
33
|
+
aggregate_max,
|
|
34
|
+
aggregate_mean,
|
|
35
|
+
aggregate_weighted_sum,
|
|
36
|
+
)
|
|
37
|
+
from kyvvu_engine.engine import PolicyEngine
|
|
38
|
+
from kyvvu_engine.io.exceptions import KyvvuBlockedError, KyvvuConfigError
|
|
39
|
+
from kyvvu_engine.io.runner import KyvvuRunner
|
|
40
|
+
from kyvvu_engine.io.settings import KyvvuSettings
|
|
41
|
+
from kyvvu_engine.logging import setup_logging
|
|
42
|
+
from kyvvu_engine.rules._registry import PolicyRule
|
|
43
|
+
from kyvvu_engine.schemas import PolicyStatus
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"PolicyEngine",
|
|
47
|
+
"KyvvuRunner",
|
|
48
|
+
"KyvvuSettings",
|
|
49
|
+
"KyvvuBlockedError",
|
|
50
|
+
"KyvvuConfigError",
|
|
51
|
+
"PolicyRule",
|
|
52
|
+
"PolicyStatus",
|
|
53
|
+
"aggregate_max",
|
|
54
|
+
"aggregate_mean",
|
|
55
|
+
"aggregate_weighted_sum",
|
|
56
|
+
"setup_logging",
|
|
57
|
+
"__version__",
|
|
58
|
+
]
|
kyvvu_engine/_cli.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
# Copyright 2026 Kyvvu B.V.
|
|
3
|
+
|
|
4
|
+
"""Entry point for the ``kyvvu-serve`` CLI.
|
|
5
|
+
|
|
6
|
+
Starts a local HTTP server wrapping :class:`~kyvvu_engine.engine.PolicyEngine`
|
|
7
|
+
so non-Python harnesses can evaluate policies via REST.
|
|
8
|
+
|
|
9
|
+
Requires the ``[serve]`` extra::
|
|
10
|
+
|
|
11
|
+
pip install "kyvvu-engine[serve]"
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> None:
|
|
20
|
+
"""Parse CLI args and start the kyvvu-serve uvicorn server.
|
|
21
|
+
|
|
22
|
+
Reads configuration from CLI flags (``--agent-key``, ``--api-url``,
|
|
23
|
+
``--api-key``) and falls back to ``KV_*`` environment variables for
|
|
24
|
+
unset values. Requires the ``[serve]`` extra for uvicorn/fastapi.
|
|
25
|
+
"""
|
|
26
|
+
parser = argparse.ArgumentParser(
|
|
27
|
+
prog="kyvvu-serve",
|
|
28
|
+
description="Local policy evaluation server for kyvvu-engine",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument("--host", default="127.0.0.1", help="Bind address (default: 127.0.0.1)")
|
|
31
|
+
parser.add_argument("--port", type=int, default=8090, help="Bind port (default: 8090)")
|
|
32
|
+
parser.add_argument("--agent-key", default=None, help="Agent key for policy fetch")
|
|
33
|
+
parser.add_argument("--api-url", default=None, help="Kyvvu platform API URL")
|
|
34
|
+
parser.add_argument("--api-key", default=None, help="Bearer API key")
|
|
35
|
+
args = parser.parse_args()
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import uvicorn
|
|
39
|
+
except ImportError:
|
|
40
|
+
print(
|
|
41
|
+
"kyvvu-serve requires the [serve] extra.\n"
|
|
42
|
+
'Install with: pip install "kyvvu-engine[serve]"',
|
|
43
|
+
file=sys.stderr,
|
|
44
|
+
)
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
from kyvvu_engine.serve.app import create_app
|
|
48
|
+
|
|
49
|
+
app = create_app(
|
|
50
|
+
agent_key=args.agent_key,
|
|
51
|
+
api_url=args.api_url,
|
|
52
|
+
api_key=args.api_key,
|
|
53
|
+
)
|
|
54
|
+
uvicorn.run(app, host=args.host, port=args.port)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
main()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
# Copyright 2026 Kyvvu B.V.
|
|
3
|
+
|
|
4
|
+
"""Score aggregation strategies for kyvvu-engine.
|
|
5
|
+
|
|
6
|
+
Given a list of per-policy outcomes, these functions compute an aggregate
|
|
7
|
+
risk score and derive the recommended :class:`~kyvvu_engine.schemas.Action`.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
from kyvvu_engine.schemas import Action, EvalResult, PolicyResult
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Normalised weight per severity level. ``critical`` maps to the maximum
|
|
19
|
+
# score of 1.0 so that a single critical violation always triggers ``block``.
|
|
20
|
+
SEVERITY_WEIGHTS: dict[str, float] = {
|
|
21
|
+
"low": 0.25,
|
|
22
|
+
"medium": 0.50,
|
|
23
|
+
"high": 0.75,
|
|
24
|
+
"critical": 1.0,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _score_to_action(score: float) -> Action:
|
|
29
|
+
"""Map a normalised risk score to a recommended :class:`Action`.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
score: Risk score in ``[0.0, 1.0]``.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
:attr:`Action.allow` when score is ``0.0``,
|
|
36
|
+
:attr:`Action.block` when score is ``>= 1.0``,
|
|
37
|
+
:attr:`Action.warn` for any value in between.
|
|
38
|
+
"""
|
|
39
|
+
if score == 0.0:
|
|
40
|
+
return Action.allow
|
|
41
|
+
if score >= 1.0:
|
|
42
|
+
return Action.block
|
|
43
|
+
return Action.warn
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def aggregate_max(results: list[PolicyResult]) -> EvalResult:
|
|
47
|
+
"""Aggregate by taking the maximum severity among all violations.
|
|
48
|
+
|
|
49
|
+
Action thresholds:
|
|
50
|
+
- ``risk_score == 0.0`` → :attr:`Action.allow`
|
|
51
|
+
- ``0.0 < risk_score < 1.0`` → :attr:`Action.warn`
|
|
52
|
+
- ``risk_score == 1.0`` → :attr:`Action.block`
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
results: Per-policy evaluation outcomes.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
:class:`EvalResult` with ``risk_score`` and ``action`` from worst violation.
|
|
59
|
+
"""
|
|
60
|
+
violations = [r for r in results if r.violated]
|
|
61
|
+
if not violations:
|
|
62
|
+
return EvalResult(risk_score=0.0, action=Action.allow, policies=results)
|
|
63
|
+
max_score = max(SEVERITY_WEIGHTS.get(v.severity, 0.0) for v in violations)
|
|
64
|
+
return EvalResult(risk_score=max_score, action=_score_to_action(max_score), policies=results)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def aggregate_mean(results: list[PolicyResult]) -> EvalResult:
|
|
68
|
+
"""Aggregate by computing the mean severity across all violations.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
results: Per-policy evaluation outcomes.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
:class:`EvalResult` with mean risk score.
|
|
75
|
+
"""
|
|
76
|
+
violations = [r for r in results if r.violated]
|
|
77
|
+
if not violations:
|
|
78
|
+
return EvalResult(risk_score=0.0, action=Action.allow, policies=results)
|
|
79
|
+
mean_score = sum(SEVERITY_WEIGHTS.get(v.severity, 0.0) for v in violations) / len(violations)
|
|
80
|
+
return EvalResult(risk_score=mean_score, action=_score_to_action(mean_score), policies=results)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def aggregate_weighted_sum(
|
|
84
|
+
results: list[PolicyResult], weights: dict[int | None, float]
|
|
85
|
+
) -> EvalResult:
|
|
86
|
+
"""Aggregate using caller-supplied per-policy weights.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
results: Per-policy evaluation outcomes.
|
|
90
|
+
weights: Dict mapping policy_id to a numeric weight.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
:class:`EvalResult` with weighted sum capped at 1.0.
|
|
94
|
+
"""
|
|
95
|
+
violations = [r for r in results if r.violated]
|
|
96
|
+
if not violations:
|
|
97
|
+
return EvalResult(risk_score=0.0, action=Action.allow, policies=results)
|
|
98
|
+
total: float = sum(
|
|
99
|
+
weights.get(v.policy_id, SEVERITY_WEIGHTS.get(v.severity, 0.0))
|
|
100
|
+
for v in violations
|
|
101
|
+
)
|
|
102
|
+
score = min(total, 1.0)
|
|
103
|
+
return EvalResult(risk_score=score, action=_score_to_action(score), policies=results)
|