@miller-tech/uap 1.172.1 → 1.172.6

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,188 @@
1
+ #!/usr/bin/env python3
2
+ """Precedence: the deliver mandate outranks recon convergence.
3
+
4
+ Both guards run on the same turn (mandate first) and both rewrite
5
+ tools/tool_choice, so without an explicit rule the later one -- recon -- wins
6
+ and silently releases the pin. That made the mandate's own docstring claim
7
+ ("runs before the softer guards so the pin stands") untrue.
8
+
9
+ The regression is only observable now: until v1.172.2 the pin was encoded in a
10
+ form llama.cpp ignored, so recon had nothing to override.
11
+ """
12
+
13
+ import importlib.util
14
+ import os
15
+ import unittest
16
+ from pathlib import Path
17
+
18
+
19
+ def _load_proxy(env: dict | None = None):
20
+ """Import the proxy with `env` applied, then restore the environment.
21
+
22
+ Module-level constants (PROXY_*) are read at import, so a knob can only be
23
+ exercised by re-importing under a different environment.
24
+ """
25
+ saved = {k: os.environ.get(k) for k in (env or {})}
26
+ try:
27
+ for k, v in (env or {}).items():
28
+ os.environ[k] = v
29
+ p = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
30
+ spec = importlib.util.spec_from_file_location("anthropic_proxy_mbr", p)
31
+ assert spec is not None and spec.loader is not None
32
+ m = importlib.util.module_from_spec(spec)
33
+ spec.loader.exec_module(m)
34
+ return m
35
+ finally:
36
+ for k, v in saved.items():
37
+ if v is None:
38
+ os.environ.pop(k, None)
39
+ else:
40
+ os.environ[k] = v
41
+
42
+
43
+ proxy = _load_proxy()
44
+
45
+ DELIVER_TOOL = {"type": "function", "function": {"name": "deliver", "parameters": {}}}
46
+ READ_TOOL = {"type": "function", "function": {"name": "read", "parameters": {}}}
47
+ GREP_TOOL = {"type": "function", "function": {"name": "grep", "parameters": {}}}
48
+ BLOCK_MSG = (
49
+ "BLOCKED: do not edit 'src/foo.ts' directly. To create or change code, call "
50
+ "the `deliver` tool (or run: uap deliver \"...\"). Do NOT retry this edit."
51
+ )
52
+
53
+
54
+ def _blocked_body(tools):
55
+ return {
56
+ "tools": list(tools),
57
+ "messages": [
58
+ {"role": "assistant", "content": "editing",
59
+ "tool_calls": [{"function": {"name": "edit"}}]},
60
+ {"role": "tool", "content": BLOCK_MSG},
61
+ ],
62
+ "tool_choice": "auto",
63
+ }
64
+
65
+
66
+ def _deep_recon(monitor, mod):
67
+ """Put the monitor well past the hard recon tier, so recon would definitely
68
+ fire and rewrite the request if it were allowed to run."""
69
+ monitor.consecutive_no_write_turns = int(
70
+ mod.PROXY_RECON_CONVERGENCE_THRESHOLD * mod.PROXY_RECON_HARD_MULTIPLIER
71
+ ) + 10
72
+
73
+
74
+ class MandateBeatsReconTest(unittest.TestCase):
75
+ def test_recon_cannot_release_a_pinned_turn(self):
76
+ mon = proxy.SessionMonitor()
77
+ mon.mandate_deliver_active = False
78
+ body = _blocked_body([READ_TOOL, GREP_TOOL, DELIVER_TOOL])
79
+
80
+ proxy._maybe_inject_mandate_deliver(body, mon)
81
+ self.assertTrue(mon.mandate_deliver_active)
82
+ self.assertEqual(body["tool_choice"], "required")
83
+ self.assertEqual([t["function"]["name"] for t in body["tools"]], ["deliver"])
84
+
85
+ _deep_recon(mon, proxy)
86
+ proxy._maybe_inject_recon_convergence(body, mon, [READ_TOOL, GREP_TOOL, DELIVER_TOOL])
87
+
88
+ # The pin must survive: still required, still exactly one tool.
89
+ self.assertEqual(body["tool_choice"], "required")
90
+ self.assertEqual([t["function"]["name"] for t in body["tools"]], ["deliver"])
91
+
92
+ def test_recon_still_runs_when_no_mandate_is_active(self):
93
+ """The suppression must be scoped to mandated turns -- recon is the
94
+ read-forever deadlock breaker and must keep working otherwise."""
95
+ mon = proxy.SessionMonitor()
96
+ mon.mandate_deliver_active = False
97
+ body = {"tools": [READ_TOOL, GREP_TOOL], "messages": [{"role": "user", "content": "explore"}]}
98
+ _deep_recon(mon, proxy)
99
+
100
+ before = mon.recon_hard_fires
101
+ proxy._maybe_inject_recon_convergence(body, mon, [READ_TOOL, GREP_TOOL])
102
+ self.assertGreater(mon.recon_hard_fires, before)
103
+
104
+ def test_flag_is_cleared_on_a_TOOL_LESS_turn(self):
105
+ """Regression: the reset used to live beside finalize_turn_active in
106
+ build_openai_request, which sits inside `if has_tools:` -- while both
107
+ _maybe_inject_mandate_deliver and _maybe_inject_recon_convergence are
108
+ called unconditionally. A tool-less turn therefore skipped the reset,
109
+ the mandate returned early (no deliver tool to find), and recon read a
110
+ stale True from an earlier turn and suppressed itself.
111
+
112
+ The mandate now owns the flag and clears it on every turn, so this
113
+ sequence -- mandate turn, then a turn with no tools at all -- must leave
114
+ it False.
115
+ """
116
+ mon = proxy.SessionMonitor()
117
+ proxy._maybe_inject_mandate_deliver(_blocked_body([DELIVER_TOOL]), mon)
118
+ self.assertTrue(mon.mandate_deliver_active)
119
+
120
+ proxy._maybe_inject_mandate_deliver({"messages": [{"role": "user", "content": "hi"}]}, mon)
121
+ self.assertFalse(mon.mandate_deliver_active)
122
+
123
+ def test_flag_is_cleared_even_when_the_mandate_is_disabled(self):
124
+ """The clear precedes the PROXY_MANDATE_DELIVER check, so turning the
125
+ mandate off cannot strand a True set while it was on."""
126
+ mod = _load_proxy({"PROXY_MANDATE_DELIVER": "off"})
127
+ self.assertFalse(mod.PROXY_MANDATE_DELIVER)
128
+ mon = mod.SessionMonitor()
129
+ mon.mandate_deliver_active = True # as if set on a previous turn
130
+ mod._maybe_inject_mandate_deliver(_blocked_body([DELIVER_TOOL]), mon)
131
+ self.assertFalse(mon.mandate_deliver_active)
132
+
133
+ def test_flag_is_per_turn_not_sticky(self):
134
+ """A stale True would suppress recon forever after one mandate. The
135
+ request builder clears it each turn; assert the field is not latched by
136
+ the mandate itself."""
137
+ mon = proxy.SessionMonitor()
138
+ self.assertFalse(mon.mandate_deliver_active)
139
+ body = _blocked_body([DELIVER_TOOL])
140
+ proxy._maybe_inject_mandate_deliver(body, mon)
141
+ self.assertTrue(mon.mandate_deliver_active)
142
+
143
+ # Simulate the next turn's reset, then a turn with no block present.
144
+ mon.mandate_deliver_active = False
145
+ clean = {"tools": [DELIVER_TOOL], "messages": [{"role": "user", "content": "hello"}]}
146
+ proxy._maybe_inject_mandate_deliver(clean, mon)
147
+ self.assertFalse(mon.mandate_deliver_active)
148
+
149
+ def test_no_mandate_claimed_when_pin_is_impossible(self):
150
+ """If deliver is absent the pin cannot be applied. Recording a mandate
151
+ anyway would suppress recon while leaving the model free to hand-edit --
152
+ strictly worse than letting recon run."""
153
+ mon = proxy.SessionMonitor()
154
+ body = _blocked_body([READ_TOOL])
155
+ proxy._maybe_inject_mandate_deliver(body, mon)
156
+ self.assertFalse(mon.mandate_deliver_active)
157
+ self.assertEqual(mon.mandate_deliver_fires, 0)
158
+
159
+ def test_precedence_off_reproduces_the_bug_it_fixes(self):
160
+ """PROXY_MANDATE_BEATS_RECON=off restores the old ordering -- and pins
161
+ down exactly what that ordering costs.
162
+
163
+ This is the load-bearing assertion of the whole change: with the
164
+ precedence disabled, recon's hard tier sets tool_choice back to "auto",
165
+ releasing a mandate raised because a source edit had just been BLOCKED.
166
+ The model is then free to do what it was blocked for. If this test ever
167
+ stops reproducing that release, the precedence guard has become dead
168
+ code and the suppression above is no longer protecting anything.
169
+ """
170
+ mod = _load_proxy({"PROXY_MANDATE_BEATS_RECON": "off"})
171
+ self.assertFalse(mod.PROXY_MANDATE_BEATS_RECON)
172
+
173
+ mon = mod.SessionMonitor()
174
+ body = _blocked_body([READ_TOOL, GREP_TOOL, DELIVER_TOOL])
175
+ mod._maybe_inject_mandate_deliver(body, mon)
176
+ self.assertTrue(mon.mandate_deliver_active)
177
+ self.assertEqual(body["tool_choice"], "required")
178
+
179
+ _deep_recon(mon, mod)
180
+ before = mon.recon_hard_fires
181
+ mod._maybe_inject_recon_convergence(body, mon, [READ_TOOL, GREP_TOOL, DELIVER_TOOL])
182
+
183
+ self.assertGreater(mon.recon_hard_fires, before) # recon ran
184
+ self.assertEqual(body["tool_choice"], "auto") # ...and released the pin
185
+
186
+
187
+ if __name__ == "__main__":
188
+ unittest.main()
@@ -46,7 +46,11 @@ class MandateDeliverTest(unittest.TestCase):
46
46
  {"role": "tool", "content": BLOCK_MSG},
