I did find a way to do this with Cypher, but it is ugly.
We're using several tricks here. We can't use FOREACH, we need to use UNWIND on indexes of the path, this also lets us use variables for the previous node which is needed so we can make sure (in the case where the current relationship's param is null) that there are no alternate relationships from the previous node that are a better fit.
Here's the test data I'm using:
unwind range(1,8) as id
create (p:Port {id:id, name:'port'+id});
match (p1:Port {id:1}),
(p2:Port {id:2}),
(p3:Port {id:3}),
(p4:Port {id:4}),
(p5:Port {id:5}),
(p6:Port {id:6}),
(p7:Port {id:7}),
(p8:Port {id:8})
create (p1)-[:r{param:2}]->(p2),
(p2)-[:r{param:1}]->(p5),
(p2)-[:r{param:3}]->(p3),
(p2)-[:r]->(p4),
(p4)-[:r{param:1}]->(p6),
(p6)-[:r{param:1}]->(p7),
(p6)-[:r{param:2}]->(p8)
and here's the query
MATCH (start:Port {name:'port1'})
MATCH path= (start)-[:r*]->(end)
WHERE not (end)-->() //not sure if you know your end node...in this case only considering nodes with no other outgoing rels
WITH path, end, [r in relationships(path) | r.param] as params
UNWIND range(1, length(path) - 1) as i
WITH path, params, nodes(path)[i] as prevNode, i
WITH path, params, size((prevNode)-[:r]->()) = 1 OR (params[i] IS NULL AND size([(prevNode)-[other:r]->() WHERE other.param = params[i-1] | other]) = 0) OR params[i] = params[i-1] as eval
WITH path, params, collect(eval) as pathEval
WHERE size(params) + pathEval + 1 and all(eval in pathEval where eval = true)
RETURN path, params
(the size(params) + pathEval + 1
is there because collect() ignores nulls...and if there is a null then something went wrong and the list will be smaller than expected and this size comparison will exclude that path)
You can of course change what is returned. The disadvantage of the Cypher approach vs a stored procedure is that this approach finds all possible paths and then filters, instead of filtering during expansion. In a case where there are many many paths this may not be an efficient approach.