Repository: OpenAssetIO/OpenAssetIO (language: python)

Issue:
Auth related `BatchElementError` codes
## What

Add an auth error code so that manager's have a means to inform the user that they do not have access to an asset.  We are thinking a generic auth error code, rather than a set of unauthorised/unauthenticated/permission denied error codes.

## Why

This is a precursor to having something more fleshed out, but just putting the error codes in gives managers and hosts an option in the meantime, and it's super simple.

## Acceptance Criteria

Add a new error code `kAuthError` 


Discussion / hints:
Think about this, could be a source level data break for non exhaustive switches.
Looking at the existing BatchElementError error codes, they follow a pattern of either `<reason><object>` (e.g. `<Invalid><PreflightHint>`, `<Malformed><EntityReference>`)  or `<action>Error` (e.g. `<EntityResolution>Error`, `<EntityAccess>Error`).

Following this pattern, I think `kAuthError` is more consistent than `kAuth`. I'll update the ACs.

Fix patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index fa772713b..7c83a50c3 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -47,6 +47,10 @@ section for more details._
   `openassetio.dll`, if required.
   [#1340](https://github.com/OpenAssetIO/OpenAssetIO/issues/1340)
 
+- Added `kAuthError` to the set of possible `BatchElementError` error
+  codes.
+  [#1009](https://github.com/OpenAssetIO/OpenAssetIO/issues/1009)
+
 ### Improvements
 
 - Added singular overload of `managementPolicy` for convenience.
diff --git a/resources/abi/libopenassetio.so.1.0.0.xml b/resources/abi/libopenassetio.so.1.0.0.xml
index a28cc0e9a..f721bc94c 100644
--- a/resources/abi/libopenassetio.so.1.0.0.xml
+++ b/resources/abi/libopenassetio.so.1.0.0.xml
@@ -63788,13 +63788,14 @@
                 <enumerator name='kEntityResolutionError' value='132'/>
                 <enumerator name='kInvalidPreflightHint' value='133'/>
                 <enumerator name='kInvalidTraitSet' value='134'/>
+                <enumerator name='kAuthError' value='135'/>
               </enum-decl>
             </member-type>
             <data-member access='public' layout-offset-in-bits='0'>
-              <var-decl name='code' type-id='type-id-1926' visibility='default' filepath='/src/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp' line='149' column='1'/>
+              <var-decl name='code' type-id='type-id-1926' visibility='default' filepath='/src/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp' line='163' column='1'/>
             </data-member>
             <data-member access='public' layout-offset-in-bits='64'>
-              <var-decl name='message' type-id='type-id-1915' visibility='default' filepath='/src/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp' line='151' column='1'/>
+              <var-decl name='message' type-id='type-id-1915' visibility='default' filepath='/src/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp' line='165' column='1'/>
             </data-member>
           </class-decl>
           <class-decl name='BatchElementException' size-in-bits='320' is-struct='yes' visibility='default' filepath='/src/src/openassetio-core/include/openassetio/errors/exceptions.hpp' line='96' column='1' id='type-id-5869'>
@@ -71023,17 +71024,6 @@
               <var-decl name='__write_digits' type-id='type-id-13098' visibility='default' filepath='/usr/local/include/fmt/format.h' line='1927' column='1'/>
             </data-member>
           </class-decl>
-          <class-decl name='&lt;lambda(fmt::v9::detail::reserve_iterator&lt;fmt::v9::appender&gt;)&gt;' size-in-bits='128' is-struct='yes' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2065' column='1' id='type-id-13099'>
-            <data-member access='public' layout-offset-in-bits='0'>
-              <var-decl name='__abs_value' type-id='type-id-3' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2099' column='1'/>
-            </data-member>
-            <data-member access='public' layout-offset-in-bits='64'>
-              <var-decl name='__num_digits' type-id='type-id-13' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2099' column='1'/>
-            </data-member>
-            <data-member access='public' layout-offset-in-bits='96'>
-              <var-decl name='__upper' type-id='type-id-474' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2077' column='1'/>
-            </data-member>
-          </class-decl>
           <class-decl name='&lt;lambda(fmt::v9::detail::reserve_iterator&lt;fmt::v9::appender&gt;)&gt;' size-in-bits='256' is-struct='yes' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2065' column='1' id='type-id-13097'>
             <data-member access='public' layout-offset-in-bits='0'>
               <var-decl name='__abs_value' type-id='type-id-265' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2099' column='1'/>
@@ -71053,6 +71043,17 @@
               <var-decl name='__num_digits' type-id='type-id-13' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2099' column='1'/>
             </data-member>
           </class-decl>
+          <class-decl name='&lt;lambda(fmt::v9::detail::reserve_iterator&lt;fmt::v9::appender&gt;)&gt;' size-in-bits='128' is-struct='yes' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2065' column='1' id='type-id-13099'>
+            <data-member access='public' layout-offset-in-bits='0'>
+              <var-decl name='__abs_value' type-id='type-id-3' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2099' column='1'/>
+            </data-member>
+            <data-member access='public' layout-offset-in-bits='64'>
+              <var-decl name='__num_digits' type-id='type-id-13' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2099' column='1'/>
+            </data-member>
+            <data-member access='public' layout-offset-in-bits='96'>
+              <var-decl name='__upper' type-id='type-id-474' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2077' column='1'/>
+            </data-member>
+          </class-decl>
           <class-decl name='&lt;lambda(fmt::v9::detail::reserve_iterator&lt;fmt::v9::appender&gt;)&gt;' size-in-bits='96' is-struct='yes' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2076' column='1' id='type-id-13100'>
             <data-member access='public' layout-offset-in-bits='0'>
               <var-decl name='__abs_value' type-id='type-id-113' visibility='default' filepath='/usr/local/include/fmt/format.h' line='2077' column='1'/>
diff --git a/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp b/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp
index 0500d6f24..8e7b218a7 100644
--- a/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp
+++ b/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp
@@ -126,6 +126,20 @@ class BatchElementError final {
      * the manager.
      */
     kInvalidTraitSet = OPENASSETIO_BatchErrorCode_kInvalidTraitSet,
+
+    /**
+     * Error code indicating that the host is not authorized for a
+     * pariticular request.
+     *
+     * The operation may be valid if attempted again, once the
+     * authorization issue is resolved.
+     *
+     * This can be due to insufficient or incomplete authentication as
+     * well as authorization, and could be specific to a particular
+     * action, or a particular entity. The error message should provide
+     * details of the reason.
+     */
+    kAuthError = OPENASSETIO_BatchErrorCode_kAuthError,
   };
 
   /**
diff --git a/src/openassetio-core/include/openassetio/errors/errorCodes.h b/src/openassetio-core/include/openassetio/errors/errorCodes.h
index 745e29a6b..394f0237f 100644
--- a/src/openassetio-core/include/openassetio/errors/errorCodes.h
+++ b/src/openassetio-core/include/openassetio/errors/errorCodes.h
@@ -47,3 +47,6 @@
 
 /// Failure due to a TraitSet being unknown to the manager
 #define OPENASSETIO_BatchErrorCode_kInvalidTraitSet (OPENASSETIO_BatchErrorCode_BEGIN + 6)
+
+/// Failure due to a authentication or authorization.
+#define OPENASSETIO_BatchErrorCode_kAuthError (OPENASSETIO_BatchErrorCode_BEGIN + 7)
diff --git a/src/openassetio-core/src/errors/exceptionMessages.cpp b/src/openassetio-core/src/errors/exceptionMessages.cpp
index 736735170..f12be6239 100644
--- a/src/openassetio-core/src/errors/exceptionMessages.cpp
+++ b/src/openassetio-core/src/errors/exceptionMessages.cpp
@@ -27,6 +27,8 @@ Str errorCodeName(BatchElementError::ErrorCode code) {
       return "invalidPreflightHint";
     case BatchElementError::ErrorCode::kInvalidTraitSet:
       return "invalidTraitSet";
+    case BatchElementError::ErrorCode::kAuthError:
+      return "authError";
   }
 
   assert(false);  // Impossible case.
diff --git a/src/openassetio-python/cmodule/src/errors/BatchElementErrorBinding.cpp b/src/openassetio-python/cmodule/src/errors/BatchElementErrorBinding.cpp
index 971c55296..33ad500a5 100644
--- a/src/openassetio-python/cmodule/src/errors/BatchElementErrorBinding.cpp
+++ b/src/openassetio-python/cmodule/src/errors/BatchElementErrorBinding.cpp
@@ -25,7 +25,8 @@ void registerBatchElementError(const py::module& mod) {
       .value("kEntityAccessError", BatchElementError::ErrorCode::kEntityAccessError)
       .value("kEntityResolutionError", BatchElementError::ErrorCode::kEntityResolutionError)
       .value("kInvalidPreflightHint", BatchElementError::ErrorCode::kInvalidPreflightHint)
-      .value("kInvalidTraitSet", BatchElementError::ErrorCode::kInvalidTraitSet);
+      .value("kInvalidTraitSet", BatchElementError::ErrorCode::kInvalidTraitSet)
+      .value("kAuthError", BatchElementError::ErrorCode::kAuthError);
 
   batchElementError
       .def(py::init<BatchElementError::ErrorCode, openassetio::Str>(), py::arg("code"),


Test patch:
diff --git a/src/openassetio-python/tests/package/hostApi/test_manager.py b/src/openassetio-python/tests/package/hostApi/test_manager.py
index 3e1eb9ffa..8c1c09962 100644
--- a/src/openassetio-python/tests/package/hostApi/test_manager.py
+++ b/src/openassetio-python/tests/package/hostApi/test_manager.py
@@ -88,6 +88,7 @@ class BatchFirstMethodTest:
         "entityResolutionError",
         "invalidPreflightHint",
         "invalidTraitSet",
+        "authError",
     ]
 
     def assert_callback_overload_wraps_the_corresponding_method_of_the_held_interface(
diff --git a/src/openassetio-python/tests/package/test_batchelementerror.py b/src/openassetio-python/tests/package/test_batchelementerror.py
index 3149b6e69..c1a715c73 100644
--- a/src/openassetio-python/tests/package/test_batchelementerror.py
+++ b/src/openassetio-python/tests/package/test_batchelementerror.py
@@ -36,6 +36,7 @@ def test_code_values(self):
         assert int(BatchElementError.ErrorCode.kEntityResolutionError) == 132
         assert int(BatchElementError.ErrorCode.kInvalidPreflightHint) == 133
         assert int(BatchElementError.ErrorCode.kInvalidTraitSet) == 134
+        assert int(BatchElementError.ErrorCode.kAuthError) == 135
 
 
 class Test_BatchElementError_inheritance:
@@ -119,6 +120,7 @@ class Test_BatchElementException:
             BatchElementError.ErrorCode.kEntityResolutionError,
             BatchElementError.ErrorCode.kInvalidPreflightHint,
             BatchElementError.ErrorCode.kInvalidTraitSet,
+            BatchElementError.ErrorCode.kAuthError,
         ],
     )
     def test_when_thrown_then_correct_data_set(self, exception_code):