47
47
  ], tools=[DELIVER_TOOL])
48
48
  proxy._maybe_inject_mandate_deliver(b, self.mon)
49
- self.assertEqual(b["tool_choice"], {"type": "function", "function": {"name": "deliver"}})
49
+ # "required" + a one-tool list, NOT the OpenAI object form: llama.cpp
50
+ # parses tool_choice as a string and silently falls back to "auto" when
51
+ # handed an object, which un-pinned every mandate.
52
+ self.assertEqual(b["tool_choice"], "required")
53
+ self.assertEqual([t["function"]["name"] for t in b["tools"]], ["deliver"])
50
54
  self.assertEqual(self.mon.mandate_deliver_fires, 1)
51
55
  self.assertTrue(any("MANDATORY" in (m.get("content") or "") for m in b["messages"]))
52
56
 
@@ -79,7 +83,36 @@ class MandateDeliverTest(unittest.TestCase):
79
83
  mcp_deliver = {"type": "function", "function": {"name": "mcp__uap-router__deliver", "parameters": {}}}
80
84
  b = _body([{"role": "tool", "content": BLOCK_MSG}], tools=[mcp_deliver])
81
85
  proxy._maybe_inject_mandate_deliver(b, self.mon)
82
- self.assertEqual(b["tool_choice"], {"type": "function", "function": {"name": "mcp__uap-router__deliver"}})
86
+ self.assertEqual(b["tool_choice"], "required")
87
+ self.assertEqual(
88
+ [t["function"]["name"] for t in b["tools"]], ["mcp__uap-router__deliver"]
89
+ )
90
+
91
+ def test_pin_narrows_tools_to_the_single_mandated_tool(self):
92
+ """The pin must REMOVE the other tools. 'required' only means "some
93
+ tool", so leaving `read` advertised would let the model satisfy the
94
+ mandate by reading a file instead of delivering."""
95
+ b = _body([{"role": "tool", "content": BLOCK_MSG}], tools=[READ_TOOL, DELIVER_TOOL])
96
+ proxy._maybe_inject_mandate_deliver(b, self.mon)
97
+ self.assertEqual(b["tool_choice"], "required")
98
+ self.assertEqual([t["function"]["name"] for t in b["tools"]], ["deliver"])
99
+
100
+ def test_tool_choice_is_never_an_object(self):
101
+ """Regression guard for the llama.cpp string-only parser. An object here
102
+ is not a hard error upstream -- it degrades to "auto" with only a log
103
+ line -- so nothing downstream would have caught the regression."""
104
+ b = _body([{"role": "tool", "content": BLOCK_MSG}], tools=[DELIVER_TOOL])
105
+ proxy._maybe_inject_mandate_deliver(b, self.mon)
106
+ self.assertIsInstance(b["tool_choice"], str)
107
+ self.assertIn(b["tool_choice"], {"auto", "none", "required"})
108
+
109
+ def test_pin_is_a_noop_when_the_named_tool_is_absent(self):
110
+ """Forcing "required" while the mandated tool is missing would demand a
111
+ call the model cannot make. _pin_tool_choice_to must decline instead."""
112
+ b = _body([{"role": "user", "content": "hi"}], tools=[READ_TOOL], tool_choice="auto")
113
+ self.assertFalse(proxy._pin_tool_choice_to(b, "deliver"))
114
+ self.assertEqual(b["tool_choice"], "auto")
115
+ self.assertEqual([t["function"]["name"] for t in b["tools"]], ["read"])
83
116
 
84
117
  def test_disabled_by_env(self):
85
118
  old = os.environ.get("PROXY_MANDATE_DELIVER")