Prolog uses backtracking search. Backtracking search is quite a good and generic approach. Its not the only approach. Depending on your problem space you can probably totally avoid Prolog.
When I need to solve a CP problem, I typically do one of the following
* Write a simple model in MiniZinc. This is great for experimentation and testing different solvers to see which kind of solver might be suitable (different CP solvers, MIP solvers, etc)
* For some cases or if I need to integrate with a larger system I write my code using a library directly, such as Gecode, Choco, or or-tools. Using a system directly has the benefit that it is possible to program a custom search heuristic, which I find is often very useful for solving problems in practice.
* Write a custom algorithm. Sometimes the right answer is to just write some code solving the problem directly.
I find models written in a modeling language such as MiniZinc or in a traditional Programming language such as C++, Java, or Python to be easier to read and reason about than Prolog programs that solve constraint problems. That is a personal preference, and varies depending on familiarity.
"Can this user do this thing given these constraints?" or even "Show me all the things this user can do given these constraints"
Our team is solving for this use case. While we focus a lot on abstractions, APIs, etc. that are appropriate for our use case, under the hood everything is driven by an embeddable logic language that we write in Rust, called Polar: https://docs.osohq.com/reference/polar.html
In principle, this would have allowed us to do something akin to property based integration testing (we only tested the fully integrated system, no partial integrations of subsystems or unit testing). The same program could have been used to analyze recordings of real world communications to see if they were correct (to the spec) and where they diverged if not, with only minor modification to the way we interacted with it.
That's actually one of the really nice things about Prolog, you can work in both directions with the same program, you can't do that in any of the mainstream programming languages without libraries (minikanren, for instance, allows for an embedding of this in many languages).
For instance, while trivial, you can use append/3 in Prolog to get the result of appending two lists, or to find all prefixes and suffixes of a list, or to find a particular prefix/suffix given both the combined list and the other (suffix or prefix, respectively).
append([1,2], [3,4], AB). -- AB = [1,2,3,4]
append(A, [3,4], [1,2,3,4]). -- A = [1,2]
append([1,2], B, [1,2,3,4]). -- B = [3,4]
append(A, B, [1,2,3,4]). -- A = [], B = [1,2,3,4];
-- A = [1], B = [2,3,4];
-- ...
By writing one program, perhaps supplying synonyms to get something that reads better, you can get all these options depending on which parts you choose to supply as knowns and which you leave as variables.That is, trying to converge a system from current state to desired state.
If it were written with logic programming and were able to run things in reverse, then it would be able to explain exactly why it is unable to converge the system to the desired state, and what other factors it is waiting on to do so. I think that would greatly enhance observability in a complex system.
I was also in the process of writing a chat bot accepting commands be able to autocompletion that takes authorization into account. So given what the user has typed so far, and what it is authorized to use, it would be able to show the available commands.