Repository: microsoft/rego-cpp (language: python)

Issue:
Debugging options could be more discoverable
Currently, the `rego_test` executable's help text looks like this:

```
Usage: ./tests/rego_test [OPTIONS] [case...]

Positionals:
  case TEXT ...               Test case YAML files or directories

Options:
  -h,--help                   Print this help message and exit
  --help-all                  Expand all help
  -c,--case TEXT ...          Test case YAML files or directories
  -a,--ast TEXT               Output the AST (debugging for test case parser)
  -l,--log_level TEXT         Set Log Level to one of Trace, Debug, Info, Warning, Output, Error, None
  -w,--wf                     Enable well-formedness checks (slow)
  -f,--fail-first             Stop after first test case failure
  -n,--note TEXT              Note (or note substring) of specific test to run
  ```
  
For the `-a` option, it isn't stated what the argument is - it's a directory into which the ASTs will be dumped.
  
Also maybe worth an extra note, the `-l` doesn't really describe what its options imply. For instance, `Debug` shows all the transformations performed, which is a logging level of specific interest compared to the others.

TODO: check this is also fixed for the other tool executables.
package directives that end with whitespace cause errors
Place the following in a package (note the space after `foo`): 

```rego
package foo 
```

This raises a wellformedness error, when I believe the whitespace should be silently ignored.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/pr_gate.yml b/.github/workflows/pr_gate.yml
index 8a262cbc..6d122df8 100644
--- a/.github/workflows/pr_gate.yml
+++ b/.github/workflows/pr_gate.yml
@@ -398,8 +398,8 @@ jobs:
         env:
           REGOCPP_REPO: https://github.com/${{github.repository}}
           REGOCPP_TAG: ${{github.sha}}
-          CXX: g++-11
-          CC: gcc-11
+          CXX: g++-12
+          CC: gcc-12
 
       - name: Python test
         working-directory: ${{github.workspace}}/wrappers/python/ 
diff --git a/CHANGELOG b/CHANGELOG
index 581b52a7..1199ca7a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,22 @@
 # Changelog
 
+## 2024-09-10 - Version 0.4.3
+Point release updating to Rego v0.68.0
+
+**New Features**
+- Updated the support version of Rego to v0.68.0
+- Updated to the latest build of Trieste.
+- Added a `v1-compatible` flag to the test driver and interpreter which forces rego-v1 compliance
+- Added code to run both the v0 and v1 OPA test suites
+- Added the new `strings.count` built-in
+- Improved the tool messaging around debug options
+
+**Bug Fixes**
+- Fixed an issue with recursion errors being swallowed by `not`
+- Fixed issues where undefined values were not handled correctly during unification
+- Fixed issue with whitespace after package definitions
+- Fixed issue where internal values were lingering in rule evaluations
+
 ## 2024-06-29 - Version 0.4.2
 Point release updating the Trieste version.
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 393eec53..1d45bafe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,7 +15,7 @@ list( GET REGOCPP_VERSION_LIST 1 REGOCPP_VERSION_MINOR )
 
 list( GET REGOCPP_VERSION_LIST 2 REGOCPP_VERSION_REVISION )
 
-set ( REGOCPP_OPA_VERSION 0.65.0 )
+set ( REGOCPP_OPA_VERSION 0.68.0 )
 
 set( REGOCPP_VERSION ${REGOCPP_VERSION_MAJOR}.${REGOCPP_VERSION_MINOR}.${REGOCPP_VERSION_REVISION} )
 
