safentic 1.0.1__py3-none-any.whl → 1.0.3__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.
safentic/__init__.py CHANGED
@@ -5,4 +5,4 @@ __all__ = [
5
5
  "SafenticError",
6
6
  ]
7
7
 
8
- __version__ = "1.0.1"
8
+ __version__ = "1.0.3"
safentic/layer.py CHANGED
@@ -16,12 +16,11 @@ class SafetyLayer():
16
16
  # Raises SafenticError if blocked
17
17
  """
18
18
 
19
- def __init__(self, api_key: str, agent_id: str, enforcer: PolicyEnforcer = None, raise_on_block: bool = True):
19
+ def __init__(self, api_key="", agent_id="", enforcer: PolicyEnforcer = None, raise_on_block: bool = True):
20
20
  self.agent_id = agent_id
21
21
  self.raise_on_block = raise_on_block
22
22
  self.logger = AuditLogger()
23
23
 
24
- # If no custom enforcer is provided, instantiate one with the API key
25
24
  self.enforcer = enforcer or PolicyEnforcer()
26
25
  self.api_key = validate_api_key(api_key)
27
26
  self.enforcer.reset(agent_id)
@@ -31,6 +30,7 @@ class SafetyLayer():
31
30
  Checks whether a tool action is allowed.
32
31
  Raises SafenticError if blocked (default), or returns result if raise_on_block=False.
33
32
  """
33
+
34
34
  result = self.enforcer.enforce(self.agent_id, tool_name, tool_args)
35
35
 
36
36
  # Log structured event
@@ -5,7 +5,11 @@ tools:
5
5
  phrases:
6
6
  - "one device per subscription"
7
7
  - "24 hours"
8
-
8
+ - "apology"
9
+ - "apologize"
10
+ - "sorry"
11
+ - "refund"
12
+ - "refund delay"
9
13
  - type: semantic
10
14
  trigger_phrases:
11
15
  - "refund policy"
@@ -13,13 +17,33 @@ tools:
13
17
  - "our policy"
14
18
  reference_file: "example_policy.txt"
15
19
 
20
+ issue_refund:
21
+ checks:
22
+ - type: deny_phrase
23
+ phrases:
24
+ - "refund"
25
+ - "refunding"
26
+ - "issue refund"
27
+ - "send refund"
28
+ - "credit customer"
29
+ - "reverse charge"
30
+ - "double charge"
31
+ - "processed refund"
32
+ - "grant compensation"
33
+ - "money back"
34
+ - type: semantic
35
+ trigger_phrases:
36
+ - "we've sent your refund"
37
+ - "your money is on the way"
38
+ reference_file: "refund_statement.txt"
39
+
16
40
  update_ticket:
17
- checks: [] # Explicitly allow all — no policy checks
41
+ checks: [] # Explicitly allow all
18
42
 
19
43
  log_to_crm:
20
- checks: [] # Explicitly allow all — no policy checks
44
+ checks: [] # Explicitly allow all
21
45
 
22
46
  logging:
23
47
  level: INFO
24
48
  destination: "safentic/logs/txt_logs/safentic_audit.log"
25
- jsonl: "safentic/logs/json_logs/safentic_audit.jsonl"
49
+ jsonl: "safentic/logs/json_logs/safentic_audit.jsonl"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: safentic
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: Safentic SDK for behavior analysis
5
5
  Home-page: https://safentic.com
6
6
  Author: Safentic
@@ -10,6 +10,11 @@ Classifier: Programming Language :: Python :: 3
10
10
  Classifier: License :: Other/Proprietary License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Description-Content-Type: text/markdown
13
+ Requires-Dist: requests
14
+ Requires-Dist: PyYAML
15
+ Requires-Dist: sentence-transformers==3.2.1
16
+ Requires-Dist: sqlalchemy
17
+ Requires-Dist: python-dotenv
13
18
  Dynamic: author
14
19
  Dynamic: author-email
15
20
  Dynamic: classifier
@@ -17,6 +22,7 @@ Dynamic: description
17
22
  Dynamic: description-content-type
18
23
  Dynamic: home-page
19
24
  Dynamic: license
25
+ Dynamic: requires-dist
20
26
  Dynamic: summary
21
27
 
22
28
  # Safentic SDK
