@miller-tech/uap 1.148.10 → 1.148.12
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 +17 -1
- package/templates/hooks/deliver_autoroute.py +26 -7
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/tests/test_deliver_autoroute.py +48 -0
- package/tools/agents/tests/test_delivery_enforcement_exemptions.py +66 -0
- package/tools/agents/tests/test_delivery_enforcement_worktree.py +8 -1
package/package.json
CHANGED
|
Binary file
|
|
@@ -89,10 +89,26 @@ BROWSER_BINS = (
|
|
|
89
89
|
# source straight into it (observed: 24 source files written to
|
|
90
90
|
# .worktrees/001-space-shooter/ with no deliver run). Gating it behind the active
|
|
91
91
|
# flag closes that hole.
|
|
92
|
+
# What stays exempt, and WHY — each is a deliberate carve-out, not an oversight:
|
|
93
|
+
# * agent/enforcement infrastructure (.claude/ .cursor/ .opencode/ .codex/
|
|
94
|
+
# .forge/ .omp/ .uap/ .policy-tools/): the hooks, plugins and enforcers that
|
|
95
|
+
# RUN the gate. Routing them through deliver is a bootstrap deadlock — an
|
|
96
|
+
# agent repairing a broken hook would need that hook to work. These are
|
|
97
|
+
# guarded by enforcement-self-protect (which blocks WEAKENING them) instead.
|
|
98
|
+
# * src/policies/, policies/: the policy definitions themselves — same
|
|
99
|
+
# self-reference; likewise guarded by self-protect rather than by routing.
|
|
100
|
+
# * test/, tests/: the deliberate fast feedback loop (deliver still verifies
|
|
101
|
+
# them via the gate ladder). _is_test_file() covers test files living outside
|
|
102
|
+
# these dirs.
|
|
103
|
+
#
|
|
104
|
+
# REMOVED 2026-07-13: "scripts/" and "docs/". Shell/Python/etc. under scripts/
|
|
105
|
+
# EXECUTES — it is code, so it must route through deliver and be tested like any
|
|
106
|
+
# other source. Exempting it left an entire language (shell) silently ungated:
|
|
107
|
+
# a model could put its whole implementation in scripts/ and never be validated.
|
|
92
108
|
EXEMPT_PREFIXES = (
|
|
93
109
|
".claude/", ".cursor/", ".opencode/", ".codex/", ".forge/", ".omp/",
|
|
94
110
|
".uap/", ".policy-tools/",
|
|
95
|
-
"src/policies/", "
|
|
111
|
+
"src/policies/", "policies/", "test/", "tests/",
|
|
96
112
|
)
|
|
97
113
|
|
|
98
114
|
# Test files are protected by deliver itself; never gate them here. Covers the
|
|
@@ -47,21 +47,36 @@ def decide(out: dict, tool: str, args: dict, autoroute_on: bool, seen_files: set
|
|
|
47
47
|
m = re.fullmatch(r'uap deliver "(.+)"', hint.strip())
|
|
48
48
|
if m:
|
|
49
49
|
hint = m.group(1)
|
|
50
|
-
|
|
50
|
+
# Accept the file-path key under ANY agent spelling. The enforcer was fixed
|
|
51
|
+
# for this long ago; autoroute was not — so for opencode (which sends
|
|
52
|
+
# `filePath`) file_path was always "", `spawn` was always False, and autoroute
|
|
53
|
+
# was INERT: the gate blocked the edit, logged the intent, told the model to
|
|
54
|
+
# call deliver… and deliver never ran. Observed live: 3 routed intents, 0
|
|
55
|
+
# deliver runs, 0 files changed — work blocked but never delivered.
|
|
56
|
+
file_path = (
|
|
57
|
+
args.get("file_path") or args.get("filePath") or args.get("path")
|
|
58
|
+
or args.get("target") or args.get("filename") or args.get("file") or ""
|
|
59
|
+
)
|
|
51
60
|
|
|
52
61
|
if route != "deliver":
|
|
53
62
|
return {"message": reason, "route": route, "spawn": False,
|
|
54
|
-
"file_path": file_path, "hint": hint, "intent": None}
|
|
63
|
+
"file_path": file_path, "hint": hint, "dedup_key": "", "intent": None}
|
|
55
64
|
|
|
56
65
|
intent = {"ts": int(time.time()), "tool": tool, "file_path": file_path, "hint": hint}
|
|
57
|
-
|
|
66
|
+
# Dedup on the file when we have one, else on the hint itself. Requiring a
|
|
67
|
+
# file_path made an entire class unspawnable: a BASH-routed source-write
|
|
68
|
+
# (`cat > app.js <<EOF`) carries a `command`, not a path — so those intents
|
|
69
|
+
# were blocked and then silently dropped. The hint is what deliver actually
|
|
70
|
+
# runs, so it is the correct spawn key.
|
|
71
|
+
dedup_key = file_path or hint
|
|
72
|
+
spawn = bool(autoroute_on and hint and dedup_key and dedup_key not in seen_files)
|
|
58
73
|
message = reason
|
|
59
74
|
if spawn:
|
|
60
75
|
message = reason + " [auto-routed to `uap deliver` — running in the background]"
|
|
61
|
-
elif autoroute_on and
|
|
62
|
-
message = reason + " [already auto-routed to `uap deliver` for this
|
|
76
|
+
elif autoroute_on and dedup_key and dedup_key in seen_files:
|
|
77
|
+
message = reason + " [already auto-routed to `uap deliver` for this change — see .uap/autoroute.log / pending-deliver.jsonl]"
|
|
63
78
|
return {"message": message, "route": route, "spawn": spawn,
|
|
64
|
-
"file_path": file_path, "hint": hint, "intent": intent}
|
|
79
|
+
"file_path": file_path, "hint": hint, "dedup_key": dedup_key, "intent": intent}
|
|
65
80
|
|
|
66
81
|
|
|
67
82
|
def _seen_path(root: Path) -> Path:
|
|
@@ -160,8 +175,12 @@ def main() -> None:
|
|
|
160
175
|
|
|
161
176
|
if d["spawn"]:
|
|
162
177
|
try:
|
|
178
|
+
# Dedup on the SAME key `decide` gated on (file when present, else the
|
|
179
|
+
# hint) — writing file_path here would record "" for a bash-routed
|
|
180
|
+
# intent and never dedup it.
|
|
181
|
+
key = d.get("dedup_key") or d["file_path"] or d["hint"]
|
|
163
182
|
with _seen_path(root).open("a") as f:
|
|
164
|
-
f.write(
|
|
183
|
+
f.write(key.replace("\n", " ").replace("\r", " ") + "\n")
|
|
165
184
|
except Exception:
|
|
166
185
|
pass
|
|
167
186
|
_spawn_deliver(root, d["hint"])
|
|
Binary file
|
|
@@ -69,5 +69,53 @@ class LoggingTest(unittest.TestCase):
|
|
|
69
69
|
self.assertEqual(rec["file_path"], "/repo/src/foo.ts")
|
|
70
70
|
|
|
71
71
|
|
|
72
|
+
class AgentKeySpellingTest(unittest.TestCase):
|
|
73
|
+
"""autoroute must accept EVERY agent's file-path key.
|
|
74
|
+
|
|
75
|
+
The enforcer was fixed for this long ago; autoroute was not — so for opencode
|
|
76
|
+
(which sends `filePath`) file_path was always "", `spawn` was always False, and
|
|
77
|
+
autoroute was INERT: the gate blocked the edit, logged the intent, told the
|
|
78
|
+
model to call deliver… and deliver never ran. Observed live: 3 routed intents,
|
|
79
|
+
0 deliver runs, 0 files changed — work blocked but never delivered. The old
|
|
80
|
+
tests only ever passed snake_case, which is why this went unnoticed.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def test_opencode_filePath_spawns(self):
|
|
84
|
+
d = mod.decide(BLOCK_OUT, "Write", {"filePath": "/repo/src/foo.ts"}, True, set())
|
|
85
|
+
self.assertTrue(d["spawn"])
|
|
86
|
+
self.assertEqual(d["dedup_key"], "/repo/src/foo.ts")
|
|
87
|
+
|
|
88
|
+
def test_claude_file_path_still_spawns(self):
|
|
89
|
+
d = mod.decide(BLOCK_OUT, "Write", {"file_path": "/repo/src/foo.ts"}, True, set())
|
|
90
|
+
self.assertTrue(d["spawn"])
|
|
91
|
+
|
|
92
|
+
def test_other_spellings(self):
|
|
93
|
+
for key in ("path", "target", "filename", "file"):
|
|
94
|
+
d = mod.decide(BLOCK_OUT, "Write", {key: "/repo/src/foo.ts"}, True, set())
|
|
95
|
+
self.assertTrue(d["spawn"], key)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class BashRoutedIntentTest(unittest.TestCase):
|
|
99
|
+
"""A BASH-routed source-write carries a `command`, not a path — requiring
|
|
100
|
+
file_path made that whole class unspawnable (blocked, then silently dropped).
|
|
101
|
+
The hint is what deliver actually runs, so it is the correct spawn key."""
|
|
102
|
+
|
|
103
|
+
def test_bash_intent_spawns_on_hint_alone(self):
|
|
104
|
+
d = mod.decide(BLOCK_OUT, "Bash", {"command": "cat > src/app.js <<EOF"}, True, set())
|
|
105
|
+
self.assertTrue(d["spawn"])
|
|
106
|
+
self.assertEqual(d["file_path"], "") # genuinely has no path
|
|
107
|
+
self.assertEqual(d["dedup_key"], d["hint"]) # …so it dedups on the hint
|
|
108
|
+
|
|
109
|
+
def test_bash_intent_dedupes_on_hint(self):
|
|
110
|
+
d1 = mod.decide(BLOCK_OUT, "Bash", {"command": "cat > a.js <<EOF"}, True, set())
|
|
111
|
+
d2 = mod.decide(BLOCK_OUT, "Bash", {"command": "cat > a.js <<EOF"}, True, {d1["dedup_key"]})
|
|
112
|
+
self.assertTrue(d1["spawn"])
|
|
113
|
+
self.assertFalse(d2["spawn"]) # no double-spawn for the same change
|
|
114
|
+
|
|
115
|
+
def test_still_never_spawns_without_a_hint(self):
|
|
116
|
+
d = mod.decide({"reason": "x", "route": "deliver"}, "Bash", {"command": "cat > a.js"}, True, set())
|
|
117
|
+
self.assertFalse(d["spawn"])
|
|
118
|
+
|
|
119
|
+
|
|
72
120
|
if __name__ == "__main__":
|
|
73
121
|
unittest.main()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""EXEMPT_PREFIXES must exempt ONLY what is genuinely un-routable.
|
|
3
|
+
|
|
4
|
+
`scripts/` and `docs/` used to be exempt — but shell/Python under scripts/ EXECUTES.
|
|
5
|
+
It is code, so it must route through deliver and be tested like any other source.
|
|
6
|
+
Exempting it left an entire language silently ungated: a model could put its whole
|
|
7
|
+
implementation in scripts/ and never be validated.
|
|
8
|
+
|
|
9
|
+
What remains exempt is deliberate:
|
|
10
|
+
* agent/enforcement infra (.uap/ .opencode/ .claude/ .policy-tools/ …) — the
|
|
11
|
+
hooks that RUN the gate; routing them is a bootstrap deadlock. Guarded by
|
|
12
|
+
enforcement-self-protect instead.
|
|
13
|
+
* src/policies/, policies/ — the policy definitions themselves (same self-reference).
|
|
14
|
+
* test/, tests/ — the deliberate fast feedback loop.
|
|
15
|
+
"""
|
|
16
|
+
import json, os, subprocess, sys, unittest
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
ENF = Path(__file__).resolve().parents[3] / "src" / "policies" / "enforcers" / "delivery_enforcement.py"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run(path):
|
|
23
|
+
e = {**os.environ, "UAP_ENFORCE_DELIVERY": "block", "UAP_INFERENCE_ENDPOINT": "http://172.17.0.1:8080/v1"}
|
|
24
|
+
for k in ("UAP_DELIVER_ACTIVE", "UAP_DELIVER_BYPASS", "UAP_DELIVER_LOCAL_MODE", "UAP_DELIVER_LOCAL_ADVISORY"):
|
|
25
|
+
e.pop(k, None)
|
|
26
|
+
p = subprocess.run(
|
|
27
|
+
[sys.executable, str(ENF), "--operation", "Write",
|
|
28
|
+
"--args", json.dumps({"file_path": path, "content": "x" * 2000})],
|
|
29
|
+
capture_output=True, text=True, env=e, cwd=str(ENF.parents[3]),
|
|
30
|
+
)
|
|
31
|
+
return p.returncode, p.stdout
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TightenedExemptionsTest(unittest.TestCase):
|
|
35
|
+
def test_scripts_and_docs_are_now_GATED(self):
|
|
36
|
+
# These execute — they are code and must route through deliver.
|
|
37
|
+
for path in ("scripts/deploy.sh", "scripts/build.py", "scripts/tool.ts", "docs/example.py"):
|
|
38
|
+
code, out = run(path)
|
|
39
|
+
self.assertEqual(code, 2, f"{path} must be GATED (it is code) — got {out}")
|
|
40
|
+
self.assertIn("route", out, path)
|
|
41
|
+
|
|
42
|
+
def test_agent_enforcement_infra_stays_exempt(self):
|
|
43
|
+
# Routing the hooks that RUN the gate is a bootstrap deadlock; these are
|
|
44
|
+
# guarded by enforcement-self-protect, not by delivery routing.
|
|
45
|
+
for path in (".uap/hook.py", ".opencode/plugin.ts", ".claude/hooks/x.sh", ".policy-tools/e.py"):
|
|
46
|
+
code, out = run(path)
|
|
47
|
+
self.assertEqual(code, 0, f"{path} must stay exempt — got {out}")
|
|
48
|
+
|
|
49
|
+
def test_policy_definitions_stay_exempt(self):
|
|
50
|
+
for path in ("src/policies/enforcers/x.py", "policies/custom.py"):
|
|
51
|
+
code, out = run(path)
|
|
52
|
+
self.assertEqual(code, 0, f"{path} must stay exempt — got {out}")
|
|
53
|
+
|
|
54
|
+
def test_test_dirs_stay_exempt(self):
|
|
55
|
+
# Deliberate fast feedback loop; deliver still verifies them via the ladder.
|
|
56
|
+
for path in ("test/helper.ts", "tests/fixtures/data.py"):
|
|
57
|
+
code, out = run(path)
|
|
58
|
+
self.assertEqual(code, 0, f"{path} must stay exempt — got {out}")
|
|
59
|
+
|
|
60
|
+
def test_ordinary_source_still_gated(self):
|
|
61
|
+
code, out = run("src/app.ts")
|
|
62
|
+
self.assertEqual(code, 2, out)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
unittest.main()
|
|
@@ -66,8 +66,15 @@ class TestDeliveryEnforcementWorktree(unittest.TestCase):
|
|
|
66
66
|
self.assertFalse(run("src/game.js", self.root))
|
|
67
67
|
|
|
68
68
|
def test_other_exemptions_intact(self):
|
|
69
|
+
# Agent/enforcement infra stays exempt (routing the hooks that RUN the
|
|
70
|
+
# gate is a bootstrap deadlock).
|
|
69
71
|
self.assertTrue(run(".uap/state.js", self.root))
|
|
70
|
-
self.assertTrue(run("
|
|
72
|
+
self.assertTrue(run("test/helper.js", self.root))
|
|
73
|
+
|
|
74
|
+
def test_scripts_no_longer_exempt(self):
|
|
75
|
+
# TIGHTENED: scripts/ executes — it is code, so it must route through
|
|
76
|
+
# deliver and be tested like any other source.
|
|
77
|
+
self.assertFalse(run("scripts/build.js", self.root))
|
|
71
78
|
|
|
72
79
|
def test_bypass_override_still_works(self):
|
|
73
80
|
self.assertTrue(
|