@miller-tech/uap 1.82.0 → 1.84.0
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.
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/delivery_enforcement.py +33 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +28 -0
- package/tools/agents/tests/test_delivery_enforcement_worktree.py +57 -0
- package/tools/agents/tests/test_recon_deliver_gate.py +45 -0
package/package.json
CHANGED
|
Binary file
|
|
@@ -54,6 +54,34 @@ EXEMPT_PREFIXES = (
|
|
|
54
54
|
TEST_MARKERS = (".test.", ".spec.", "_test.", "/test/", "/tests/", "/__tests__/")
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
def _is_local_model_session() -> bool:
|
|
58
|
+
"""True when this session targets a self-hosted/local model endpoint."""
|
|
59
|
+
base = (
|
|
60
|
+
os.environ.get("ANTHROPIC_BASE_URL")
|
|
61
|
+
or os.environ.get("OPENAI_BASE_URL")
|
|
62
|
+
or os.environ.get("UAP_INFERENCE_ENDPOINT")
|
|
63
|
+
or ""
|
|
64
|
+
).lower()
|
|
65
|
+
return any(h in base for h in ("127.0.0.1", "localhost", "0.0.0.0", "::1"))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# #2/#3a: how a LOCAL-model session is handled under block mode. A plain local
|
|
69
|
+
# session deadlocks under strict block (can read, never write -> recon loop,
|
|
70
|
+
# observed live). UAP_DELIVER_LOCAL_MODE selects the resolution:
|
|
71
|
+
# advisory (default) -> allow direct writes (fast; the proxy guards the model)
|
|
72
|
+
# deliver -> keep the block + route:deliver so the change is driven
|
|
73
|
+
# through `uap deliver` (VERIFIED path; pairs with
|
|
74
|
+
# UAP_DELIVER_AUTOROUTE)
|
|
75
|
+
# block -> strict block (no relaxation)
|
|
76
|
+
# Back-compat: UAP_DELIVER_LOCAL_ADVISORY=0 maps to "block".
|
|
77
|
+
def _local_mode() -> str:
|
|
78
|
+
m = os.environ.get("UAP_DELIVER_LOCAL_MODE", "").lower()
|
|
79
|
+
if m in {"advisory", "deliver", "block"}:
|
|
80
|
+
return m
|
|
81
|
+
adv = os.environ.get("UAP_DELIVER_LOCAL_ADVISORY", "on").lower()
|
|
82
|
+
return "advisory" if adv not in {"0", "off", "false", "no", ""} else "block"
|
|
83
|
+
|
|
84
|
+
|
|
57
85
|
def main() -> None:
|
|
58
86
|
op, args = parse_cli()
|
|
59
87
|
if op not in EDIT_OPS:
|
|
@@ -99,6 +127,11 @@ def main() -> None:
|
|
|
99
127
|
)
|
|
100
128
|
|
|
101
129
|
mode = os.environ.get("UAP_ENFORCE_DELIVERY", "block").lower()
|
|
130
|
+
if mode == "block" and _is_local_model_session():
|
|
131
|
+
# #3a: route-through-deliver keeps the block (route:deliver -> autoroute
|
|
132
|
+
# drives the verified deliver loop); advisory unblocks direct writes.
|
|
133
|
+
if _local_mode() == "advisory":
|
|
134
|
+
mode = "advisory"
|
|
102
135
|
if mode == "block":
|
|
103
136
|
# R1: emit a machine-actionable routing signal so a capable harness can
|
|
104
137
|
# auto-route the blocked change INTO `uap deliver` instead of leaving the
|
|
Binary file
|
|
@@ -3849,6 +3849,22 @@ def _resolve_state_machine_tool_choice(
|
|
|
3849
3849
|
return None, "unknown_phase"
|
|
3850
3850
|
|
|
3851
3851
|
|
|
3852
|
+
def _writes_are_gated(openai_body: dict) -> bool:
|
|
3853
|
+
"""True when recent tool results show the harness is BLOCKING direct writes
|
|
3854
|
+
(delivery-enforcement), so a "write the file" directive is futile and the
|
|
3855
|
+
model must route through the deliver tool instead. #1: aligns the recon
|
|
3856
|
+
directive with the gate to break the read-forever / can't-write deadlock."""
|
|
3857
|
+
for m in (openai_body.get("messages") or [])[-8:]:
|
|
3858
|
+
c = m.get("content")
|
|
3859
|
+
text = c if isinstance(c, str) else (json.dumps(c) if c else "")
|
|
3860
|
+
low = text.lower()
|
|
3861
|
+
if "delivery-enforcement" in low or (
|
|
3862
|
+
"blocked" in low and "deliver" in low and "tool" in low
|
|
3863
|
+
):
|
|
3864
|
+
return True
|
|
3865
|
+
return False
|
|
3866
|
+
|
|
3867
|
+
|
|
3852
3868
|
def _maybe_inject_recon_convergence(
|
|
3853
3869
|
openai_body: dict,
|
|
3854
3870
|
monitor: "SessionMonitor",
|
|
@@ -3920,6 +3936,18 @@ def _maybe_inject_recon_convergence(
|
|
|
3920
3936
|
"if strictly required to write it."
|
|
3921
3937
|
)
|
|
3922
3938
|
tier = "firm"
|
|
3939
|
+
# #1: if the harness is blocking direct writes (delivery-enforcement), a
|
|
3940
|
+
# "write the file" directive is impossible to satisfy and the model loops in
|
|
3941
|
+
# recon. Redirect it to the deliver tool — the only write path under a gate.
|
|
3942
|
+
if not escalate and _writes_are_gated(openai_body):
|
|
3943
|
+
directive += (
|
|
3944
|
+
" IMPORTANT: your direct Edit/Write calls are being BLOCKED by policy. "
|
|
3945
|
+
"Do NOT try to write the file directly. Call the `deliver` tool (or run "
|
|
3946
|
+
"`uap deliver \"<one-line task>\"`) to produce the deliverable — that is "
|
|
3947
|
+
"the only path that can write here."
|
|
3948
|
+
)
|
|
3949
|
+
tier = tier + "+deliver-gated"
|
|
3950
|
+
|
|
3923
3951
|
msgs = openai_body.get("messages", [])
|
|
3924
3952
|
msgs.append({"role": "user", "content": directive})
|
|
3925
3953
|
openai_body["messages"] = msgs
|
|
@@ -75,3 +75,60 @@ class TestDeliveryEnforcementWorktree(unittest.TestCase):
|
|
|
75
75
|
|
|
76
76
|
if __name__ == "__main__":
|
|
77
77
|
unittest.main()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
import os as _os, subprocess as _sp, sys as _sys, json as _json, tempfile as _tf
|
|
81
|
+
from pathlib import Path as _Path
|
|
82
|
+
_ENF = _Path(__file__).resolve().parents[3] / "src" / "policies" / "enforcers" / "delivery_enforcement.py"
|
|
83
|
+
|
|
84
|
+
class LocalAdvisoryTest(unittest.TestCase):
|
|
85
|
+
def _run(self, extra_env):
|
|
86
|
+
with _tf.TemporaryDirectory() as td:
|
|
87
|
+
root = _Path(td); (root/".git").mkdir()
|
|
88
|
+
f = root/"src"/"a.ts"; f.parent.mkdir(parents=True); f.write_text("x")
|
|
89
|
+
e = dict(_os.environ); e["UAP_REPO_ROOT"]=str(root)
|
|
90
|
+
for k in ("ANTHROPIC_BASE_URL","UAP_DELIVER_LOCAL_ADVISORY","UAP_DELIVER_ACTIVE"): e.pop(k, None)
|
|
91
|
+
e.update(extra_env)
|
|
92
|
+
p = _sp.run([_sys.executable, str(_ENF), "--operation","Write","--args",_json.dumps({"file_path":str(f)})], capture_output=True, text=True, env=e)
|
|
93
|
+
return p.returncode, _json.loads(p.stdout) if p.stdout.strip() else {}
|
|
94
|
+
|
|
95
|
+
def test_local_session_downgrades_to_advisory(self):
|
|
96
|
+
rc, out = self._run({"ANTHROPIC_BASE_URL":"http://127.0.0.1:4000"})
|
|
97
|
+
self.assertEqual(rc, 0); self.assertTrue(out["allowed"])
|
|
98
|
+
|
|
99
|
+
def test_local_advisory_off_keeps_block(self):
|
|
100
|
+
rc, out = self._run({"ANTHROPIC_BASE_URL":"http://127.0.0.1:4000","UAP_DELIVER_LOCAL_ADVISORY":"0"})
|
|
101
|
+
self.assertEqual(rc, 2)
|
|
102
|
+
|
|
103
|
+
def test_cloud_session_still_blocks(self):
|
|
104
|
+
rc, out = self._run({"ANTHROPIC_BASE_URL":"https://api.anthropic.com"})
|
|
105
|
+
self.assertEqual(rc, 2)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class LocalModeTest(unittest.TestCase):
|
|
109
|
+
def _run(self, env):
|
|
110
|
+
with _tf.TemporaryDirectory() as td:
|
|
111
|
+
root = _Path(td); (root/".git").mkdir()
|
|
112
|
+
f = root/"src"/"a.ts"; f.parent.mkdir(parents=True); f.write_text("x")
|
|
113
|
+
e = dict(_os.environ); e["UAP_REPO_ROOT"]=str(root)
|
|
114
|
+
for k in ("ANTHROPIC_BASE_URL","UAP_DELIVER_LOCAL_ADVISORY","UAP_DELIVER_LOCAL_MODE","UAP_DELIVER_ACTIVE"): e.pop(k, None)
|
|
115
|
+
e.update(env)
|
|
116
|
+
p = _sp.run([_sys.executable, str(_ENF), "--operation","Write","--args",_json.dumps({"file_path":str(f)})], capture_output=True, text=True, env=e)
|
|
117
|
+
return p.returncode, _json.loads(p.stdout) if p.stdout.strip() else {}
|
|
118
|
+
|
|
119
|
+
def test_local_mode_deliver_routes_through_deliver(self):
|
|
120
|
+
rc, out = self._run({"ANTHROPIC_BASE_URL":"http://127.0.0.1:4000","UAP_DELIVER_LOCAL_MODE":"deliver"})
|
|
121
|
+
self.assertEqual(rc, 2)
|
|
122
|
+
self.assertEqual(out.get("route"), "deliver")
|
|
123
|
+
|
|
124
|
+
def test_local_mode_advisory_allows(self):
|
|
125
|
+
rc, out = self._run({"ANTHROPIC_BASE_URL":"http://127.0.0.1:4000","UAP_DELIVER_LOCAL_MODE":"advisory"})
|
|
126
|
+
self.assertEqual(rc, 0); self.assertTrue(out["allowed"])
|
|
127
|
+
|
|
128
|
+
def test_local_mode_block_strict(self):
|
|
129
|
+
rc, out = self._run({"ANTHROPIC_BASE_URL":"http://127.0.0.1:4000","UAP_DELIVER_LOCAL_MODE":"block"})
|
|
130
|
+
self.assertEqual(rc, 2)
|
|
131
|
+
|
|
132
|
+
def test_default_is_advisory(self):
|
|
133
|
+
rc, out = self._run({"ANTHROPIC_BASE_URL":"http://127.0.0.1:4000"})
|
|
134
|
+
self.assertEqual(rc, 0)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Tests for #1: RECON directive is write-block aware (routes to deliver)."""
|
|
2
|
+
import importlib.util
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
proxy_path = Path(__file__).resolve().parents[3] / "tools" / "agents" / "scripts" / "anthropic_proxy.py"
|
|
7
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
|
|
8
|
+
ap = importlib.util.module_from_spec(spec)
|
|
9
|
+
spec.loader.exec_module(ap)
|
|
10
|
+
|
|
11
|
+
GATED_MSG = {"role": "user", "content": [
|
|
12
|
+
{"type": "tool_result", "content": "[UAP policy blocked: delivery-enforcement] BLOCKED: call the `deliver` tool"}
|
|
13
|
+
]}
|
|
14
|
+
PLAIN_MSG = {"role": "user", "content": "read the next file"}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WritesGatedTest(unittest.TestCase):
|
|
18
|
+
def test_detects_delivery_enforcement_block(self):
|
|
19
|
+
self.assertTrue(ap._writes_are_gated({"messages": [GATED_MSG]}))
|
|
20
|
+
|
|
21
|
+
def test_plain_messages_not_gated(self):
|
|
22
|
+
self.assertFalse(ap._writes_are_gated({"messages": [PLAIN_MSG]}))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ReconDirectiveTest(unittest.TestCase):
|
|
26
|
+
def _run(self, gated):
|
|
27
|
+
m = ap.SessionMonitor(context_window=132096)
|
|
28
|
+
m.consecutive_no_write_turns = max(1, ap.PROXY_RECON_CONVERGENCE_THRESHOLD)
|
|
29
|
+
body = {"messages": [GATED_MSG if gated else PLAIN_MSG], "tools": [{"name": "Write"}]}
|
|
30
|
+
ap._maybe_inject_recon_convergence(body, m)
|
|
31
|
+
return body["messages"][-1]["content"]
|
|
32
|
+
|
|
33
|
+
def test_gated_directive_routes_to_deliver(self):
|
|
34
|
+
d = self._run(gated=True)
|
|
35
|
+
self.assertIn("deliver", d.lower())
|
|
36
|
+
self.assertIn("BLOCKED", d)
|
|
37
|
+
|
|
38
|
+
def test_ungated_directive_says_write(self):
|
|
39
|
+
d = self._run(gated=False)
|
|
40
|
+
self.assertIn("write", d.lower())
|
|
41
|
+
self.assertNotIn("being BLOCKED by policy", d)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
unittest.main()
|