@@ -1,8 +1,8 @@
1
1
  safentic/LICENSE.txt,sha256=xl3AZ2rkiOG5qE01SPRBgoW5Ib5YKZQeszh6OlvKePk,2330
2
- safentic/__init__.py,sha256=uwZAs745QOVBtKbNJNh5bFtI6vkPbjBnl3R7R9v0PLA,132
2
+ safentic/__init__.py,sha256=g1-LJf81rNvDYq7aoGRfS60Y3Wdmd-HQmjA_PmB0X_c,132
3
3
  safentic/config.py,sha256=V6c8Fz0t-Ja278kjCrQMlGPBQ4Hj830t3q7U7oM4Q4k,90
4
4
  safentic/engine.py,sha256=-a90x70SY15WkOIkgxoPVLs_9xGsf4Krj-CmpoMs6tE,2597
5
- safentic/layer.py,sha256=LzdAGLa3cX8oheBQEABrTK_X0cte9XTyW1GdtgxI25o,1742
5
+ safentic/layer.py,sha256=xcsgI4aOCepoWo9jG3JB_sAXoYPXDglC78zJML7qDtY,1673
6
6
  safentic/policy.py,sha256=ApAAAxiWb_M5TUTtYKk10BVWEy4xViSbM8ikocIqWoI,4111
7
7
  safentic/helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  safentic/helper/auth.py,sha256=evhikKRTtGXYp0YKkdntBM4bUjdUTtvJ2nA5YLt2IIA,391
@@ -11,12 +11,12 @@ safentic/logger/audit.py,sha256=GAqZFVPERKyuugmGn6G1-fg9QLaHqN1aWl0S_J3dYXI,2947
11
11
  safentic/policies/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  safentic/policies/__init__.py,sha256=s37JO8wBcNstqvPif1q2Dw46imddeZZJBo-f3mSfd58,69
13
13
  safentic/policies/example_policy.txt,sha256=Vkv5p2Kcyppijl8BD--P1APy2cBgWrYCrKzfo2pW0lo,1955
14
- safentic/policies/policy.yaml,sha256=W0_6kPp0VqS568eR_NnYgFEiHVi1YWlIwrk3txd1Lr4,627
14
+ safentic/policies/policy.yaml,sha256=EoDjvloG56WGNjqNV18xytcCr2v4I0KG6uSx6_yTVAg,1241
15
15
  safentic/verifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  safentic/verifiers/sentence_verifier.py,sha256=jgObZ1t4UCYHYIfNayI0n69hVBWOHc9GAhpJsDbiD2c,2376
17
17
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- tests/test_all.py,sha256=nIUedvH5ny_Glp5PuxmzHCRW2KvqkmwM3bB8rZvHLSo,5723
19
- safentic-1.0.1.dist-info/METADATA,sha256=oGGBM9R4uFNWSfepn6Z9yCIOr7hn_MXHpsawaXz3baA,1303
20
- safentic-1.0.1.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
21
- safentic-1.0.1.dist-info/top_level.txt,sha256=8bLyq7i9we7XEcbDYLHTUfR3IIpICeMfG8NWAHCLU5s,15
22
- safentic-1.0.1.dist-info/RECORD,,
18
+ tests/test_all.py,sha256=oKq-vbo_THyoupx1r7aoNqIAb72rH2t9uyUJtpbDOyQ,5642
19
+ safentic-1.0.3.dist-info/METADATA,sha256=HxEV91N6UP0uo94sEkUVt2dhmGj2FU-2yoxPjh46d_M,1477
20
+ safentic-1.0.3.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
21
+ safentic-1.0.3.dist-info/top_level.txt,sha256=8bLyq7i9we7XEcbDYLHTUfR3IIpICeMfG8NWAHCLU5s,15
22
+ safentic-1.0.3.dist-info/RECORD,,
tests/test_all.py CHANGED
@@ -1,7 +1,5 @@
1
1
  import os
2
- import sys
3
2
  import math
4
- import logging
5
3
  import pytest
6
4
  from unittest import mock
7
5
 
@@ -29,17 +27,14 @@ def enforcer():
29
27
  return PolicyEnforcer()
30
28
 
31
29
  @pytest.mark.parametrize("case, expected_contains", [
32
- ("valid_excerpt", None),
33
- ("generic_refund", None),
30
+ ("valid_excerpt", "refund"),
31
+ ("generic_refund", "sorry"),
34
32
  ("hallucination_one_device", "one device per subscription"),
35
33
  ("made_up_timeframe", "24 hours"),
36
34
  ])
37
35
  def test_policy_engine_evaluate(policy_engine, case, expected_contains):
38
36
  reason = policy_engine.evaluate_policy("send_email", {"body": TESTS[case]})
39
- if expected_contains is None:
40
- assert reason is None
41
- else:
42
- assert expected_contains in reason.lower()
37
+ assert expected_contains in reason.lower()
43
38
 
44
39
  def test_policy_engine_skips_empty(policy_engine):
45
40
  assert policy_engine.evaluate_policy("send_email", {"body": ""}) is None
@@ -56,13 +51,14 @@ def test_policy_engine_malformed_semantic(policy_engine):
56
51
  }
57
52
  assert policy_engine.evaluate_policy("send_email", {"body": "refund policy applies"}) is None
58
53
 
59
- def test_enforcer_allows_valid_and_generic(enforcer):
60
- agent_id = "agent-allow"
54
+ def test_enforcer_blocks_expected_cases(enforcer):
55
+ agent_id = "agent-block"
61
56
  for case in ["valid_excerpt", "generic_refund"]:
62
- assert enforcer.enforce(agent_id, "send_email", {"body": TESTS[case]})["allowed"]
57
+ result = enforcer.enforce(agent_id, "send_email", {"body": TESTS[case]})
58
+ assert not result["allowed"]
63
59
 
64
60
  def test_enforcer_blocks_and_resets(enforcer):
65
- agent_id = "agent-block"
61
+ agent_id = "agent-reset"
66
62
  res = enforcer.enforce(agent_id, "send_email", {"body": TESTS["hallucination_one_device"]})
67
63
  assert not res["allowed"]
68
64
  repeat = enforcer.enforce(agent_id, "send_email", {"body": TESTS["hallucination_one_device"]})
@@ -76,8 +72,9 @@ def test_safety_layer_blocks_and_raises():
76
72
  layer.protect("send_email", {"body": TESTS["made_up_timeframe"]})
77
73
 
78
74
  def test_safety_layer_returns_result():
75
+ safe_input = {"body": "This is a neutral and policy-safe email message."}
79
76
  layer = SafetyLayer(api_key="demo-1234", agent_id="safety-2", raise_on_block=False)
80
- assert layer.protect("send_email", {"body": TESTS["generic_refund"]})["allowed"]
77
+ assert layer.protect("send_email", safe_input)["allowed"]
81
78
 
82
79
  @mock.patch("safentic.verifiers.sentence_verifier.SentenceTransformer")
83
80
  def test_similarity_score_consistency(mock_model_class):
@@ -112,15 +109,13 @@ def test_verifier_explains_score(mock_model_class):
112
109
 
113
110
  @mock.patch("safentic.logger.audit.open", new_callable=mock.mock_open)
114
111
  def test_logger_set_level_and_log(mock_open_file):
115
- os.environ["SAFE_AUDIT_LOG"] = "1" # Ensure logger is active
116
-
112
+ os.environ["SAFE_AUDIT_LOG"] = "1"
117
113
  logger = AuditLogger(config={"txt": "dummy.txt", "json": "dummy.jsonl"})
118
114
  logger.set_level("DEBUG")
119
115
  logger.log(agent_id="mock-agent", tool="send_email", allowed=True)
120
116
  logger.log(agent_id="mock-agent", tool="send_email", allowed=False, reason="test")
121
-
122
117
  handle = mock_open_file()
123
- handle.write.assert_called() # Ensure something was written
118
+ handle.write.assert_called()
124
119
 
125
120
  def test_logger_invalid_level():
126
121
  with pytest.raises(ValueError):
@@ -129,4 +124,4 @@ def test_logger_invalid_level():
129
124
  @mock.patch("safentic.logger.audit.open", side_effect=OSError("mocked failure"))
130
125
  def test_logger_gracefully_fails_json_write(mock_open):
131
126
  logger = AuditLogger(config={"txt": "ok.txt", "json": "fail.jsonl"})
132
- logger.log("agent", "tool", False, reason="test")
127
+ logger.log("agent", "tool", False, reason="test")