@@ -93,7 +93,7 @@ FetchContent_Declare(
 FetchContent_Declare(
   trieste
   GIT_REPOSITORY https://github.com/microsoft/trieste
-  GIT_TAG        7cd90387ac2de6423ed1dc43496273b0a20a03d4
+  GIT_TAG        467157a167d2e929095498089f29fc9419f4ef11
 )
 
 FetchContent_MakeAvailable(cmake_utils)
diff --git a/VERSION b/VERSION
index f7abe273..70d5b25f 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.4.2
\ No newline at end of file
+0.4.3
\ No newline at end of file
diff --git a/include/rego/rego.hh b/include/rego/rego.hh
index 3e517fa4..61072078 100644
--- a/include/rego/rego.hh
+++ b/include/rego/rego.hh
@@ -740,6 +740,7 @@ namespace rego
 
   using BuiltIns = std::shared_ptr<BuiltInsDef>;
 
+  const std::string UnknownError = "unknown_error";
   const std::string EvalTypeError = "eval_type_error";
   const std::string EvalBuiltInError = "eval_builtin_error";
   const std::string RegoTypeError = "rego_type_error";
@@ -748,6 +749,7 @@ namespace rego
   const std::string EvalConflictError = "eval_conflict_error";
   const std::string WellFormedError = "wellformed_error";
   const std::string RuntimeError = "runtime_error";
+  const std::string RecursionError = "rego_recursion_error";
   const std::string DefaultVersion = "v0";
 
   /**
@@ -760,7 +762,7 @@ namespace rego
   Node err(
     NodeRange& r,
     const std::string& msg,
-    const std::string& code = WellFormedError);
+    const std::string& code = UnknownError);
 
   /**
    * Generates an error node.
@@ -770,9 +772,7 @@ namespace rego
    * @param code The error code.
    */
   Node err(
-    Node node,
-    const std::string& msg,
-    const std::string& code = WellFormedError);
+    Node node, const std::string& msg, const std::string& code = UnknownError);
 
   /**
    * Returns a node representing the version of the library.
@@ -845,8 +845,10 @@ namespace rego
   public:
     /**
      * Constructor.
+     *
+     * @param v1_compatible whether the Interpreter should run in rego-v1 mode.
      */
-    Interpreter();
+    Interpreter(bool v1_compatible = false);
 
     /**
      * Adds a module (i.e. virtual document) file to the interpreter.
@@ -1017,7 +1019,7 @@ namespace rego
   /**
    * Parses Rego queries and virtual documents.
    */
