Repository: hyperledger/aries-cloudagent-python

Issue:
Make sure —preserve-exchange-records applies to issuers, holders, and verifiers
From this [comment on issue 535](https://github.com/hyperledger/aries-cloudagent-python/issues/535#issuecomment-1840089139):

> Hi regarding the --preserve-exchange-records, it retains for the holder side but not on the issuer's side. Is there a way to retain the exchange records for the issuer's side as well?

This task is to look at what is being done for all parts of the exchanges (issuers, holders and verifiers) and to make sure that the settings are applied correctly for each.


Discussion:
see #2352. I did some work to extend that flag to include presentation exchanges and `auto_remove` to override. maybe there are still gaps?
It sounds like. Per the comment — it appears that the `—preserve-exchange-records` is not saving all of the objects.
quikc update, haven't checked out all of it.
using issue credential v1 API (haven't checked v2)... issuer and holder can retain/preserve the exchanges. there is a bug on the holder side to override `--preserve-exchange-records` behaviour (by setting `auto_remove=True` on send-request), so exchanges currently are ALWAYS preserved. easy fix though.

more investigation on presentation exchanges (v1 and v2) tomorrow.

Resolving patch:
diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py
--- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py
+++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py
@@ -3,7 +3,6 @@
 import asyncio
 import json
 import logging
-
 from typing import Mapping, Optional, Tuple
 
 from ....cache.base import BaseCache
@@ -18,8 +17,8 @@
     IndyLedgerRequestsExecutor,
 )
 from ....messaging.credential_definitions.util import (
-    CRED_DEF_TAGS,
     CRED_DEF_SENT_RECORD_TYPE,
+    CRED_DEF_TAGS,
 )
 from ....messaging.responder import BaseResponder
 from ....multitenant.base import BaseMultitenantManager
@@ -28,7 +27,6 @@
 from ....revocation.models.revocation_registry import RevocationRegistry
 from ....storage.base import BaseStorage
 from ....storage.error import StorageError, StorageNotFoundError
-
 from ...out_of_band.v1_0.models.oob_record import OobRecord
 from .messages.credential_ack import CredentialAck
 from .messages.credential_issue import CredentialIssue
@@ -410,6 +408,9 @@ async def create_request(
         cred_req_ser = None
         cred_req_meta = None
 
+        # hold on to values that may have changed so we can restore after fetch
+        auto_remove = cred_ex_record.auto_remove
+
         async def _create():
             multitenant_mgr = self.profile.inject_or(BaseMultitenantManager)
             if multitenant_mgr:
@@ -478,6 +479,8 @@ async def _create():
                 cred_ex_record.credential_request = cred_req_ser
                 cred_ex_record.credential_request_metadata = cred_req_meta
                 cred_ex_record.state = V10CredentialExchange.STATE_REQUEST_SENT
+                # restore values passed in...
+                cred_ex_record.auto_remove = auto_remove
                 await cred_ex_record.save(txn, reason="create credential request")
                 await txn.commit()
         else:
diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py
--- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py
+++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py
@@ -10,7 +10,6 @@
     request_schema,
     response_schema,
 )
-
 from marshmallow import fields, validate
 
 from ....admin.request_context import AdminRequestContext
@@ -484,14 +483,16 @@ async def credential_exchange_create(request: web.BaseRequest):
     r_time = get_timer()
 
     context: AdminRequestContext = request["context"]
-
+    profile = context.profile
     body = await request.json()
 
     comment = body.get("comment")
     preview_spec = body.get("credential_proposal")
     if not preview_spec:
         raise web.HTTPBadRequest(reason="credential_proposal must be provided")
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     trace_msg = body.get("trace")
 
     try:
@@ -570,7 +571,9 @@ async def credential_exchange_send(request: web.BaseRequest):
     preview_spec = body.get("credential_proposal")
     if not preview_spec:
         raise web.HTTPBadRequest(reason="credential_proposal must be provided")
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     trace_msg = body.get("trace")
 
     connection_record = None
@@ -663,7 +666,9 @@ async def credential_exchange_send_proposal(request: web.BaseRequest):
     connection_id = body.get("connection_id")
     comment = body.get("comment")
     preview_spec = body.get("credential_proposal")
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     trace_msg = body.get("trace")
 
     connection_record = None
@@ -788,7 +793,9 @@ async def credential_exchange_create_free_offer(request: web.BaseRequest):
     auto_issue = body.get(
         "auto_issue", context.settings.get("debug.auto_respond_credential_request")
     )
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     comment = body.get("comment")
     preview_spec = body.get("credential_preview")
     if not preview_spec:
@@ -862,7 +869,9 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest):
     auto_issue = body.get(
         "auto_issue", context.settings.get("debug.auto_respond_credential_request")
     )
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     comment = body.get("comment")
     preview_spec = body.get("credential_preview")
     if not preview_spec:
diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py
--- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py
+++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py
@@ -12,14 +12,13 @@
     request_schema,
     response_schema,
 )
-
 from marshmallow import ValidationError, fields, validate, validates_schema
 
 from ....admin.request_context import AdminRequestContext
-from ....connections.models.conn_record import ConnRecord
-from ....core.profile import Profile
 from ....anoncreds.holder import AnonCredsHolderError
 from ....anoncreds.issuer import AnonCredsIssuerError
+from ....connections.models.conn_record import ConnRecord
+from ....core.profile import Profile
 from ....indy.holder import IndyHolderError
 from ....indy.issuer import IndyIssuerError
 from ....ledger.error import LedgerError
@@ -607,13 +606,15 @@ async def credential_exchange_create(request: web.BaseRequest):
     r_time = get_timer()
 
     context: AdminRequestContext = request["context"]
-
+    profile = context.profile
     body = await request.json()
 
     comment = body.get("comment")
     preview_spec = body.get("credential_preview")
     filt_spec = body.get("filter")
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     if not filt_spec:
         raise web.HTTPBadRequest(reason="Missing filter")
     trace_msg = body.get("trace")
@@ -694,7 +695,9 @@ async def credential_exchange_send(request: web.BaseRequest):
     if not filt_spec:
         raise web.HTTPBadRequest(reason="Missing filter")
     preview_spec = body.get("credential_preview")
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     replacement_id = body.get("replacement_id")
     trace_msg = body.get("trace")
 
@@ -802,7 +805,9 @@ async def credential_exchange_send_proposal(request: web.BaseRequest):
     filt_spec = body.get("filter")
     if not filt_spec:
         raise web.HTTPBadRequest(reason="Missing filter")
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     trace_msg = body.get("trace")
 
     conn_record = None
@@ -925,7 +930,9 @@ async def credential_exchange_create_free_offer(request: web.BaseRequest):
     auto_issue = body.get(
         "auto_issue", context.settings.get("debug.auto_respond_credential_request")
     )
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     replacement_id = body.get("replacement_id")
     comment = body.get("comment")
     preview_spec = body.get("credential_preview")
@@ -1000,7 +1007,9 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest):
     auto_issue = body.get(
         "auto_issue", context.settings.get("debug.auto_respond_credential_request")
     )
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     replacement_id = body.get("replacement_id")
     comment = body.get("comment")
     preview_spec = body.get("credential_preview")
@@ -1196,7 +1205,9 @@ async def credential_exchange_send_free_request(request: web.BaseRequest):
     filt_spec = body.get("filter")
     if not filt_spec:
         raise web.HTTPBadRequest(reason="Missing filter")
-    auto_remove = body.get("auto_remove")
+    auto_remove = body.get(
+        "auto_remove", not profile.settings.get("preserve_exchange_records")
+    )
     trace_msg = body.get("trace")
     holder_did = body.get("holder_did")
 


Test patch: