@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.148.10",
3
+ "version": "1.148.12",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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/", "scripts/", "docs/", "policies/", "test/", "tests/",
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
- file_path = args.get("file_path") or args.get("path") or args.get("target") or ""
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
- spawn = bool(autoroute_on and hint and file_path and file_path not in seen_files)
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 file_path in seen_files:
62
- message = reason + " [already auto-routed to `uap deliver` for this file — see .uap/autoroute.log / pending-deliver.jsonl]"
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(d["file_path"].replace("\n", " ").replace("\r", " ") + "\n")
183
+ f.write(key.replace("\n", " ").replace("\r", " ") + "\n")
165
184
  except Exception:
166
185
  pass
167
186
  _spawn_deliver(root, d["hint"])
@@ -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("scripts/build.js", self.root))
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(