-  Reader reader();
+  Reader reader(bool v1_compatible = false);
 
   /**
    * Rewrites a Query AST to an input term.
diff --git a/src/builtins/strings.cc b/src/builtins/strings.cc
index 1068c9d9..6ce795cd 100644
--- a/src/builtins/strings.cc
+++ b/src/builtins/strings.cc
@@ -932,6 +932,41 @@ namespace
 
     return value;
   }
+
+  Node count(const Nodes& args)
+  {
+    Node search =
+      unwrap_arg(args, UnwrapOpt(0).type(JSONString).func("strings.count"));
+    if (search->type() == Error)
+    {
+      return search;
+    }
+
+    Node substring =
+      unwrap_arg(args, UnwrapOpt(1).type(JSONString).func("strings.count"));
+    if (substring->type() == Error)
+    {
+      return substring;
+    }
+
+    std::string search_str = get_string(search);
+    std::string substring_str = get_string(substring);
+
+    size_t pos = 0;
+    size_t count = 0;
+    while (pos < search_str.size())
+    {
+      pos = search_str.find(substring_str, pos);
+      if (pos == std::string::npos)
+      {
+        break;
+      }
+      ++count;
+      pos += substring_str.size();
+    }
+
+    return Int ^ std::to_string(count);
+  }
 }
 
 namespace rego
@@ -957,6 +992,7 @@ namespace rego
           Location("strings.any_prefix_match"), 2, any_prefix_match),
         BuiltInDef::create(
           Location("strings.any_suffix_match"), 2, any_suffix_match),
+        BuiltInDef::create(Location("strings.count"), 2, count),
         BuiltInDef::create(Location("strings.replace_n"), 2, replace_n),
         BuiltInDef::create(Location("strings.reverse"), 1, reverse),
         BuiltInDef::create(Location("substring"), 3, substring),
diff --git a/src/internal.cc b/src/internal.cc
index f2190268..8c1b0375 100644
--- a/src/internal.cc
+++ b/src/internal.cc
@@ -1,5 +1,7 @@
 #include "internal.hh"
 
+#include "rego.hh"
+
 #include <trieste/json.h>
 
 namespace logging = trieste::logging;
@@ -88,7 +90,7 @@ namespace rego
       return false;
     }
 
-    return is_in(node->parent()->shared_from_this(), types);
+    return is_in(node->parent(), types);
   }
 
   bool is_constant(const Node& term)
@@ -209,15 +211,31 @@ namespace rego
     return quoted;
   }
 
+  std::string get_code(const std::string& msg, const std::string& code)
+  {
+    if (code == UnknownError)
+    {
+      if (starts_with(msg, "Recursion"))
+      {
+        return RecursionError;
+      }
+
+      return WellFormedError;
+    }
+
+    return code;
+  }
+
   Node err(NodeRange& r, const std::string& msg, const std::string& code)
   {
-    return Error << (ErrorMsg ^ msg) << (ErrorAst << r) << (ErrorCode ^ code);
+    return Error << (ErrorMsg ^ msg) << (ErrorAst << r)
+                 << (ErrorCode ^ get_code(msg, code));
   }
 
   Node err(Node node, const std::string& msg, const std::string& code)
   {
     return Error << (ErrorMsg ^ msg) << (ErrorAst << node->clone())
-                 << (ErrorCode ^ code);
+                 << (ErrorCode ^ get_code(msg, code));
   }
 
   bool all_alnum(const std::string_view& str)
@@ -412,6 +430,12 @@ namespace rego
   bool is_falsy(const Node& node)
   {
     Node value = node;
+
+    if (value->type() == Error)
+    {
+      return false;
+    }
+
     if (value->type() == Term)
     {
       value = value->front();
diff --git a/src/interpreter.cc b/src/interpreter.cc
index a1f0bb9d..7e02b3c7 100644
--- a/src/interpreter.cc
+++ b/src/interpreter.cc
@@ -16,8 +16,8 @@ namespace rego
   using duration = std::chrono::high_resolution_clock::duration;
   using timestamp = std::chrono::high_resolution_clock::time_point;
 
-  Interpreter::Interpreter() :
-    m_reader(reader()),
+  Interpreter::Interpreter(bool v1_compatible) :
+    m_reader(reader(v1_compatible)),
     m_debug_path("."),
     m_builtins(BuiltInsDef::create()),
     m_unify(unify(m_builtins)),
diff --git a/src/parse.cc b/src/parse.cc
index 02a24a33..2b5744f1 100644
--- a/src/parse.cc
+++ b/src/parse.cc
@@ -211,7 +211,7 @@ namespace rego
 
         ";" >> [](auto& m) { m.add(NewLine); },
 
-        R"(\r?\n)" >>
+        R"([ \t]*\r?\n)" >>
           [newline_mode](auto& m) {
             switch (*newline_mode)
             {
diff --git a/src/reader.cc b/src/reader.cc
index 2dc57137..f606dbcb 100644
--- a/src/reader.cc
+++ b/src/reader.cc
@@ -37,7 +37,8 @@ namespace
     Unify,
     IsIn,
     Colon,
-    As};
+    As,
+    Contains};
 
   Nodes comma_separation(Node group)
   {
@@ -216,10 +217,11 @@ namespace
     ;
   // clang-format on
 
-  PassDef keywords()
+  PassDef keywords(bool v1_compatible)
   {
     std::shared_ptr<std::map<std::string, Token>> keywords =
       std::make_shared<std::map<std::string, Token>>();
+
     PassDef pass = {
       "keywords",
       wf_keywords,
@@ -320,6 +322,29 @@ namespace
           },
       }};
 
+    pass.pre(Module, [v1_compatible, keywords](Node module) {
+      if (v1_compatible)
+      {
+        Nodes nodes;
+        keywords->insert({"if", If});
+        nodes.push_back(Keyword << (Var ^ "if"));
+        keywords->insert({"in", IsIn});
+        nodes.push_back(Keyword << (Var ^ "in"));
+        keywords->insert({"contains", Contains});
+        nodes.push_back(Keyword << (Var ^ "contains"));
+        keywords->insert({"every", Every});
+        nodes.push_back(Keyword << (Var ^ "every"));
+        keywords->insert({"version", Version});
+        nodes.push_back(Version ^ "v1");
+        auto it = module->find_first(Package, module->begin());
+        // we want to insert immediately after the package node
+        ++it;
+        module->insert(it, nodes.begin(), nodes.end());
+      }
+
+      return 0;
+    });
+
     pass.post([keywords](Node) {
       keywords->clear();
       return 0;
@@ -665,17 +690,17 @@ namespace
       }};
 
     pass.pre(Comma, [comma_groups](Node node) {
-      comma_groups->insert(node->parent());
+      comma_groups->insert(node->parent_unsafe());
       return 0;
     });
 
     pass.pre(Colon, [colon_groups](Node node) {
-      colon_groups->insert(node->parent());
+      colon_groups->insert(node->parent_unsafe());
       return 0;
     });
 
     pass.pre(Or, [or_groups](Node node) {
-      auto group = node->parent();
+      auto group = node->parent_unsafe();
       auto pos = group->find(node);
       for (auto it = group->begin(); it != pos; ++it)
       {
@@ -1063,7 +1088,7 @@ namespace
             return Else << _(Expr) << (Query << (Group << query));
           },
 
-        In(Group) * (T(Else) * T(Query)[Query]) >>
+        In(Group) * (T(Else) * ~T(If) * T(Query)[Query]) >>
           [](Match& _) {
             return Else << (Expr << (Term << (Scalar << True))) << _(Query);
           },
@@ -1071,6 +1096,19 @@ namespace
         In(Group) * (T(Else) * T(Assign, Unify) * T(Expr)[Expr]) >>
           [](Match& _) { return Else << _(Expr) << (Query << Group); },
 
+        In(Group) *
+            (T(If)[If] * ~T(Not)[Not] * T(Expr)[Query] * T(With)[With] *
+             T(Expr)[Lhs] * T(As)[As] * T(Expr)[Rhs]) >>
+          [](Match& _) {
+            Node query = Group << _(Query) << _(With) << _(Lhs) << _(As)
+                               << _(Rhs);
+            if (_(Not) != nullptr)
+            {
+              query = Group << (NotExpr << (Expr << query));
+            }
+            return Seq << _(If) << (Query << query);
+          },
+
         In(Group) * (T(If)[If] * ~T(Not)[Not] * T(Expr, SomeDecl)[Query]) >>
           [](Match& _) {
             Node query = _(Query);
@@ -1090,7 +1128,7 @@ namespace
                   << (T(Ref)
                       << ((T(RefHead) << T(Var, "input|data")[Var]) *
                           (T(RefArgSeq) << End))))) *
-             (T(InfixOperator) << T(AssignOperator)))(
+             (T(InfixOperator) << (T(AssignOperator) << T(Assign))))(
               [strict](auto&) { return *strict; }) >>
           [](Match& _) {
             std::ostringstream err_buf;
@@ -1620,8 +1658,10 @@ namespace
                        << (Policy << _[Policy]);
           },
 
-        In(Module) * (T(Package)[Package] * T(Import)++[ImportSeq] * End) >>
+        In(Module) *
+            (T(Package)[Package] * T(Import, Version)++[ImportSeq] * End) >>
           [](Match& _) {
+            // empty module
             Node version;
             Node importseq = NodeDef::create(ImportSeq);
             for (auto node : _[ImportSeq])
@@ -1735,16 +1775,21 @@ namespace
 
 namespace rego
 {
-  Reader reader()
+  Reader reader(bool v1_compatible)
   {
     return {
       "rego",
       {
-        prep(),           keywords(),        some_every(),  ref_args(),
-        refs(),           groups(),          terms(),       unary(),
-        arithbin_first(), arithbin_second(), comparison(),  membership(),
-        assign(),         else_not(),        collections(), lines(),
-        rules(),          literals(),        structure(),
+        prep(),           keywords(v1_compatible),
+        some_every(),     ref_args(),
+        refs(),           groups(),
+        terms(),          unary(),
+        arithbin_first(), arithbin_second(),
+        comparison(),     membership(),
+        assign(),         else_not(),
+        collections(),    lines(),
+        rules(),          literals(),
+        structure(),
       },
       parser(),
     };
diff --git a/src/rego_c.cc b/src/rego_c.cc
index 6bb32d26..31987267 100644
--- a/src/rego_c.cc
+++ b/src/rego_c.cc
@@ -552,7 +552,7 @@ extern "C"
     logging::Debug() << "regoNodeJSONSize";
     auto node_ptr = reinterpret_cast<trieste::NodeDef*>(node);
     trieste::WFContext context(rego::wf_result);
-    std::string json = rego::to_key(node_ptr->shared_from_this(), true);
+    std::string json = rego::to_key(node_ptr->intrusive_ptr_from_this(), true);
     return static_cast<regoSize>(json.size() + 1);
   }
 
@@ -562,7 +562,7 @@ extern "C"
 
     auto node_ptr = reinterpret_cast<trieste::NodeDef*>(node);
     trieste::WFContext context(rego::wf_result);
-    std::string json = rego::to_key(node_ptr->shared_from_this(), true);
+    std::string json = rego::to_key(node_ptr->intrusive_ptr_from_this(), true);
     if (size < json.size() + 1)
     {
       return REGO_ERROR_BUFFER_TOO_SMALL;
diff --git a/src/unifier.cc b/src/unifier.cc
index 75247461..295b27db 100644
--- a/src/unifier.cc
+++ b/src/unifier.cc
@@ -1,3 +1,4 @@
+#include "internal.hh"
 #include "unify.hh"
 
 namespace rego
@@ -239,15 +240,26 @@ namespace rego
           if (m_negate && (var.is_unify() || var.is_user_var()))
           {
             bool all_false = true;
+            Values errors;
             for (auto& value : values)
             {
-              if (!is_falsy(value->node()))
+              if (value->node()->type() == Error)
+              {
+                errors.push_back(value);
+              }
+              else if (!is_falsy(value->node()))
               {
                 all_false = false;
                 break;
               }
             }
-            if (all_false)
+
+            if (!errors.empty())
+            {
+              var.unify(errors);
+              logging::Debug() << "> result: " << var;
+            }
+            else if (all_false)
             {
               for (auto& value : values)
               {
@@ -1757,21 +1769,29 @@ namespace rego
           rank = index;
           result = value;
         }
-        else if (index < rank)
-        {
-          rank = index;
-          result = value;
-        }
-        else if (index == rank)
+        else if (!is_undefined(value))
         {
-          std::string result_str = to_key(result);
-          std::string value_str = to_key(value);
-          if (result_str != value_str)
+          if (index < rank)
           {
-            return err(
-              rulecomp,
-              "complete rules must not produce multiple outputs",
-              EvalConflictError);
+            rank = index;
+            result = value;
+          }
+          else if (is_undefined(result))
+          {
+            rank = index;
+            result = value;
+          }
+          else if (index == rank)
+          {
+            std::string result_str = to_key(result);
+            std::string value_str = to_key(value);
+            if (result_str != value_str)
+            {
+              return err(
+                rulecomp,
+                "complete rules must not produce multiple outputs",
+                EvalConflictError);
+            }
           }
         }
       }
diff --git a/src/unify.cc b/src/unify.cc
index 6f12a6a8..615eb074 100644
--- a/src/unify.cc
+++ b/src/unify.cc
@@ -67,7 +67,7 @@ namespace rego
       return name.find("query$") != std::string::npos;
     }
 
-    return in_query(node->parent()->shared_from_this());
+    return in_query(node->parent());
   }
 
   bool contains_local(const Node& node)
diff --git a/src/unify/absolute_refs.cc b/src/unify/absolute_refs.cc
index f656f347..18a4e218 100644
--- a/src/unify/absolute_refs.cc
+++ b/src/unify/absolute_refs.cc
@@ -21,7 +21,7 @@ namespace
       return Ref << (RefHead << (Var ^ "data")) << refargseq;
     }
 
-    Node ref = build_ref(leaf->parent()->shared_from_this());
+    Node ref = build_ref(leaf->parent());
 
     if (leaf->type() == Policy || leaf->type() == DataModule)
     {
diff --git a/src/unify/enumerate.cc b/src/unify/enumerate.cc
index 476e7f5c..f8b151b8 100644
--- a/src/unify/enumerate.cc
+++ b/src/unify/enumerate.cc
@@ -31,7 +31,7 @@ namespace
     find_all_refs_in(local->scope(), (local / Var)->location(), refs);
     for (auto& ref : refs)
     {
-      if (ref->parent() == local.get())
+      if (ref->parent_unsafe() == local.get())
       {
         continue;
       }
@@ -61,7 +61,7 @@ namespace
 
   Node next_enum(Node local)
   {
-    Node unifybody = local->parent()->shared_from_this();
+    Node unifybody = local->parent();
     auto it = unifybody->find(local) + 1;
     return find_enum(unifybody, it);
   }
@@ -302,21 +302,21 @@ namespace rego
       if (is_in(local, {LiteralEnum}))
       {
         // should this local be defined here?
-        Node unifybody = local->parent()->shared_from_this();
+        Node unifybody = local->parent();
         bool requires_move = false;
         while (!should_be_defined_in(local, unifybody))
         {
           // we need to keep popping out of nested enums until we find the
           // correct scope
           requires_move = true;
-          NodeDef* literalenum = unifybody->parent();
+          Node literalenum = unifybody->parent();
           if (literalenum->type() != LiteralEnum)
           {
             // we've popped out of the nested enums
             break;
           }
 
-          unifybody = literalenum->parent()->shared_from_this();
+          unifybody = literalenum->parent();
         }
 
         if (requires_move)
diff --git a/src/unify/lift_refheads.cc b/src/unify/lift_refheads.cc
index 640dd005..4980f1bf 100644
--- a/src/unify/lift_refheads.cc
+++ b/src/unify/lift_refheads.cc
@@ -215,7 +215,7 @@ namespace
         return;
       }
 
-      Node module = node->parent()->parent()->shared_from_this();
+      Node module = node->parent()->parent();
       Node prefix_ref = concat_refs(Var ^ "data", (module / Package)->front());
       if (prefix_ref->type() == Error)
       {
@@ -316,7 +316,7 @@ namespace rego
         }) >>
           [refheads](Match& _) {
             ACTION();
-            Node module = _(Rule)->parent()->parent()->shared_from_this();
+            Node module = _(Rule)->parent()->parent();
             Node imports = (module / ImportSeq)->clone();
             Node package_ref = (module / Package)->front();
             Node version = (module / Version)->clone();
@@ -374,7 +374,7 @@ namespace rego
     });
 
     lift_refheads.post(Rule, [refheads](Node node) {
-      Node module = node->parent()->parent()->shared_from_this();
+      Node module = node->parent()->parent();
       Node rulehead = node / RuleHead;
       Node package_ref = (module / Package)->front();
       Node prefix_ref = concat_refs(Var ^ "data", package_ref);
diff --git a/src/unify/lift_to_rule.cc b/src/unify/lift_to_rule.cc
index 8565ac8b..d8a0627d 100644
--- a/src/unify/lift_to_rule.cc
+++ b/src/unify/lift_to_rule.cc
@@ -151,10 +151,10 @@ namespace
         return (*it)->clone();
       }
 
-      return err(node->shared_from_this(), "Missing version");
+      return err(node->intrusive_ptr_from_this(), "Missing version");
     }
 
-    return get_version(node->parent());
+    return get_version(node->parent_unsafe());
   }
 }
 
@@ -175,7 +175,7 @@ namespace rego
                  T(UnifyBody)[UnifyBody])) >>
           [](Match& _) {
             ACTION();
-            Node version = get_version(_(UnifyBody)->parent());
+            Node version = get_version(_(UnifyBody)->parent_unsafe());
             Node rulebody = _(UnifyBody);
             // in vars
             Locs invars;
@@ -278,7 +278,7 @@ namespace rego
                  (T(NestedBody) << (T(Key)[Key] * T(UnifyBody)[UnifyBody])))) >>
           [](Match& _) {
             ACTION();
-            Node version = get_version(_(Var)->parent());
+            Node version = get_version(_(Var)->parent_unsafe());
             Node rulebody = _(UnifyBody);
             Locs invars;
             find_invars(_(UnifyBody), invars);
@@ -360,7 +360,7 @@ namespace rego
             ((T(ExprEvery) * In(DataModule)++) << T(UnifyBody)[UnifyBody]) >>
           [](Match& _) {
             ACTION();
-            Node version = get_version(_(UnifyBody)->parent());
+            Node version = get_version(_(UnifyBody)->parent_unsafe());
             Node rulebody = _(UnifyBody);
             Locs invars;
             find_invars(_(UnifyBody), invars);
diff --git a/src/unify/rulebody.cc b/src/unify/rulebody.cc
index 81ea5801..88ea21ab 100644
--- a/src/unify/rulebody.cc
+++ b/src/unify/rulebody.cc
@@ -136,7 +136,7 @@ namespace rego
              << (T(Var)[Lhs] * T(Var)[Rhs] * T(UnifyBody)[UnifyBody])) >>
           [](Match& _) {
             ACTION();
-            Location value = _.fresh({"value"});
+            Location value = _.fresh({in_query(_(Lhs)) ? "qvalue" : "value"});
             return Seq << (Lift << UnifyBody
                                 << (Local << (Var ^ value) << Undefined))
                        << (UnifyExprEnum << (Var ^ value) << _(Lhs) << _(Rhs)
diff --git a/src/unify/symbols.cc b/src/unify/symbols.cc
index fb22b53e..421c8ccf 100644
--- a/src/unify/symbols.cc
+++ b/src/unify/symbols.cc
@@ -39,7 +39,7 @@ namespace
     auto it = module->find_first(Version, module->begin());
     if (it == module->end())
     {
-      return err(module->shared_from_this(), "No version found in module");
+      return err(module, "No version found in module");
     }
     return (*it)->clone();
   }
diff --git a/tools/fuzzer.cc b/tools/fuzzer.cc
index 279b7214..d15891b5 100644
--- a/tools/fuzzer.cc
+++ b/tools/fuzzer.cc
@@ -32,8 +32,8 @@ int main(int argc, char** argv)
       "-l,--log_level",
       log_level,
       "Set Log Level to one of "
-      "Trace, Debug, Info, "
-      "Warning, Output, Error, "
+      "Trace, Debug (includes log of unification),"
+      "Info, Warning, Output, Error, "
       "None")
     ->check(logging::set_log_level_from_string);
 
diff --git a/tools/main.cc b/tools/main.cc
index 84579385..5db09f31 100644
--- a/tools/main.cc
+++ b/tools/main.cc
@@ -25,7 +25,7 @@ int main(int argc, char** argv)
   app.add_flag("-w,--wf", wf_checks, "Enable well-formedness checks");
 
   std::filesystem::path output;
-  app.add_option("-a,--ast", output, "Output the AST");
+  app.add_option("-a,--ast", output, "Folder to use for AST output");
 
   std::string log_level;
   app
@@ -33,11 +33,15 @@ int main(int argc, char** argv)
       "-l,--log_level",
       log_level,
       "Set Log Level to one of "
-      "Trace, Debug, Info, "
-      "Warning, Output, Error, "
+      "Trace, Debug (includes log of unification),"
+      "Info, Warning, Output, Error, "
       "None")
     ->check(rego::set_log_level_from_string);
 
+  bool v1_compatible{false};
+  app.add_flag(
+    "-1,--v1-compatible", v1_compatible, "Use Rego v1 compatibility mode");
+
   try
   {
     app.parse(argc, argv);
@@ -47,7 +51,7 @@ int main(int argc, char** argv)
     return app.exit(e);
   }
 
-  auto interpreter = rego::Interpreter();
+  auto interpreter = rego::Interpreter(v1_compatible);
   interpreter.wf_check_enabled(wf_checks);
   if (!output.empty())
   {


Test patch:
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 3436bc33..38ee2e81 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -38,8 +38,6 @@ add_custom_command(TARGET rego_test POST_BUILD
 if(REGOCPP_OPA_TESTS)
   set( OPA_TEST_DIRS
   aggregates
-  all
-  any
   arithmetic
   array
   assignments
@@ -113,9 +111,7 @@ if(REGOCPP_OPA_TESTS)
   sprintf
   strings
   subset
-  toarray
   topdowndynamicdispatch
-  toset
   trim
   trimleft
   trimprefix
@@ -134,10 +130,26 @@ if(REGOCPP_OPA_TESTS)
   withkeyword
   )
 
+  set( OPA_TEST_DIRS_V0
+    any
+    all
+    toarray
+    toset
+  )
+
   set( OPA_TEST_ROOT ${CMAKE_CURRENT_BINARY_DIR}/../opa/test/cases/testdata )
   foreach( TEST_DIR ${OPA_TEST_DIRS})
-    add_test(NAME rego_test_${TEST_DIR}
-             COMMAND rego_test -wf ${OPA_TEST_ROOT}/${TEST_DIR}/
+    add_test(NAME rego_test_${TEST_DIR}_v0
+             COMMAND rego_test -wf ${OPA_TEST_ROOT}/v0/${TEST_DIR}/
+             WORKING_DIRECTORY $<TARGET_FILE_DIR:rego_test>)
+    add_test(NAME rego_test_${TEST_DIR}_v1
+             COMMAND rego_test -1 -wf ${OPA_TEST_ROOT}/v1/${TEST_DIR}/
              WORKING_DIRECTORY $<TARGET_FILE_DIR:rego_test>)
   endforeach()
+
+  foreach( TEST_DIR ${OPA_TEST_DIRS_V0})
+    add_test(NAME rego_test_${TEST_DIR}_v0
+             COMMAND rego_test -wf ${OPA_TEST_ROOT}/v0/${TEST_DIR}/
+             WORKING_DIRECTORY $<TARGET_FILE_DIR:rego_test>)
+  endforeach()  
 endif()
diff --git a/tests/bugs.yaml b/tests/bugs.yaml
index 9747fa35..1e8e214b 100644
--- a/tests/bugs.yaml
+++ b/tests/bugs.yaml
@@ -265,3 +265,66 @@ cases:
   - x:
       p: 1
       q: 2
+- note: regocpp/bug154
+  modules:
+  - |
+    package test
+
+    disallow := allow == false
+    allow { not disallow }
+  query: "data.test = x"
+  want_error: "Recursion detected in rule body: data.test.allow"
+  want_error_code: rego_recursion_error
+- note: regocpp/bug156
+  modules:
+  - |
+    package test
+
+    import future.keywords
+
+    allow := input.x if { not input.x }
+    else := 5
+  query: "data.test = x"
+  want_result:
+    - x:
+        allow: 5
+- note: regocpp/bug158
+  modules:
+  - |
+    package test
+
+    x := 5
+    x := 4 + 1
+
+    y := 5
+    y := input.x
+  query: "data.test = x"
+  want_result:
+  - x:
+      x: 5
+      y: 5
+- note: regocpp/bug159
+  modules:
+  - |
+    package test
+
+    default x := 10
+    x := data.x
+
+    default y := 10
+    y := input.y
+  query: "data.test = x"
+  want_result:
+  - x:
+      x: 10
+      y: 10
+- note: regocpp/bug160
+  modules:
+  - |
+    package test 
+
+    p := 1
+  query: "data.test = x"
+  want_result:
+  - x:
+      p: 1
diff --git a/tests/main.cc b/tests/main.cc
index ca77b1cc..594b0eb2 100644
--- a/tests/main.cc
+++ b/tests/main.cc
@@ -63,8 +63,7 @@ int main(int argc, char** argv)
     "case,-c,--case", case_paths, "Test case YAML files or directories");
 
   std::filesystem::path debug_path;
-  app.add_option(
-    "-a,--ast", debug_path, "Output the AST (debugging for test case parser)");
+  app.add_option("-a,--ast", debug_path, "Folder to use for AST output");
 
   std::string log_level;
   app
@@ -72,8 +71,8 @@ int main(int argc, char** argv)
       "-l,--log_level",
       log_level,
       "Set Log Level to one of "
-      "Trace, Debug, Info, "
-      "Warning, Output, Error, "
+      "Trace, Debug (includes log of unification),"
+      "Info, Warning, Output, Error, "
       "None")
     ->check(rego::set_log_level_from_string);
 
@@ -90,6 +89,12 @@ int main(int argc, char** argv)
     note_match,
     "Note (or note substring) of specific test to run");
 
+  bool v1_compatible{false};
+  app.add_flag(
+    "-1,--v1-compatible",
+    v1_compatible,
+    "Run tests in Rego v1 compatibility mode (default: false)");
+
   try
   {
     app.parse(argc, argv);
@@ -146,7 +151,7 @@ int main(int argc, char** argv)
       try
       {
         auto start = std::chrono::steady_clock::now();
-        auto result = testcase.run(debug_path, wf_checks);
+        auto result = testcase.run(debug_path, wf_checks, v1_compatible);
         auto end = std::chrono::steady_clock::now();
         const std::chrono::duration<double> elapsed = end - start;
 
diff --git a/tests/test_case.cc b/tests/test_case.cc
index cdca005c..b63a8b1b 100644
--- a/tests/test_case.cc
+++ b/tests/test_case.cc
@@ -586,9 +586,11 @@ namespace rego_test
   }
 
   Result TestCase::run(
-    const std::filesystem::path& debug_path, bool wf_checks) const
+    const std::filesystem::path& debug_path,
+    bool wf_checks,
+    bool v1_compatible) const
   {
-    rego::Interpreter interpreter;
+    rego::Interpreter interpreter(v1_compatible);
     interpreter.builtins().strict_errors(m_strict_error);
     interpreter.wf_check_enabled(wf_checks)
       .debug_enabled(!debug_path.empty())
diff --git a/tests/test_case.h b/tests/test_case.h
index 37df19d0..f1e209a4 100644
--- a/tests/test_case.h
+++ b/tests/test_case.h
@@ -25,7 +25,10 @@ namespace rego_test
 
     TestCase();
 
-    Result run(const std::filesystem::path& debug_path, bool wf_checks) const;
+    Result run(
+      const std::filesystem::path& debug_path,
+      bool wf_checks,
+      bool v1_compatible) const;
 
     /** name of the test case category. */
     const std::string& category() const;