This is an automated email from the ASF dual-hosted git repository.

martinzink pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 746c4cfeb MINIFICPP-2845 - Monotonic provenance event identifiers, 
iterations support, refactor (#2209)
746c4cfeb is described below

commit 746c4cfebe01fb9d714fb7f58bf92d2771b78d31
Author: Adam Debreceni <[email protected]>
AuthorDate: Mon Nov 9 17:12:39 2026 +0100

    MINIFICPP-2845 - Monotonic provenance event identifiers, iterations 
support, refactor (#2209)
---
 core-framework/include/core/Repository.h           |   8 +-
 core-framework/src/core/Repository.cpp             |  16 --
 extensions/rocksdb-repos/ProvenanceRepository.cpp  | 109 ---------
 .../rocksdb-repos/RocksDbProvenanceRepository.cpp  | 244 ++++++++++++++++++++
 ...eRepository.h => RocksDbProvenanceRepository.h} |  38 ++--
 .../rocksdb-repos/database/RocksDbInstance.cpp     |   6 +
 .../tests/DBProvenanceRepositoryTests.cpp          | 253 ++++++++++++++++++++-
 extensions/rocksdb-repos/tests/ProvenanceTests.cpp | 136 ++++++++---
 extensions/rocksdb-repos/tests/RepoTests.cpp       |  12 +-
 .../tests/integration/VerifyInvokeHTTP.h           |   2 +-
 .../tests/unit/ProcessorTests.cpp                  |   9 +-
 libminifi/include/CronDrivenSchedulingAgent.h      |   4 +-
 libminifi/include/EventDrivenSchedulingAgent.h     |   4 +-
 libminifi/include/FlowController.h                 |   6 +-
 libminifi/include/SchedulingAgent.h                |   8 +-
 libminifi/include/ThreadedSchedulingAgent.h        |   4 +-
 libminifi/include/TimerDrivenSchedulingAgent.h     |   4 +-
 libminifi/include/core/ProcessContextImpl.h        |   8 +-
 .../reporting/SiteToSiteProvenanceReportingTask.h  |   2 +-
 .../include/core/repository/AtomicRepoEntries.h    |  18 ++
 .../core/repository/NoOpThreadedRepository.h       |  18 +-
 .../core/repository/VolatileProvenanceRepository.h |  34 ++-
 .../include/core/repository/VolatileRepository.h   |   4 +-
 libminifi/include/provenance/Provenance.h          |  32 +--
 libminifi/src/FlowController.cpp                   |   2 +-
 libminifi/src/ThreadedSchedulingAgent.cpp          |   4 +-
 libminifi/src/core/ProcessContextImpl.cpp          |  12 +-
 libminifi/src/core/ProcessSession.cpp              |   2 +-
 .../src/core/flow/StructuredConfiguration.cpp      |   4 +
 .../SiteToSiteProvenanceReportingTask.cpp          |  95 ++++++--
 .../repository/VolatileProvenanceRepository.cpp    | 112 +++++++++
 .../src/core/repository/VolatileRepository.cpp     |   2 +-
 libminifi/src/provenance/Provenance.cpp            |  75 ++----
 libminifi/src/sitetosite/SiteToSiteClient.cpp      |   2 +-
 libminifi/test/flow-tests/SessionTests.cpp         |   2 +-
 libminifi/test/integration/C2PauseResumeTest.cpp   |   2 +-
 .../ControllerServiceIntegrationTests.cpp          |   2 +-
 .../test/libtest/integration/IntegrationBase.cpp   |   2 +-
 libminifi/test/libtest/unit/ProvenanceTestHelper.h |  33 ++-
 libminifi/test/libtest/unit/TestBase.cpp           |   7 +-
 libminifi/test/libtest/unit/TestBase.h             |   9 +-
 .../test/libtest/unit/TestControllerWithFlow.cpp   |   2 +-
 .../test/persistence-tests/PersistenceTests.cpp    |   9 +-
 libminifi/test/unit/LogMetricsPublisherTests.cpp   |  11 +-
 libminifi/test/unit/SchedulingAgentTests.cpp       |   2 +-
 .../unit/VolatileProvenanceRepositoryTests.cpp     | 169 ++++++++++++++
 .../include/minifi-cpp/core/ProcessContext.h       |   3 +-
 minifi-api/include/minifi-cpp/core/Repository.h    |   8 +-
 .../include/minifi-cpp/provenance/Provenance.h     |   8 +-
 .../minifi-cpp/provenance/ProvenanceRepository.h   |  44 ++++
 minifi_main/MiNiFiMain.cpp                         |   2 +-
 51 files changed, 1256 insertions(+), 348 deletions(-)

diff --git a/core-framework/include/core/Repository.h 
b/core-framework/include/core/Repository.h
index 7a817ae2f..37e289489 100644
--- a/core-framework/include/core/Repository.h
+++ b/core-framework/include/core/Repository.h
@@ -80,7 +80,7 @@ class RepositoryImpl : public core::CoreComponentImpl, public 
core::RepositoryMe
     return true;
   }
 
-  bool MultiPut(const std::vector<std::pair<std::string, 
std::unique_ptr<io::BufferStream>>>& /*data*/) override {
+  bool MultiPut(const EntryStreams& /*data*/) override {
     return true;
   }
 
@@ -106,12 +106,6 @@ class RepositoryImpl : public core::CoreComponentImpl, 
public core::RepositoryMe
     return false;
   }
 
-  std::vector<std::shared_ptr<core::SerializableComponent>> getElements(size_t 
/*max_size*/) override {
-    return {};
-  }
-
-  bool storeElement(const std::shared_ptr<core::SerializableComponent>& 
element) override;
-
   void loadComponent(const std::shared_ptr<core::ContentRepository>& 
/*content_repo*/) override {
   }
 
diff --git a/core-framework/src/core/Repository.cpp 
b/core-framework/src/core/Repository.cpp
index 722e82e7a..7004e05c0 100644
--- a/core-framework/src/core/Repository.cpp
+++ b/core-framework/src/core/Repository.cpp
@@ -27,20 +27,4 @@ bool 
RepositoryImpl::Delete(std::vector<std::shared_ptr<core::SerializableCompon
   return found;
 }
 
-bool RepositoryImpl::storeElement(const 
std::shared_ptr<core::SerializableComponent>& element) {
-  if (!element) {
-    return false;
-  }
-
-  org::apache::nifi::minifi::io::BufferStream stream;
-
-  element->serialize(stream);
-
-  if (!Put(element->getUUIDStr(), reinterpret_cast<const 
uint8_t*>(stream.getBuffer().data()), stream.size())) {
-    logger_->log_error("NiFi Provenance Store event {} size {} fail", 
element->getUUIDStr(), stream.size());
-    return false;
-  }
-  return true;
-}
-
 }  // namespace org::apache::nifi::minifi::core
diff --git a/extensions/rocksdb-repos/ProvenanceRepository.cpp 
b/extensions/rocksdb-repos/ProvenanceRepository.cpp
deleted file mode 100644
index 2eb186067..000000000
--- a/extensions/rocksdb-repos/ProvenanceRepository.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ProvenanceRepository.h"
-
-#include <string>
-
-#include "core/Resource.h"
-#include "minifi-cpp/Exception.h"
-
-namespace org::apache::nifi::minifi::provenance {
-
-bool ProvenanceRepository::initialize(const 
std::shared_ptr<org::apache::nifi::minifi::Configure> &config) {
-  std::string value;
-  if (config->get(Configure::nifi_provenance_repository_directory_default, 
value) && !value.empty()) {
-    directory_ = value;
-  }
-  logger_->log_debug("MiNiFi Provenance Repository Directory {}", directory_);
-  if (config->get(Configure::nifi_provenance_repository_max_storage_size, 
value)) {
-    max_partition_bytes_ = gsl::narrow<int64_t>(parsing::parseDataSize(value) 
| utils::orThrow("expected parsable data size"));
-  }
-  logger_->log_debug("MiNiFi Provenance Max Partition Bytes {}", 
max_partition_bytes_);
-  if (config->get(Configure::nifi_provenance_repository_max_storage_time, 
value)) {
-    if (auto max_partition = 
utils::timeutils::StringToDuration<std::chrono::milliseconds>(value))
-      max_partition_millis_ = *max_partition;
-  }
-  logger_->log_debug("MiNiFi Provenance Max Storage Time: [{}]", 
max_partition_millis_);
-
-  verify_checksums_in_rocksdb_reads_ = 
(config->get(Configure::nifi_provenance_repository_rocksdb_read_verify_checksums)
 | utils::andThen(&utils::string::toBool)).value_or(false);
-  logger_->log_debug("{} checksum verification in ProvenanceRepository", 
verify_checksums_in_rocksdb_reads_ ? "Using" : "Not using");
-
-  auto db_options = [] (minifi::internal::Writable<rocksdb::DBOptions>& 
db_opts) {
-    minifi::internal::setCommonRocksDbOptions(db_opts);
-  };
-
-  // Rocksdb write buffers act as a log of database operation: grow till 
reaching the limit, serialized after
-  // This shouldn't go above 16MB and the configured total size of the db 
should cap it as well
-  auto cf_options = [this] (rocksdb::ColumnFamilyOptions& cf_opts) {
-    int64_t max_buffer_size = 16 << 20;
-    cf_opts.write_buffer_size = gsl::narrow<size_t>(std::min(max_buffer_size, 
max_partition_bytes_));
-    cf_opts.max_write_buffer_number = 4;
-    cf_opts.min_write_buffer_number_to_merge = 1;
-
-    cf_opts.compaction_style = rocksdb::CompactionStyle::kCompactionStyleFIFO;
-    cf_opts.compaction_options_fifo = 
rocksdb::CompactionOptionsFIFO(max_partition_bytes_, false);
-    if (max_partition_millis_ > std::chrono::milliseconds(0)) {
-      cf_opts.ttl = 
std::chrono::duration_cast<std::chrono::seconds>(max_partition_millis_).count();
-    }
-  };
-
-  db_ = minifi::internal::RocksDatabase::create(db_options, cf_options, 
directory_,
-    minifi::internal::getRocksDbOptionsToOverride(config, 
Configure::nifi_provenance_repository_rocksdb_options));
-  if (db_->open()) {
-    logger_->log_debug("MiNiFi Provenance Repository database open {} 
success", directory_);
-  } else {
-    logger_->log_error("MiNiFi Provenance Repository database open {} failed", 
directory_);
-    return false;
-  }
-
-  return true;
-}
-
-std::vector<std::shared_ptr<core::SerializableComponent>> 
ProvenanceRepository::getElements(size_t max_size) {
-  if (max_size == 0) {
-    return {};
-  }
-  auto opendb = db_->open();
-  if (!opendb) {
-    throw minifi::Exception(ExceptionType::REPOSITORY_EXCEPTION, "Failed to 
open provenance repository");
-  }
-  std::vector<std::shared_ptr<core::SerializableComponent>> records;
-  rocksdb::ReadOptions options;
-  options.verify_checksums = verify_checksums_in_rocksdb_reads_;
-  std::unique_ptr<rocksdb::Iterator> it(opendb->NewIterator(options));
-  for (it->SeekToFirst(); it->Valid(); it->Next()) {
-    auto eventRead = ProvenanceEventRecord::create();
-    const auto slice = it->value();
-    io::BufferStream stream(std::as_bytes(std::span(slice.data(), 
slice.size())));
-    if (eventRead->deserialize(stream)) {
-      records.push_back(eventRead);
-      if (--max_size == 0) {
-        break;
-      }
-    }
-  }
-  return records;
-}
-
-void ProvenanceRepository::destroy() {
-  db_.reset();
-}
-
-REGISTER_RESOURCE_AS(ProvenanceRepository, InternalResource, 
("ProvenanceRepository", "provenancerepository"));
-
-}  // namespace org::apache::nifi::minifi::provenance
diff --git a/extensions/rocksdb-repos/RocksDbProvenanceRepository.cpp 
b/extensions/rocksdb-repos/RocksDbProvenanceRepository.cpp
new file mode 100644
index 000000000..12d46e1bb
--- /dev/null
+++ b/extensions/rocksdb-repos/RocksDbProvenanceRepository.cpp
@@ -0,0 +1,244 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RocksDbProvenanceRepository.h"
+
+#include <optional>
+#include <string>
+
+#include "core/Resource.h"
+#include "minifi-cpp/utils/Literals.h"
+
+namespace org::apache::nifi::minifi::provenance {
+
+namespace {
+// Events are keyed by their ordinal, and the keys are iterated in bytewise 
order, so the
+// encoding must be fixed width, otherwise key order would not match ordinal 
order
+// (e.g. "10" < "2") and an iteration could skip events. 20 digits fit any 
uint64_t.
+std::string eventOrdinalToKey(uint64_t event_ordinal) {
+  return fmt::format("{:020}", event_ordinal);
+}
+
+// also accepts keys written before the fixed width encoding was introduced
+std::optional<uint64_t> keyToEventOrdinal(std::string_view key) {
+  if (auto event_ordinal = parsing::parseIntegral<uint64_t>(key)) {
+    return *event_ordinal;
+  }
+  return std::nullopt;
+}
+
+class EventCursor : public ProvenanceRepository::Cursor {
+ public:
+  explicit EventCursor(uint64_t last_event_ordinal): 
last_event_ordinal_(last_event_ordinal) {}
+  EventCursor(const EventCursor&) = default;
+  EventCursor(EventCursor&&) = default;
+  EventCursor& operator=(const EventCursor&) = default;
+  EventCursor& operator=(EventCursor&&) = default;
+
+  // the ordinal of the last event observed, 0 if no event has been read yet
+  [[nodiscard]]
+  std::string toString() const override {
+    return std::to_string(last_event_ordinal_);
+  }
+  ~EventCursor() override = default;
+
+  uint64_t last_event_ordinal_;
+};
+}  // namespace
+
+static const std::string_view NEXT_EVENT_KEY = "next_event_key";
+
+bool RocksDbProvenanceRepository::initialize(const 
std::shared_ptr<org::apache::nifi::minifi::Configure> &config) {
+  if (!RocksDbRepository::initialize(config)) {
+    return false;
+  }
+  std::string value;
+  if (config->get(Configure::nifi_provenance_repository_directory_default, 
value) && !value.empty()) {
+    directory_ = value;
+  }
+  logger_->log_debug("MiNiFi Provenance Repository Directory {}", directory_);
+  if (config->get(Configure::nifi_provenance_repository_max_storage_size, 
value)) {
+    max_partition_bytes_ = gsl::narrow<int64_t>(parsing::parseDataSize(value) 
| utils::orThrow("expected parsable data size"));
+  }
+  logger_->log_debug("MiNiFi Provenance Max Partition Bytes {}", 
max_partition_bytes_);
+  if (config->get(Configure::nifi_provenance_repository_max_storage_time, 
value)) {
+    if (auto max_partition = 
utils::timeutils::StringToDuration<std::chrono::milliseconds>(value)) {
+      max_partition_millis_ = *max_partition;
+    }
+  }
+  logger_->log_debug("MiNiFi Provenance Max Storage Time: [{}]", 
max_partition_millis_);
+
+  verify_checksums_in_rocksdb_reads_ = 
(config->get(Configure::nifi_provenance_repository_rocksdb_read_verify_checksums)
 | utils::andThen(&utils::string::toBool)).value_or(false);
+  logger_->log_debug("{} checksum verification in 
RocksDbProvenanceRepository", verify_checksums_in_rocksdb_reads_ ? "Using" : 
"Not using");
+
+  auto db_options = [] (minifi::internal::Writable<rocksdb::DBOptions>& 
db_opts) {
+    minifi::internal::setCommonRocksDbOptions(db_opts);
+  };
+
+  // Rocksdb write buffers act as a log of database operation: grow till 
reaching the limit, serialized after
+  // This shouldn't go above 16MB and the configured total size of the db 
should cap it as well
+  auto cf_options = [this] (rocksdb::ColumnFamilyOptions& cf_opts) {
+    cf_opts.write_buffer_size = std::min<size_t>(16_MiB, max_partition_bytes_);
+    cf_opts.max_write_buffer_number = 4;
+    cf_opts.min_write_buffer_number_to_merge = 1;
+
+    cf_opts.compaction_style = rocksdb::CompactionStyle::kCompactionStyleFIFO;
+    cf_opts.compaction_options_fifo = 
rocksdb::CompactionOptionsFIFO(max_partition_bytes_, false);
+    if (max_partition_millis_ > std::chrono::milliseconds(0)) {
+      cf_opts.ttl = 
std::chrono::duration_cast<std::chrono::seconds>(max_partition_millis_).count();
+    }
+  };
+
+  db_ = minifi::internal::RocksDatabase::create(db_options, cf_options, 
directory_,
+    minifi::internal::getRocksDbOptionsToOverride(config, 
Configure::nifi_provenance_repository_rocksdb_options));
+  std::string internal_state_db_uri = [&] {
+    const std::string_view minifidb_scheme = "minifidb://";
+    if (directory_.starts_with(minifidb_scheme)) {
+      return directory_ + "-internal-state";
+    }
+    std::string uri = utils::string::join_pack(minifidb_scheme, directory_);
+    if (uri.ends_with("/") || uri.ends_with("\\")) {
+      uri.pop_back();
+    }
+    return uri + "/internal-state";
+  }();
+  internal_state_db_ = minifi::internal::RocksDatabase::create(db_options, {}, 
internal_state_db_uri, {});
+  if (auto open_state_db = internal_state_db_->open()) {
+    rocksdb::ReadOptions options;
+    options.verify_checksums = verify_checksums_in_rocksdb_reads_;
+    std::string next_event_key_str;
+    if (open_state_db->Get(options, NEXT_EVENT_KEY, &next_event_key_str).ok()) 
{
+      next_event_key_ = std::stoull(next_event_key_str);
+    } else {
+      logger_->log_debug("Could not find '{}', resetting event key", 
NEXT_EVENT_KEY);
+      next_event_key_ = 1;
+    }
+    logger_->log_trace("Using next event key: {}", next_event_key_);
+  } else {
+    logger_->log_error("Could not open internal state column in provenance 
repository {}", internal_state_db_uri);
+    return false;
+  }
+  if (db_->open()) {
+    logger_->log_debug("MiNiFi Provenance Repository database open {} 
success", directory_);
+  } else {
+    logger_->log_error("MiNiFi Provenance Repository database open {} failed", 
directory_);
+    return false;
+  }
+
+  return true;
+}
+
+void RocksDbProvenanceRepository::destroy() {
+  db_.reset();
+}
+
+std::unique_ptr<ProvenanceRepository::Cursor> 
RocksDbProvenanceRepository::cursorFromString(std::string_view cursor_str) {
+  if (cursor_str.empty()) {
+    return std::make_unique<EventCursor>(0);
+  }
+  if (auto last_event_ordinal = keyToEventOrdinal(cursor_str)) {
+    return std::make_unique<EventCursor>(*last_event_ordinal);
+  }
+  logger_->log_warn("Could not interpret provenance cursor '{}', reading from 
the first event", cursor_str);
+  return std::make_unique<EventCursor>(0);
+}
+
+std::expected<std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>>, 
std::string> RocksDbProvenanceRepository::getEvents(size_t max_size, Cursor* 
cursor) {
+  auto* event_cursor = dynamic_cast<EventCursor*>(cursor);
+  if (cursor && !event_cursor) {
+    return std::unexpected{"Invalid cursor"};
+  }
+  if (max_size == 0) {
+    return {};
+  }
+  auto opendb = db_->open();
+  if (!opendb) {
+    return std::unexpected{"Failed to open database"};
+  }
+  std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>> records;
+  rocksdb::ReadOptions options;
+  options.verify_checksums = verify_checksums_in_rocksdb_reads_;
+  std::unique_ptr<rocksdb::Iterator> it(opendb->NewIterator(options));
+  uint64_t last_event_ordinal = 0;
+  if (event_cursor) {
+    last_event_ordinal = event_cursor->last_event_ordinal_;
+    const auto last_event_key = eventOrdinalToKey(last_event_ordinal);
+    it->Seek(last_event_key);
+    if (it->Valid() && it->key() == last_event_key) {
+      it->Next();
+    }
+  } else {
+    it->SeekToFirst();
+  }
+  for (; it->Valid(); it->Next()) {
+    // the ordinal is taken from the key, not from the event, so that an event 
that fails to
+    // deserialize does not stall the cursor
+    if (auto event_ordinal = 
keyToEventOrdinal(std::string_view{it->key().data(), it->key().size()})) {
+      last_event_ordinal = *event_ordinal;
+    } else {
+      logger_->log_warn("Skipping provenance entry with unexpected key '{}'", 
it->key().ToString());
+      continue;
+    }
+    auto eventRead = ProvenanceEventRecord::create();
+    const auto slice = it->value();
+    io::BufferStream stream(std::as_bytes(std::span(slice.data(), 
slice.size())));
+    if (eventRead->deserialize(stream)) {
+      records.push_back(eventRead);
+      if (--max_size == 0) {
+        break;
+      }
+    }
+  }
+  if (event_cursor) {
+    event_cursor->last_event_ordinal_ = last_event_ordinal;
+  }
+  return records;
+}
+
+std::expected<void, std::string> 
RocksDbProvenanceRepository::appendEvents(const 
std::vector<std::shared_ptr<ProvenanceEventRecord>>& events) {
+  EntryStreams data;
+  data.reserve(events.size());
+  std::lock_guard guard(next_event_key_mtx_);
+  for (auto& event : events) {
+    event->setEventOrdinal(next_event_key_++);
+  }
+  {
+    auto open_state_db = internal_state_db_->open();
+    if (!open_state_db) {
+      return std::unexpected{"Failed to open internal state column in 
provenance database"};
+    }
+    auto operation = [this, &open_state_db]() { return 
open_state_db->Put(rocksdb::WriteOptions(), NEXT_EVENT_KEY, 
std::to_string(next_event_key_)); };
+    if (!ExecuteWithRetry(operation)) {
+      return std::unexpected{"Failed to update next provenance event key"};
+    }
+  }
+  for (auto& event : events) {
+    data.emplace_back(eventOrdinalToKey(event->getEventOrdinal()), 
std::make_unique<io::BufferStream>());
+    if (!event->serialize(*data.back().second)) {
+      return std::unexpected{fmt::format("Failed to serialize provenance event 
'{}'", event->getUUIDStr())};
+    }
+  }
+  if (MultiPut(data)) {
+    return {};
+  }
+
+  return std::unexpected{"Failed to append provenance events"};
+}
+
+REGISTER_RESOURCE_AS(RocksDbProvenanceRepository, InternalResource, 
("RocksDbProvenanceRepository", "rocksdbprovenancerepository", 
"ProvenanceRepository", "provenancerepository"));
+
+}  // namespace org::apache::nifi::minifi::provenance
diff --git a/extensions/rocksdb-repos/ProvenanceRepository.h 
b/extensions/rocksdb-repos/RocksDbProvenanceRepository.h
similarity index 61%
rename from extensions/rocksdb-repos/ProvenanceRepository.h
rename to extensions/rocksdb-repos/RocksDbProvenanceRepository.h
index 950ecd31c..34a5872b9 100644
--- a/extensions/rocksdb-repos/ProvenanceRepository.h
+++ b/extensions/rocksdb-repos/RocksDbProvenanceRepository.h
@@ -29,6 +29,7 @@
 #include "core/Core.h"
 #include "core/logging/LoggerFactory.h"
 #include "minifi-cpp/provenance/Provenance.h"
+#include "minifi-cpp/provenance/ProvenanceRepository.h"
 #include "minifi-cpp/utils/Literals.h"
 #include "RocksDbRepository.h"
 
@@ -39,22 +40,22 @@ constexpr auto MAX_PROVENANCE_STORAGE_SIZE = 10_MiB;
 constexpr auto MAX_PROVENANCE_ENTRY_LIFE_TIME = std::chrono::minutes(1);
 constexpr auto PROVENANCE_PURGE_PERIOD = std::chrono::milliseconds(2500);
 
-class ProvenanceRepository : public core::repository::RocksDbRepository {
+class RocksDbProvenanceRepository : public 
core::repository::RocksDbRepository, public ProvenanceRepository {
  public:
-  ProvenanceRepository(std::string_view name, const utils::Identifier& 
/*uuid*/)
-    : ProvenanceRepository(name) {
+  RocksDbProvenanceRepository(std::string_view name, const utils::Identifier& 
/*uuid*/)
+    : RocksDbProvenanceRepository(name) {
   }
 
-  explicit ProvenanceRepository(std::string_view repo_name = "",
-                                std::string directory = PROVENANCE_DIRECTORY,
-                                std::chrono::milliseconds maxPartitionMillis = 
MAX_PROVENANCE_ENTRY_LIFE_TIME,
-                                int64_t maxPartitionBytes = 
MAX_PROVENANCE_STORAGE_SIZE,
-                                std::chrono::milliseconds purgePeriod = 
PROVENANCE_PURGE_PERIOD)
-    : RocksDbRepository(repo_name.length() > 0 ? repo_name : 
core::className<ProvenanceRepository>(),
-        directory, maxPartitionMillis, maxPartitionBytes, purgePeriod, 
core::logging::LoggerFactory<ProvenanceRepository>::getLogger()) {
+  explicit RocksDbProvenanceRepository(std::string_view repo_name = "",
+      std::string directory = PROVENANCE_DIRECTORY,
+      std::chrono::milliseconds maxPartitionMillis = 
MAX_PROVENANCE_ENTRY_LIFE_TIME,
+      int64_t maxPartitionBytes = MAX_PROVENANCE_STORAGE_SIZE,
+      std::chrono::milliseconds purgePeriod = PROVENANCE_PURGE_PERIOD)
+    : RocksDbRepository(repo_name.length() > 0 ? repo_name : 
core::className<RocksDbProvenanceRepository>(),
+        directory, maxPartitionMillis, maxPartitionBytes, purgePeriod, 
core::logging::LoggerFactory<RocksDbProvenanceRepository>::getLogger()) {
   }
 
-  ~ProvenanceRepository() override {
+  ~RocksDbProvenanceRepository() override {
     stop();
   }
 
@@ -72,17 +73,26 @@ class ProvenanceRepository : public 
core::repository::RocksDbRepository {
     // The repo is cleaned up by itself, there is no need to delete items.
     return true;
   }
-  std::vector<std::shared_ptr<core::SerializableComponent>> getElements(size_t 
max_size) override;
 
   void destroy();
 
-  ProvenanceRepository(const ProvenanceRepository &parent) = delete;
+  RocksDbProvenanceRepository(const RocksDbProvenanceRepository &parent) = 
delete;
 
-  ProvenanceRepository &operator=(const ProvenanceRepository &parent) = delete;
+  RocksDbProvenanceRepository &operator=(const RocksDbProvenanceRepository 
&parent) = delete;
+
+  std::unique_ptr<Cursor> cursorFromString(std::string_view cursor_str) 
override;
+
+  
std::expected<std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>>, 
std::string> getEvents(size_t max_size, Cursor* cursor) override;
+
+  std::expected<void, std::string> appendEvents(const 
std::vector<std::shared_ptr<ProvenanceEventRecord>>& events) override;
 
  private:
   // Run function for the thread
   void run() override {};
+
+  std::unique_ptr<minifi::internal::RocksDatabase> internal_state_db_;
+  std::mutex next_event_key_mtx_;
+  uint64_t next_event_key_;
 };
 
 }  // namespace org::apache::nifi::minifi::provenance
diff --git a/extensions/rocksdb-repos/database/RocksDbInstance.cpp 
b/extensions/rocksdb-repos/database/RocksDbInstance.cpp
index 3c6de0c03..2aca1d8c4 100644
--- a/extensions/rocksdb-repos/database/RocksDbInstance.cpp
+++ b/extensions/rocksdb-repos/database/RocksDbInstance.cpp
@@ -180,6 +180,12 @@ std::optional<OpenRocksDb> RocksDbInstance::open(const 
std::string& column) {
       logger_->log_error("Couldn't query database '{}' for options: '{}'", 
db_name_, latest_option_status.ToString());
       return std::nullopt;
     }
+    if (mode_ == RocksDbMode::ReadWrite) {
+      // The column families are taken from the persisted options, but we 
create them on demand
+      // (see getOrCreateColumnFamily), so one that the options mention while 
the database itself
+      // does not have it yet must not fail the open of the whole database.
+      db_options_.create_missing_column_families = true;
+    }
     std::vector<rocksdb::ColumnFamilyHandle*> column_handles;
     switch (mode_) {
       case RocksDbMode::ReadWrite:
diff --git a/extensions/rocksdb-repos/tests/DBProvenanceRepositoryTests.cpp 
b/extensions/rocksdb-repos/tests/DBProvenanceRepositoryTests.cpp
index 8f6677c35..8b93991ad 100644
--- a/extensions/rocksdb-repos/tests/DBProvenanceRepositoryTests.cpp
+++ b/extensions/rocksdb-repos/tests/DBProvenanceRepositoryTests.cpp
@@ -18,10 +18,13 @@
 
 #include <array>
 #include <chrono>
+#include <filesystem>
+#include <memory>
 #include <random>
+#include <string>
 #include <vector>
 
-#include "ProvenanceRepository.h"
+#include "RocksDbProvenanceRepository.h"
 #include "unit/TestBase.h"
 #include "unit/Catch.h"
 
@@ -61,13 +64,33 @@ void verifyMaxKeyCount(const 
minifi::provenance::ProvenanceRepository& repo, uin
   REQUIRE(k < keyCount);
 }
 
+std::vector<std::byte> 
serializeEvent(minifi::provenance::ProvenanceEventRecord& event) {
+  minifi::io::BufferStream stream;
+  event.serialize(stream);
+  return stream.moveBuffer();
+}
+
+template<typename T>
+void appendAll(std::vector<T>& sink, const std::vector<T>& source) {
+  sink.insert(sink.end(), source.begin(), source.end());
+}
+
+std::vector<std::shared_ptr<minifi::provenance::ProvenanceEventRecord>> 
createEvents(size_t count) {
+  std::vector<std::shared_ptr<minifi::provenance::ProvenanceEventRecord>> 
events;
+  events.reserve(count);
+  for (size_t i = 0; i < count; ++i) {
+    events.push_back(minifi::provenance::ProvenanceEventRecord::create());
+  }
+  return events;
+}
+
 TEST_CASE("Test size limit", "[sizeLimitTest]") {
   TestController testController;
   auto temp_dir = testController.createTempDirectory();
   REQUIRE(!temp_dir.empty());
 
   // 60 sec, 100 KB - going to exceed the size limit
-  minifi::provenance::ProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1min, TEST_PROVENANCE_STORAGE_SIZE, 1s);
+  minifi::provenance::RocksDbProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1min, TEST_PROVENANCE_STORAGE_SIZE, 1s);
 
   auto configuration = 
std::make_shared<org::apache::nifi::minifi::ConfigureImpl>();
   
configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default,
 temp_dir.string());
@@ -87,7 +110,7 @@ TEST_CASE("Test time limit", "[timeLimitTest]") {
   REQUIRE(!temp_dir.empty());
 
   // 1 sec, 100 MB - going to exceed TTL
-  minifi::provenance::ProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
+  minifi::provenance::RocksDbProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
 
   auto configuration = 
std::make_shared<org::apache::nifi::minifi::ConfigureImpl>();
   
configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default,
 temp_dir.string());
@@ -114,3 +137,227 @@ TEST_CASE("Test time limit", "[timeLimitTest]") {
 
   verifyMaxKeyCount(provdb, 400);
 }
+
+TEST_CASE("Test query elements after cursor", "[iterationTest]") {
+  TestController testController;
+  auto temp_dir = testController.createTempDirectory();
+  REQUIRE(!temp_dir.empty());
+
+  minifi::provenance::RocksDbProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
+
+  auto configuration = 
std::make_shared<org::apache::nifi::minifi::ConfigureImpl>();
+  
configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default,
 temp_dir.string());
+
+  REQUIRE(provdb.initialize(configuration));
+
+  auto events = createEvents(8);
+
+  REQUIRE(provdb.appendEvents(events));
+
+  auto cursor = provdb.cursorFromString("");
+  REQUIRE(cursor);
+
+  std::vector<std::shared_ptr<minifi::provenance::ProvenanceEventRecord>> 
queried_events;
+
+  appendAll(queried_events, provdb.getEvents(3, cursor.get()).value());
+  REQUIRE(queried_events.size() == 3);
+  appendAll(queried_events, provdb.getEvents(3, cursor.get()).value());
+  REQUIRE(queried_events.size() == 6);
+  appendAll(queried_events, provdb.getEvents(3, cursor.get()).value());
+  REQUIRE(queried_events.size() == 8);
+
+  for (size_t i = 0; i < queried_events.size(); ++i) {
+    REQUIRE(queried_events.at(i)->getEventOrdinal() == i + 1);
+    REQUIRE(serializeEvent(*events.at(i)) == 
serializeEvent(*queried_events.at(i)));
+  }
+}
+
+TEST_CASE("Test loading cursor from string", "[cursorSerializationTest]") {
+  TestController testController;
+  auto temp_dir = testController.createTempDirectory();
+  REQUIRE(!temp_dir.empty());
+
+  minifi::provenance::RocksDbProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
+
+  auto configuration = 
std::make_shared<org::apache::nifi::minifi::ConfigureImpl>();
+  
configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default,
 temp_dir.string());
+
+  REQUIRE(provdb.initialize(configuration));
+
+  auto events = createEvents(8);
+
+  REQUIRE(provdb.appendEvents(events));
+
+  auto cursor = provdb.cursorFromString("");
+  REQUIRE(cursor);
+
+  std::vector<std::shared_ptr<minifi::provenance::ProvenanceEventRecord>> 
queried_events;
+
+  appendAll(queried_events, provdb.getEvents(3, cursor.get()).value());
+  REQUIRE(queried_events.size() == 3);
+
+  // the cursor is persisted as the ordinal of the last event read
+  REQUIRE(cursor->toString() == "3");
+
+  cursor = provdb.cursorFromString(cursor->toString());
+  REQUIRE(cursor);
+
+  appendAll(queried_events, provdb.getEvents(3, cursor.get()).value());
+  REQUIRE(queried_events.size() == 6);
+
+  for (size_t i = 0; i < queried_events.size(); ++i) {
+    REQUIRE(queried_events.at(i)->getEventOrdinal() == i + 1);
+    REQUIRE(serializeEvent(*events.at(i)) == 
serializeEvent(*queried_events.at(i)));
+  }
+}
+
+TEST_CASE("Test appendEvents assigns consecutive ordinals", 
"[eventOrdinalTest]") {
+  TestController testController;
+  auto temp_dir = testController.createTempDirectory();
+  REQUIRE(!temp_dir.empty());
+
+  minifi::provenance::RocksDbProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
+
+  auto configuration = 
std::make_shared<org::apache::nifi::minifi::ConfigureImpl>();
+  
configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default,
 temp_dir.string());
+
+  REQUIRE(provdb.initialize(configuration));
+
+  auto first_batch = createEvents(5);
+  REQUIRE(provdb.appendEvents(first_batch));
+  for (size_t i = 0; i < first_batch.size(); ++i) {
+    REQUIRE(first_batch.at(i)->getEventOrdinal() == i + 1);
+  }
+
+  auto second_batch = createEvents(3);
+  REQUIRE(provdb.appendEvents(second_batch));
+  for (size_t i = 0; i < second_batch.size(); ++i) {
+    REQUIRE(second_batch.at(i)->getEventOrdinal() == first_batch.size() + i + 
1);
+  }
+}
+
+TEST_CASE("Test querying events whose ordinals have different number of 
digits", "[eventOrdinalTest]") {
+  TestController testController;
+  auto temp_dir = testController.createTempDirectory();
+  REQUIRE(!temp_dir.empty());
+
+  minifi::provenance::RocksDbProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
+
+  auto configuration = 
std::make_shared<org::apache::nifi::minifi::ConfigureImpl>();
+  
configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default,
 temp_dir.string());
+
+  REQUIRE(provdb.initialize(configuration));
+
+  auto events = createEvents(12);
+  REQUIRE(provdb.appendEvents(events));
+
+  auto queried_events = provdb.getEvents(12, nullptr).value();
+  REQUIRE(queried_events.size() == 12);
+
+  for (size_t i = 0; i < queried_events.size(); ++i) {
+    REQUIRE(queried_events.at(i)->getEventOrdinal() == i + 1);
+    REQUIRE(serializeEvent(*events.at(i)) == 
serializeEvent(*queried_events.at(i)));
+  }
+}
+
+TEST_CASE("Test cursor observes events appended with more digits in their 
ordinal", "[eventOrdinalTest]") {
+  TestController testController;
+  auto temp_dir = testController.createTempDirectory();
+  REQUIRE(!temp_dir.empty());
+
+  minifi::provenance::RocksDbProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
+
+  auto configuration = 
std::make_shared<org::apache::nifi::minifi::ConfigureImpl>();
+  
configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default,
 temp_dir.string());
+
+  REQUIRE(provdb.initialize(configuration));
+
+  REQUIRE(provdb.appendEvents(createEvents(9)));
+
+  auto cursor = provdb.cursorFromString("");
+  REQUIRE(cursor);
+  REQUIRE(provdb.getEvents(9, cursor.get()).value().size() == 9);
+  REQUIRE(cursor->toString() == "9");
+
+  REQUIRE(provdb.appendEvents(createEvents(3)));
+
+  auto queried_events = provdb.getEvents(9, cursor.get()).value();
+  REQUIRE(queried_events.size() == 3);
+  for (size_t i = 0; i < queried_events.size(); ++i) {
+    REQUIRE(queried_events.at(i)->getEventOrdinal() == i + 10);
+  }
+}
+
+TEST_CASE("Test opening a database whose options mention the internal state 
column but does not have it", "[eventOrdinalTest]") {
+  TestController testController;
+  auto temp_dir = testController.createTempDirectory();
+  REQUIRE(!temp_dir.empty());
+
+  auto configuration = 
std::make_shared<org::apache::nifi::minifi::ConfigureImpl>();
+  
configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default,
 temp_dir.string());
+
+  {
+    minifi::provenance::RocksDbProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
+    REQUIRE(provdb.initialize(configuration));
+    REQUIRE(provdb.appendEvents(createEvents(1)));
+  }
+
+  // Leave the directory in the state an interrupted deletion produces: the 
persisted options still
+  // list the internal state column, while the database itself is gone. The 
column is created on
+  // demand, so this must not stop the repository from opening.
+  bool options_file_kept = false;
+  for (const auto& entry : std::filesystem::directory_iterator{temp_dir}) {
+    const auto filename = entry.path().filename().string();
+    if (filename.starts_with("CURRENT") || filename.starts_with("MANIFEST") || 
filename.ends_with(".log")) {
+      std::filesystem::remove(entry.path());
+    } else if (filename.starts_with("OPTIONS")) {
+      options_file_kept = true;
+    }
+  }
+  // the scenario is only reproduced as long as the persisted options are the 
ones left behind
+  REQUIRE(options_file_kept);
+
+  minifi::provenance::RocksDbProvenanceRepository provdb("TestProvRepo", 
temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
+  REQUIRE(provdb.initialize(configuration));
+  REQUIRE(provdb.getRocksDbStats());
+  REQUIRE(provdb.appendEvents(createEvents(1)));
+}
+
+TEST_CASE("Test opening existing database loads monotonic counter", 
"[eventUuidMonotonicTest]") {
+  TestController testController;
+  auto temp_dir = testController.createTempDirectory();
+  REQUIRE(!temp_dir.empty());
+
+  auto provdb = 
std::make_unique<minifi::provenance::RocksDbProvenanceRepository>("TestProvRepo",
 temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
+
+  auto configuration = 
std::make_shared<org::apache::nifi::minifi::ConfigureImpl>();
+  
configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default,
 temp_dir.string());
+
+  REQUIRE(provdb->initialize(configuration));
+
+  auto events = createEvents(4);
+
+  REQUIRE(provdb->appendEvents(events));
+
+  provdb = 
std::make_unique<minifi::provenance::RocksDbProvenanceRepository>("TestProvRepo",
 temp_dir.string(), 1s, TEST_MAX_PROVENANCE_STORAGE_SIZE, 1s);
+
+  REQUIRE(provdb->initialize(configuration));
+
+  auto new_events = createEvents(4);
+  appendAll(events, new_events);
+
+  REQUIRE(provdb->appendEvents(new_events));
+
+  // the counter continues where the previous instance left off
+  for (size_t i = 0; i < new_events.size(); ++i) {
+    REQUIRE(new_events.at(i)->getEventOrdinal() == i + 5);
+  }
+
+  std::vector<std::shared_ptr<minifi::provenance::ProvenanceEventRecord>> 
queried_events = provdb->getEvents(8, nullptr).value();
+  REQUIRE(queried_events.size() == 8);
+
+  for (size_t i = 0; i < queried_events.size(); ++i) {
+    REQUIRE(queried_events.at(i)->getEventOrdinal() == i + 1);
+    REQUIRE(serializeEvent(*events.at(i)) == 
serializeEvent(*queried_events.at(i)));
+  }
+}
diff --git a/extensions/rocksdb-repos/tests/ProvenanceTests.cpp 
b/extensions/rocksdb-repos/tests/ProvenanceTests.cpp
index 404791448..8b314bc96 100644
--- a/extensions/rocksdb-repos/tests/ProvenanceTests.cpp
+++ b/extensions/rocksdb-repos/tests/ProvenanceTests.cpp
@@ -16,12 +16,17 @@
  * limitations under the License.
  */
 
+#include <cstddef>
+#include <limits>
 #include <map>
 #include <memory>
+#include <span>
 #include <utility>
 #include <string>
+#include <vector>
 
 #include "core/Core.h"
+#include "io/BufferStream.h"
 #include "core/repository/AtomicRepoEntries.h"
 #include "core/repository/VolatileProvenanceRepository.h"
 #include "core/RepositoryFactory.h"
@@ -40,35 +45,94 @@ TEST_CASE("Test Provenance record create", 
"[Testprovenance::ProvenanceEventReco
   REQUIRE(record1->getAlternateIdentifierUri().empty());
 }
 
+TEST_CASE("Test Provenance event ordinal serialization round trip", 
"[Testprovenance::ProvenanceEventRecordSerializeDeser]") {
+  auto record1 = 
std::make_shared<provenance::ProvenanceEventRecordImpl>(provenance::ProvenanceEventRecord::ProvenanceEventType::CREATE,
+      
utils::Identifier::parse("00000000-0000-0000-0000-000000000007").value(), 
"componenttype");
+  record1->setDetails("details");
+
+  uint64_t event_ordinal = 0;
+  SECTION("Unset ordinal") {
+    event_ordinal = 0;
+  }
+  SECTION("Small ordinal") {
+    event_ordinal = 42;
+  }
+  SECTION("Largest ordinal") {
+    event_ordinal = std::numeric_limits<uint64_t>::max();
+  }
+  record1->setEventOrdinal(event_ordinal);
+
+  minifi::io::BufferStream stream;
+  REQUIRE(record1->serialize(stream));
+
+  auto record2 = provenance::ProvenanceEventRecord::create();
+  REQUIRE(record2->deserialize(stream));
+  REQUIRE(record2->getEventOrdinal() == event_ordinal);
+  // the ordinal is serialized last, verify that the preceding fields are 
intact as well
+  REQUIRE(record2->getEventId() == record1->getEventId());
+  REQUIRE(record2->getEventType() == record1->getEventType());
+  REQUIRE(record2->getDetails() == record1->getDetails());
+}
+
+TEST_CASE("Test Provenance event serialized before the ordinal was 
introduced", "[Testprovenance::ProvenanceEventRecordSerializeDeser]") {
+  auto record1 = 
std::make_shared<provenance::ProvenanceEventRecordImpl>(provenance::ProvenanceEventRecord::ProvenanceEventType::CREATE,
+      
utils::Identifier::parse("00000000-0000-0000-0000-000000000008").value(), 
"componenttype");
+  record1->setDetails("details");
+  record1->setEventOrdinal(42);
+
+  minifi::io::BufferStream stream;
+  REQUIRE(record1->serialize(stream));
+  const auto serialized_event = stream.moveBuffer();
+  REQUIRE(serialized_event.size() > sizeof(uint64_t));
+
+  // the ordinal is written last, an event stored before it was introduced 
simply ends earlier
+  std::ptrdiff_t missing_bytes = 0;
+  SECTION("The ordinal is missing entirely") {
+    missing_bytes = sizeof(uint64_t);
+  }
+  SECTION("The ordinal is truncated") {
+    missing_bytes = sizeof(uint64_t) / 2;
+  }
+  const std::vector<std::byte> old_format_event{serialized_event.begin(), 
serialized_event.end() - missing_bytes};
+
+  minifi::io::BufferStream old_format_stream{std::span<const 
std::byte>{old_format_event}};
+  auto record2 = provenance::ProvenanceEventRecord::create();
+  REQUIRE(record2->deserialize(old_format_stream));
+  REQUIRE(record2->getEventOrdinal() == 0);
+  REQUIRE(record2->getEventId() == record1->getEventId());
+  REQUIRE(record2->getDetails() == record1->getDetails());
+}
+
 TEST_CASE("Test Provenance record serialization", 
"[Testprovenance::ProvenanceEventRecordSerializeDeser]") {
   auto record1 = 
std::make_shared<provenance::ProvenanceEventRecordImpl>(provenance::ProvenanceEventRecord::ProvenanceEventType::CREATE,
         
utils::Identifier::parse("00000000-0000-0000-0000-000000000002").value(), 
"componenttype");
 
-  utils::Identifier eventId = record1->getEventId();
-
   std::string smileyface = ":)";
   record1->setDetails(smileyface);
 
   auto sample = 65555ms;
-  std::shared_ptr<core::Repository> testRepository = 
std::make_shared<TestRepository>();
+  auto testRepository = std::make_shared<TestRepository>();
   record1->setEventDuration(sample);
 
-  testRepository->storeElement(record1);
-  auto record2 = provenance::ProvenanceEventRecord::create();
-  record2->setEventId(eventId);
-  REQUIRE(record2->loadFromRepository(testRepository) == true);
+  REQUIRE(testRepository->appendEvents({record1}));
+  REQUIRE(record1->getEventOrdinal() == 1);
+
+  auto events = testRepository->getEvents(1, nullptr);
+  REQUIRE(events);
+  REQUIRE(events->size() == 1);
+  const auto& record2 = events->at(0);
   REQUIRE(record2->getEventId() == record1->getEventId());
   REQUIRE(record2->getComponentId() == record1->getComponentId());
   REQUIRE(record2->getComponentType() == record1->getComponentType());
   REQUIRE(record2->getDetails() == record1->getDetails());
   REQUIRE(record2->getDetails() == smileyface);
   REQUIRE(record2->getEventDuration() == sample);
+  REQUIRE(record2->getEventOrdinal() == record1->getEventOrdinal());
 }
 
 TEST_CASE("Test Flowfile record added to provenance", "[TestFlowAndProv1]") {
   auto record1 = 
std::make_shared<provenance::ProvenanceEventRecordImpl>(provenance::ProvenanceEventRecord::ProvenanceEventType::CLONE,
       
utils::Identifier::parse("00000000-0000-0000-0000-000000000003").value(), 
"componenttype");
-  utils::Identifier eventId = record1->getEventId();
   std::shared_ptr<minifi::FlowFileRecord> ffr1 = 
std::make_shared<minifi::FlowFileRecordImpl>();
   ffr1->setAttribute("potato", "potatoe");
   ffr1->setAttribute("tomato", "tomatoe");
@@ -76,13 +140,15 @@ TEST_CASE("Test Flowfile record added to provenance", 
"[TestFlowAndProv1]") {
   record1->addChildFlowFile(*ffr1);
 
   auto sample = 65555ms;
-  std::shared_ptr<core::Repository> testRepository = 
std::make_shared<TestRepository>();
+  auto testRepository = std::make_shared<TestRepository>();
   record1->setEventDuration(sample);
 
-  testRepository->storeElement(record1);
-  auto record2 = provenance::ProvenanceEventRecord::create();
-  record2->setEventId(eventId);
-  REQUIRE(record2->loadFromRepository(testRepository) == true);
+  REQUIRE(testRepository->appendEvents({record1}));
+
+  auto events = testRepository->getEvents(1, nullptr);
+  REQUIRE(events);
+  REQUIRE(events->size() == 1);
+  const auto& record2 = events->at(0);
   REQUIRE(record1->getChildrenUuids().size() == 1);
   REQUIRE(record2->getChildrenUuids().size() == 1);
   utils::Identifier childId = record2->getChildrenUuids().at(0);
@@ -93,33 +159,34 @@ TEST_CASE("Test Provenance record serialization Volatile", 
"[Testprovenance::Pro
   auto record1 = 
std::make_shared<provenance::ProvenanceEventRecordImpl>(provenance::ProvenanceEventRecord::ProvenanceEventType::CREATE,
       
utils::Identifier::parse("00000000-0000-0000-0000-000000000004").value(), 
"componenttype");
 
-  utils::Identifier eventId = record1->getEventId();
-
   std::string smileyface = ":)";
   record1->setDetails(smileyface);
 
   auto sample = 65555ms;
 
-  std::shared_ptr<core::Repository> testRepository = 
std::make_shared<core::repository::VolatileProvenanceRepository>();
+  auto testRepository = 
std::make_shared<core::repository::VolatileProvenanceRepository>();
   testRepository->initialize(nullptr);
   record1->setEventDuration(sample);
 
-  testRepository->storeElement(record1);
-  auto record2 = provenance::ProvenanceEventRecord::create();
-  record2->setEventId(eventId);
-  REQUIRE(record2->loadFromRepository(testRepository) == true);
+  REQUIRE(testRepository->appendEvents({record1}));
+  REQUIRE(record1->getEventOrdinal() == 1);
+
+  auto events = testRepository->getEvents(1, nullptr);
+  REQUIRE(events);
+  REQUIRE(events->size() == 1);
+  const auto& record2 = events->at(0);
   REQUIRE(record2->getEventId() == record1->getEventId());
   REQUIRE(record2->getComponentId() == record1->getComponentId());
   REQUIRE(record2->getComponentType() == record1->getComponentType());
   REQUIRE(record2->getDetails() == record1->getDetails());
   REQUIRE(record2->getDetails() == smileyface);
   REQUIRE(record2->getEventDuration() == sample);
+  REQUIRE(record2->getEventOrdinal() == record1->getEventOrdinal());
 }
 
 TEST_CASE("Test Flowfile record added to provenance using Volatile Repo", 
"[TestFlowAndProv1]") {
   auto record1 = 
std::make_shared<provenance::ProvenanceEventRecordImpl>(provenance::ProvenanceEventRecord::ProvenanceEventType::CLONE,
       
utils::Identifier::parse("00000000-0000-0000-0000-000000000005").value(), 
"componenttype");
-  utils::Identifier eventId = record1->getEventId();
   std::shared_ptr<minifi::FlowFileRecord> ffr1 = 
std::make_shared<minifi::FlowFileRecordImpl>();
   ffr1->setAttribute("potato", "potatoe");
   ffr1->setAttribute("tomato", "tomatoe");
@@ -127,14 +194,16 @@ TEST_CASE("Test Flowfile record added to provenance using 
Volatile Repo", "[Test
   record1->addChildFlowFile(*ffr1);
 
   auto sample = 65555ms;
-  std::shared_ptr<core::Repository> testRepository = 
std::make_shared<core::repository::VolatileProvenanceRepository>();
+  auto testRepository = 
std::make_shared<core::repository::VolatileProvenanceRepository>();
   testRepository->initialize(nullptr);
   record1->setEventDuration(sample);
 
-  testRepository->storeElement(record1);
-  auto record2 = provenance::ProvenanceEventRecord::create();
-  record2->setEventId(eventId);
-  REQUIRE(record2->loadFromRepository(testRepository) == true);
+  REQUIRE(testRepository->appendEvents({record1}));
+
+  auto events = testRepository->getEvents(1, nullptr);
+  REQUIRE(events);
+  REQUIRE(events->size() == 1);
+  const auto& record2 = events->at(0);
   REQUIRE(record1->getChildrenUuids().size() == 1);
   REQUIRE(record2->getChildrenUuids().size() == 1);
   utils::Identifier childId = record2->getChildrenUuids().at(0);
@@ -145,19 +214,20 @@ TEST_CASE("Test Provenance record serialization NoOp", 
"[Testprovenance::Provena
   auto record1 = 
std::make_shared<provenance::ProvenanceEventRecordImpl>(provenance::ProvenanceEventRecord::ProvenanceEventType::CREATE,
       
utils::Identifier::parse("00000000-0000-0000-0000-000000000006").value(), 
"componenttype");
 
-  utils::Identifier eventId = record1->getEventId();
-
   std::string smileyface = ":)";
   record1->setDetails(smileyface);
 
   auto sample = 65555ms;
 
-  std::shared_ptr<core::Repository> testRepository = 
core::createRepository("nooprepository");
+  std::shared_ptr testRepository = 
utils::dynamic_unique_cast<minifi::provenance::ProvenanceRepository>(core::createRepository("nooprepository"));
   testRepository->initialize(nullptr);
   record1->setEventDuration(sample);
 
-  REQUIRE(testRepository->storeElement(record1));
-  auto record2 = provenance::ProvenanceEventRecord::create();
-  record2->setEventId(eventId);
-  REQUIRE(record2->loadFromRepository(testRepository) == false);
+  REQUIRE(testRepository->appendEvents({record1}));
+  // the event is not stored, so it is not assigned an ordinal either
+  REQUIRE(record1->getEventOrdinal() == 0);
+
+  auto events = testRepository->getEvents(1, nullptr);
+  REQUIRE(events);
+  REQUIRE(events->empty());
 }
diff --git a/extensions/rocksdb-repos/tests/RepoTests.cpp 
b/extensions/rocksdb-repos/tests/RepoTests.cpp
index 79b887d18..4f0ad3ea9 100644
--- a/extensions/rocksdb-repos/tests/RepoTests.cpp
+++ b/extensions/rocksdb-repos/tests/RepoTests.cpp
@@ -28,7 +28,7 @@
 #include "core/RepositoryFactory.h"
 #include "FlowFileRecord.h"
 #include "FlowFileRepository.h"
-#include "ProvenanceRepository.h"
+#include "RocksDbProvenanceRepository.h"
 #include "properties/Configure.h"
 #include "unit/ProvenanceTestHelper.h"
 #include "unit/TestBase.h"
@@ -280,7 +280,7 @@ TEST_CASE("Test FlowFile Restore", "[TestFFR6]") {
   config->set(minifi::Configure::nifi_dbcontent_repository_directory_default, 
(dir / "content_repository").string());
   config->set(minifi::Configure::nifi_flowfile_repository_directory_default, 
(dir / "flowfile_repository").string());
 
-  std::shared_ptr<core::Repository> prov_repo = 
std::make_shared<TestThreadedRepository>();
+  auto prov_repo = std::make_shared<TestThreadedRepository>();
   auto ff_repository = 
std::make_shared<core::repository::FlowFileRepository>("flowFileRepository");
   std::shared_ptr<core::ContentRepository> content_repo = 
std::make_shared<core::repository::FileSystemRepository>();
   ff_repository->initialize(config);
@@ -554,8 +554,8 @@ TEST_CASE("Test getting flow file repository size 
properties", "[TestGettingRepo
     expected_rocksdb_stats = true;
   }
 
-  SECTION("ProvenanceRepository") {
-    repository = 
std::make_shared<minifi::provenance::ProvenanceRepository>("ff", dir.string(), 
0ms, 0, 1ms);
+  SECTION("RocksDbProvenanceRepository") {
+    repository = 
std::make_shared<minifi::provenance::RocksDbProvenanceRepository>("ff", 
dir.string(), 0ms, 0, 1ms);
     expected_rocksdb_stats = true;
   }
 
@@ -712,8 +712,8 @@ TEST_CASE("Flow file repositories can be stopped", 
"[TestRepoIsRunning]") {
     repository = std::make_shared<core::repository::FlowFileRepository>("ff", 
dir.string(), 0ms, 0, 1ms);
   }
 
-  SECTION("ProvenanceRepository") {
-    repository = 
std::make_shared<minifi::provenance::ProvenanceRepository>("ff", dir.string(), 
0ms, 0, 1ms);
+  SECTION("RocksDbProvenanceRepository") {
+    repository = 
std::make_shared<minifi::provenance::RocksDbProvenanceRepository>("ff", 
dir.string(), 0ms, 0, 1ms);
   }
 
   SECTION("VolatileProvenanceRepository") {
diff --git 
a/extensions/standard-processors/tests/integration/VerifyInvokeHTTP.h 
b/extensions/standard-processors/tests/integration/VerifyInvokeHTTP.h
index 0f6c74a03..499e5e41b 100644
--- a/extensions/standard-processors/tests/integration/VerifyInvokeHTTP.h
+++ b/extensions/standard-processors/tests/integration/VerifyInvokeHTTP.h
@@ -85,7 +85,7 @@ class VerifyInvokeHTTP : public HTTPIntegrationBase {
   virtual void setupFlow() {
     testSetup();
 
-    std::shared_ptr<core::Repository> test_repo = 
std::make_shared<TestThreadedRepository>();
+    auto test_repo = std::make_shared<TestThreadedRepository>();
     std::shared_ptr<core::Repository> test_flow_repo = 
std::make_shared<TestFlowRepository>();
 
     if (flow_config_path_.config_path) {
diff --git a/extensions/standard-processors/tests/unit/ProcessorTests.cpp 
b/extensions/standard-processors/tests/unit/ProcessorTests.cpp
index 4589f1ac9..503f4421b 100644
--- a/extensions/standard-processors/tests/unit/ProcessorTests.cpp
+++ b/extensions/standard-processors/tests/unit/ProcessorTests.cpp
@@ -468,13 +468,16 @@ TEST_CASE("Test Find file", "[getfileCreate3]") {
   taskReport->setBatchSize(1);
   processorReport->incrementActiveTasks();
   processorReport->setScheduledState(core::ScheduledState::RUNNING);
-  auto recordsReport = repo->getElements(1);
+  auto recordsReport = repo->getEvents(1, nullptr);
+  REQUIRE(recordsReport);
   std::function<void(const std::shared_ptr<core::ProcessContext> &, const 
std::shared_ptr<core::ProcessSession>&)> verifyReporter =
       [&](const std::shared_ptr<core::ProcessContext> &context, const 
std::shared_ptr<core::ProcessSession> &session) {
-        auto json_str = taskReport->getJsonReport(*context, *session, 
recordsReport);
-        REQUIRE(recordsReport.size() == 1);
+        auto json_str = taskReport->getJsonReport(*context, *session, 
recordsReport.value());
+        REQUIRE(recordsReport->size() == 1);
         REQUIRE(taskReport->getName() == 
std::string(minifi::core::reporting::SiteToSiteProvenanceReportingTask::ReportTaskName));
         REQUIRE(json_str.find("\"componentType\": \"getfileCreate2\"") != 
std::string::npos);
+        REQUIRE(recordsReport->at(0)->getEventOrdinal() == 1);
+        REQUIRE(json_str.find("\"eventOrdinal\": 1") != std::string::npos);
       };
 
   testController.runSession(plan, false, verifyReporter);
diff --git a/libminifi/include/CronDrivenSchedulingAgent.h 
b/libminifi/include/CronDrivenSchedulingAgent.h
index d08ac5a4f..aed7d30e5 100644
--- a/libminifi/include/CronDrivenSchedulingAgent.h
+++ b/libminifi/include/CronDrivenSchedulingAgent.h
@@ -37,12 +37,12 @@ namespace org::apache::nifi::minifi {
 class CronDrivenSchedulingAgent : public ThreadedSchedulingAgent {
  public:
   CronDrivenSchedulingAgent(const 
gsl::not_null<core::controller::ControllerServiceProvider*> 
controller_service_provider,
-                            std::shared_ptr<core::Repository> repo,
+                            std::shared_ptr<provenance::ProvenanceRepository> 
provenance_repo,
                             std::shared_ptr<core::Repository> flow_repo,
                             std::shared_ptr<core::ContentRepository> 
content_repo,
                             std::shared_ptr<Configure> configuration,
                             utils::ThreadPool& thread_pool)
-      : ThreadedSchedulingAgent(controller_service_provider, std::move(repo), 
std::move(flow_repo), std::move(content_repo), std::move(configuration), 
thread_pool) {
+      : ThreadedSchedulingAgent(controller_service_provider, 
std::move(provenance_repo), std::move(flow_repo), std::move(content_repo), 
std::move(configuration), thread_pool) {
   }
 
   ~CronDrivenSchedulingAgent() override = default;
diff --git a/libminifi/include/EventDrivenSchedulingAgent.h 
b/libminifi/include/EventDrivenSchedulingAgent.h
index 060fff588..5da7d7e65 100644
--- a/libminifi/include/EventDrivenSchedulingAgent.h
+++ b/libminifi/include/EventDrivenSchedulingAgent.h
@@ -35,10 +35,10 @@ namespace org::apache::nifi::minifi {
 
 class EventDrivenSchedulingAgent : public ThreadedSchedulingAgent {
  public:
-  EventDrivenSchedulingAgent(const 
gsl::not_null<core::controller::ControllerServiceProvider*> 
controller_service_provider, std::shared_ptr<core::Repository> repo,
+  EventDrivenSchedulingAgent(const 
gsl::not_null<core::controller::ControllerServiceProvider*> 
controller_service_provider, std::shared_ptr<provenance::ProvenanceRepository> 
provenance_repo,
                              std::shared_ptr<core::Repository> flow_repo, 
std::shared_ptr<core::ContentRepository> content_repo, 
std::shared_ptr<Configure> configuration,
                              utils::ThreadPool &thread_pool)
-      : ThreadedSchedulingAgent(controller_service_provider, repo, flow_repo, 
content_repo, configuration, thread_pool) {
+      : ThreadedSchedulingAgent(controller_service_provider, provenance_repo, 
flow_repo, content_repo, configuration, thread_pool) {
     using namespace std::literals::chrono_literals;
 
     time_slice_ = 
configuration->get(Configure::nifi_flow_engine_event_driven_time_slice)
diff --git a/libminifi/include/FlowController.h 
b/libminifi/include/FlowController.h
index 5177e2957..0b9efcbfd 100644
--- a/libminifi/include/FlowController.h
+++ b/libminifi/include/FlowController.h
@@ -67,7 +67,7 @@ class ProcessorController;
 
 class FlowController : public 
core::controller::ForwardingControllerServiceProvider,  public 
state::StateMonitor {
  public:
-  FlowController(std::shared_ptr<core::Repository> provenance_repo, 
std::shared_ptr<core::Repository> flow_file_repo,
+  FlowController(std::shared_ptr<provenance::ProvenanceRepository> 
provenance_repo, std::shared_ptr<core::Repository> flow_file_repo,
                  std::shared_ptr<Configure> configure, 
std::shared_ptr<core::FlowConfiguration> flow_configuration,
                  std::shared_ptr<core::ContentRepository> content_repo, 
std::unique_ptr<state::MetricsPublisherStore> metrics_publisher_store = nullptr,
                  std::shared_ptr<utils::file::FileSystem> filesystem = 
std::make_shared<utils::file::FileSystem>(),
@@ -76,7 +76,7 @@ class FlowController : public 
core::controller::ForwardingControllerServiceProvi
 
   ~FlowController() override;
 
-  virtual std::shared_ptr<core::Repository> getProvenanceRepository() {
+  virtual std::shared_ptr<provenance::ProvenanceRepository> 
getProvenanceRepository() {
     return this->provenance_repo_;
   }
 
@@ -189,7 +189,7 @@ class FlowController : public 
core::controller::ForwardingControllerServiceProvi
   // Thread pool for schedulers
   utils::ThreadPool thread_pool_;
   std::shared_ptr<Configure> configuration_;
-  std::shared_ptr<core::Repository> provenance_repo_;
+  std::shared_ptr<provenance::ProvenanceRepository> provenance_repo_;
   std::shared_ptr<core::Repository> flow_file_repo_;
   std::shared_ptr<core::ContentRepository> content_repo_;
   std::shared_ptr<core::FlowConfiguration> flow_configuration_;
diff --git a/libminifi/include/SchedulingAgent.h 
b/libminifi/include/SchedulingAgent.h
index d5cab7af5..6c8b12f15 100644
--- a/libminifi/include/SchedulingAgent.h
+++ b/libminifi/include/SchedulingAgent.h
@@ -50,8 +50,8 @@ namespace org::apache::nifi::minifi {
 
 class SchedulingAgent {
  public:
-  SchedulingAgent(const 
gsl::not_null<core::controller::ControllerServiceProvider*> 
controller_service_provider, std::shared_ptr<core::Repository> repo, 
std::shared_ptr<core::Repository> flow_repo,
-                  std::shared_ptr<core::ContentRepository> content_repo, 
std::shared_ptr<Configure> configuration, utils::ThreadPool& thread_pool)
+  SchedulingAgent(const 
gsl::not_null<core::controller::ControllerServiceProvider*> 
controller_service_provider, std::shared_ptr<provenance::ProvenanceRepository> 
provenance_repo,
+                  std::shared_ptr<core::Repository> flow_repo, 
std::shared_ptr<core::ContentRepository> content_repo, 
std::shared_ptr<Configure> configuration, utils::ThreadPool& thread_pool)
       : admin_yield_duration_(),
         bored_yield_duration_(0),
         configure_(configuration),
@@ -60,7 +60,7 @@ class SchedulingAgent {
         controller_service_provider_(controller_service_provider),
         logger_(core::logging::LoggerFactory<SchedulingAgent>::getLogger()) {
     running_ = false;
-    repo_ = repo;
+    provenance_repo_ = provenance_repo;
     flow_repo_ = flow_repo;
 
     alert_time_ = configuration->get(Configure::nifi_flow_engine_alert_period)
@@ -122,7 +122,7 @@ class SchedulingAgent {
 
   std::shared_ptr<Configure> configure_;
 
-  std::shared_ptr<core::Repository> repo_;
+  std::shared_ptr<provenance::ProvenanceRepository> provenance_repo_;
 
   std::shared_ptr<core::Repository> flow_repo_;
 
diff --git a/libminifi/include/ThreadedSchedulingAgent.h 
b/libminifi/include/ThreadedSchedulingAgent.h
index 7a3ad2299..5de546779 100644
--- a/libminifi/include/ThreadedSchedulingAgent.h
+++ b/libminifi/include/ThreadedSchedulingAgent.h
@@ -38,10 +38,10 @@ namespace org::apache::nifi::minifi {
  */
 class ThreadedSchedulingAgent : public SchedulingAgent {
  public:
-  ThreadedSchedulingAgent(const 
gsl::not_null<core::controller::ControllerServiceProvider*> 
controller_service_provider, std::shared_ptr<core::Repository> repo,
+  ThreadedSchedulingAgent(const 
gsl::not_null<core::controller::ControllerServiceProvider*> 
controller_service_provider, std::shared_ptr<provenance::ProvenanceRepository> 
provenance_repo,
         std::shared_ptr<core::Repository> flow_repo, 
std::shared_ptr<core::ContentRepository> content_repo,
         std::shared_ptr<Configure> configuration,  utils::ThreadPool 
&thread_pool)
-      : SchedulingAgent(controller_service_provider, repo, flow_repo, 
content_repo, configuration, thread_pool) {
+      : SchedulingAgent(controller_service_provider, provenance_repo, 
flow_repo, content_repo, configuration, thread_pool) {
   }
   ~ThreadedSchedulingAgent() override = default;
 
diff --git a/libminifi/include/TimerDrivenSchedulingAgent.h 
b/libminifi/include/TimerDrivenSchedulingAgent.h
index 99073ce99..936d60c74 100644
--- a/libminifi/include/TimerDrivenSchedulingAgent.h
+++ b/libminifi/include/TimerDrivenSchedulingAgent.h
@@ -30,10 +30,10 @@
 namespace org::apache::nifi::minifi {
 class TimerDrivenSchedulingAgent : public ThreadedSchedulingAgent {
  public:
-  TimerDrivenSchedulingAgent(const 
gsl::not_null<core::controller::ControllerServiceProvider*> 
controller_service_provider, std::shared_ptr<core::Repository> repo,
+  TimerDrivenSchedulingAgent(const 
gsl::not_null<core::controller::ControllerServiceProvider*> 
controller_service_provider, std::shared_ptr<provenance::ProvenanceRepository> 
provenance_repo,
                              std::shared_ptr<core::Repository> flow_repo, 
std::shared_ptr<core::ContentRepository> content_repo, 
std::shared_ptr<Configure> configure,
                              utils::ThreadPool &thread_pool)
-      : ThreadedSchedulingAgent(controller_service_provider, repo, flow_repo, 
content_repo, configure, thread_pool) {
+      : ThreadedSchedulingAgent(controller_service_provider, provenance_repo, 
flow_repo, content_repo, configure, thread_pool) {
   }
 
   utils::TaskRescheduleInfo run(core::Processor* processor, const 
std::shared_ptr<core::ProcessContext> &processContext,
diff --git a/libminifi/include/core/ProcessContextImpl.h 
b/libminifi/include/core/ProcessContextImpl.h
index 7bbf3ccf5..2115a598c 100644
--- a/libminifi/include/core/ProcessContextImpl.h
+++ b/libminifi/include/core/ProcessContextImpl.h
@@ -50,11 +50,11 @@ class Processor;
 class ProcessContextImpl : public core::VariableRegistryImpl, public virtual 
ProcessContext {
  public:
   ProcessContextImpl(Processor& processor, 
controller::ControllerServiceProvider* controller_service_provider, const 
std::shared_ptr<core::StateStorage>& state_storage,
-      const std::shared_ptr<core::Repository>& repo, const 
std::shared_ptr<core::Repository>& flow_repo,
+      const std::shared_ptr<provenance::ProvenanceRepository>& repo, const 
std::shared_ptr<core::Repository>& flow_repo,
       const std::shared_ptr<core::ContentRepository>& content_repo = 
repository::createFileSystemRepository());
 
   ProcessContextImpl(Processor& processor, 
controller::ControllerServiceProvider* controller_service_provider, const 
std::shared_ptr<core::StateStorage>& state_storage,
-      const std::shared_ptr<core::Repository>& repo, const 
std::shared_ptr<core::Repository>& flow_repo, const 
std::shared_ptr<minifi::Configure>& configuration,
+      const std::shared_ptr<provenance::ProvenanceRepository>& repo, const 
std::shared_ptr<core::Repository>& flow_repo, const 
std::shared_ptr<minifi::Configure>& configuration,
       const std::shared_ptr<core::ContentRepository>& content_repo = 
repository::createFileSystemRepository());
 
   // Get Processor associated with the Process Context
@@ -85,7 +85,7 @@ class ProcessContextImpl : public core::VariableRegistryImpl, 
public virtual Pro
 
   void yield() override;
 
-  std::shared_ptr<core::Repository> getProvenanceRepository() override { 
return repo_; }
+  std::shared_ptr<provenance::ProvenanceRepository> getProvenanceRepository() 
override { return provenance_repo_; }
 
   /**
    * Returns a reference to the content repository for the running instance.
@@ -199,7 +199,7 @@ class ProcessContextImpl : public 
core::VariableRegistryImpl, public virtual Pro
   std::shared_ptr<logging::Logger> logger_;
   controller::ControllerServiceProvider* controller_service_provider_;
   std::shared_ptr<core::StateStorage> state_storage_;
-  std::shared_ptr<core::Repository> repo_;
+  std::shared_ptr<provenance::ProvenanceRepository> provenance_repo_;
   std::shared_ptr<core::Repository> flow_repo_;
   std::shared_ptr<core::ContentRepository> content_repo_;
   Processor& processor_;
diff --git 
a/libminifi/include/core/reporting/SiteToSiteProvenanceReportingTask.h 
b/libminifi/include/core/reporting/SiteToSiteProvenanceReportingTask.h
index 055cd867d..83c4900ca 100644
--- a/libminifi/include/core/reporting/SiteToSiteProvenanceReportingTask.h
+++ b/libminifi/include/core/reporting/SiteToSiteProvenanceReportingTask.h
@@ -44,7 +44,7 @@ class SiteToSiteProvenanceReportingTask : public 
minifi::RemoteProcessGroupPort
   static constexpr char const* ReportTaskName = 
"SiteToSiteProvenanceReportingTask";
   static const char *ProvenanceAppStr;
 
-  static std::string getJsonReport(core::ProcessContext& context, 
core::ProcessSession& session, const 
std::vector<std::shared_ptr<core::SerializableComponent>> &records);
+  static std::string getJsonReport(core::ProcessContext& context, 
core::ProcessSession& session, const 
std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>> &records);
 
   void onSchedule(core::ProcessContext& context, core::ProcessSessionFactory& 
session_factory) override;
   void onTrigger(core::ProcessContext& context, core::ProcessSession& session) 
override;
diff --git a/libminifi/include/core/repository/AtomicRepoEntries.h 
b/libminifi/include/core/repository/AtomicRepoEntries.h
index 317bd0eea..4156db192 100644
--- a/libminifi/include/core/repository/AtomicRepoEntries.h
+++ b/libminifi/include/core/repository/AtomicRepoEntries.h
@@ -278,6 +278,24 @@ class AtomicEntry {
     return true;
   }
 
+  /**
+   * Calls @visitor with the key and the buffer of the contained value, 
without consuming it.
+   * The visitor is called while this entry is locked, so it should not block.
+   * @return false if this entry is empty
+   */
+  template<typename Visitor>
+  bool visitValue(Visitor&& visitor) {
+    try_lock();
+    if (!has_value_) {
+      try_unlock();
+      return false;
+    }
+    const T& key = value_.getKey();
+    std::forward<Visitor>(visitor)(key, value_.getBuffer());
+    try_unlock();
+    return true;
+  }
+
   void decrementOwnership() {
     try_lock();
     if (!has_value_) {
diff --git a/libminifi/include/core/repository/NoOpThreadedRepository.h 
b/libminifi/include/core/repository/NoOpThreadedRepository.h
index df23a1b32..3b7b5354c 100644
--- a/libminifi/include/core/repository/NoOpThreadedRepository.h
+++ b/libminifi/include/core/repository/NoOpThreadedRepository.h
@@ -21,10 +21,11 @@
 #include <thread>
 
 #include "core/ThreadedRepository.h"
+#include "minifi-cpp/provenance/ProvenanceRepository.h"
 
 namespace org::apache::nifi::minifi::core::repository {
 
-class NoOpThreadedRepository : public core::ThreadedRepositoryImpl {
+class NoOpThreadedRepository : public core::ThreadedRepositoryImpl, public 
provenance::ProvenanceRepository {
  public:
   explicit NoOpThreadedRepository(std::string_view repo_name)
     : ThreadedRepositoryImpl(repo_name) {
@@ -47,6 +48,21 @@ class NoOpThreadedRepository : public 
core::ThreadedRepositoryImpl {
     return 0;
   }
 
+  std::expected<void, std::string> appendEvents(const 
std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>>& /*events*/) 
override {
+    return {};
+  }
+
+  std::unique_ptr<ProvenanceRepository::Cursor> 
cursorFromString(std::string_view /*cursor_str*/) override {
+    return nullptr;
+  }
+
+  
std::expected<std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>>, 
std::string> getEvents(size_t /*max_size*/, Cursor* cursor) override {
+    if (cursor) {
+      return std::unexpected{"Cursor based query is not supported"};
+    }
+    return {};
+  }
+
  private:
   void run() override {
   }
diff --git a/libminifi/include/core/repository/VolatileProvenanceRepository.h 
b/libminifi/include/core/repository/VolatileProvenanceRepository.h
index da5df86fc..5877bb892 100644
--- a/libminifi/include/core/repository/VolatileProvenanceRepository.h
+++ b/libminifi/include/core/repository/VolatileProvenanceRepository.h
@@ -21,10 +21,11 @@
 #include <string_view>
 
 #include "VolatileRepository.h"
+#include "minifi-cpp/provenance/ProvenanceRepository.h"
 
 namespace org::apache::nifi::minifi::core::repository {
 
-class VolatileProvenanceRepository : public VolatileRepository {
+class VolatileProvenanceRepository : public VolatileRepository, public 
provenance::ProvenanceRepository {
  public:
   explicit VolatileProvenanceRepository(std::string_view repo_name = "",
                                         std::string /*dir*/ = 
REPOSITORY_DIRECTORY,
@@ -38,6 +39,35 @@ class VolatileProvenanceRepository : public 
VolatileRepository {
     stop();
   }
 
+  bool initialize(const std::shared_ptr<Configure> &configure) override {
+    if (!VolatileRepository::initialize(configure)) {
+      return false;
+    }
+    next_event_key_ =  1;
+    return true;
+  }
+
+  std::expected<void, std::string> appendEvents(const 
std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>>& events) 
override {
+    EntryStreams data;
+    data.reserve(events.size());
+    std::lock_guard guard(next_event_key_mtx_);
+    for (auto& event : events) {
+      event->setEventOrdinal(next_event_key_++);
+      data.emplace_back(std::to_string(event->getEventOrdinal()), 
std::make_unique<io::BufferStream>());
+      event->serialize(*data.back().second);
+    }
+    MultiPut(data);
+
+    return {};
+  }
+
+  std::unique_ptr<ProvenanceRepository::Cursor> 
cursorFromString(std::string_view cursor_str) override;
+
+  // Returns the events following the cursor in ordinal order. As this 
repository can overwrite
+  // its oldest entries, a cursor guarantees that no event is returned twice, 
not that no event
+  // is missed.
+  
std::expected<std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>>, 
std::string> getEvents(size_t max_size, Cursor* cursor) override;
+
  private:
   void run() override {
   }
@@ -51,6 +81,8 @@ class VolatileProvenanceRepository : public 
VolatileRepository {
   }
 
   std::thread thread_;
+  std::mutex next_event_key_mtx_;
+  uint64_t next_event_key_;
 };
 
 }  // namespace org::apache::nifi::minifi::core::repository
diff --git a/libminifi/include/core/repository/VolatileRepository.h 
b/libminifi/include/core/repository/VolatileRepository.h
index c552601cd..5273cd26e 100644
--- a/libminifi/include/core/repository/VolatileRepository.h
+++ b/libminifi/include/core/repository/VolatileRepository.h
@@ -65,7 +65,7 @@ class VolatileRepository : public 
core::ThreadedRepositoryImpl {
    * Places new objects into the volatile memory area
    * @param data the key-value pairs to add to the repository
    **/
-  bool MultiPut(const std::vector<std::pair<std::string, 
std::unique_ptr<io::BufferStream>>>& data) override;
+  bool MultiPut(const EntryStreams& data) override;
 
   /**
    * Deletes the key
@@ -106,8 +106,6 @@ class VolatileRepository : public 
core::ThreadedRepositoryImpl {
   std::atomic<uint32_t> current_index_;
   std::mutex purge_mutex_;
   std::vector<std::string> purge_list_;
-
- private:
   std::shared_ptr<logging::Logger> logger_;
 };
 
diff --git a/libminifi/include/provenance/Provenance.h 
b/libminifi/include/provenance/Provenance.h
index d891581de..3f433409b 100644
--- a/libminifi/include/provenance/Provenance.h
+++ b/libminifi/include/provenance/Provenance.h
@@ -38,6 +38,7 @@
 #include "utils/Id.h"
 #include "utils/TimeUtil.h"
 #include "minifi-cpp/provenance/Provenance.h"
+#include "minifi-cpp/provenance/ProvenanceRepository.h"
 
 namespace org::apache::nifi::minifi::provenance {
 
@@ -54,6 +55,14 @@ class ProvenanceEventRecordImpl : public 
core::SerializableComponentImpl, public
 
   ~ProvenanceEventRecordImpl() override = default;
 
+  uint64_t getEventOrdinal() const override {
+    return event_ordinal_;
+  }
+
+  void setEventOrdinal(uint64_t event_ordinal) override {
+    event_ordinal_ = event_ordinal;
+  }
+
   utils::Identifier getEventId() const override {
     return getUUID();
   }
@@ -214,10 +223,11 @@ class ProvenanceEventRecordImpl : public 
core::SerializableComponentImpl, public
 
   bool serialize(io::OutputStream& output_stream) override;
   bool deserialize(io::InputStream &input_stream) override;
-  bool loadFromRepository(const std::shared_ptr<core::Repository> &repo) 
override;
 
  protected:
   ProvenanceEventType event_type_;
+  // the index of the event
+  uint64_t event_ordinal_{0};
   // Date at which the event was created
   std::chrono::system_clock::time_point event_time_{};
   // Date at which the flow file entered the flow
@@ -243,15 +253,11 @@ class ProvenanceEventRecordImpl : public 
core::SerializableComponentImpl, public
   std::string source_queue_identifier_;
   std::string relationship_;
   std::string alternate_identifier_uri_;
-
- private:
-  static std::shared_ptr<core::logging::Logger> logger_;
-  static std::shared_ptr<utils::IdGenerator> id_generator_;
 };
 
 class ProvenanceReporterImpl : public virtual ProvenanceReporter {
  public:
-  ProvenanceReporterImpl(std::shared_ptr<core::Repository> repo, 
utils::Identifier component_id, std::string component_type)
+  ProvenanceReporterImpl(std::shared_ptr<provenance::ProvenanceRepository> 
repo, utils::Identifier component_id, std::string component_type)
       : component_id_(component_id),
         component_type_(std::move(component_type)),
         logger_(core::logging::LoggerFactory<ProvenanceReporter>::getLogger()),
@@ -266,16 +272,12 @@ class ProvenanceReporterImpl : public virtual 
ProvenanceReporter {
     clear();
   }
 
-  std::set<std::shared_ptr<ProvenanceEventRecord>> getEvents() const override {
+  std::vector<std::shared_ptr<ProvenanceEventRecord>> getEvents() const 
override {
     return events_;
   }
 
   void add(const std::shared_ptr<ProvenanceEventRecord> &event) override {
-    events_.insert(event);
-  }
-
-  void remove(const std::shared_ptr<ProvenanceEventRecord> &event) override {
-    events_.erase(event);
+    events_.push_back(event);
   }
 
   void clear() final {
@@ -290,7 +292,7 @@ class ProvenanceReporterImpl : public virtual 
ProvenanceReporter {
   void clone(const core::FlowFile& parent, const core::FlowFile& child) 
override;
   void expire(const core::FlowFile& flow_file, const std::string& detail) 
override;
   void drop(const core::FlowFile& flow_file, const std::string& reason) 
override;
-  void send(const core::FlowFile& flow_file, const std::string& transitUri, 
const std::string& detail, std::chrono::milliseconds processingDuration, bool 
force) override;
+  void send(const core::FlowFile& flow_file, const std::string& transitUri, 
const std::string& detail, std::chrono::milliseconds processingDuration) 
override;
   void fetch(const core::FlowFile& flow_file, const std::string& transitUri, 
const std::string& detail, std::chrono::milliseconds processingDuration) 
override;
   void receive(const core::FlowFile& flow_file, const std::string& transitUri,
     const std::string& sourceSystemFlowFileIdentifier, const std::string& 
detail, std::chrono::milliseconds processingDuration) override;
@@ -313,8 +315,8 @@ class ProvenanceReporterImpl : public virtual 
ProvenanceReporter {
 
  private:
   std::shared_ptr<core::logging::Logger> logger_;
-  std::set<std::shared_ptr<ProvenanceEventRecord>> events_;
-  std::shared_ptr<core::Repository> repo_;
+  std::vector<std::shared_ptr<ProvenanceEventRecord>> events_;
+  std::shared_ptr<provenance::ProvenanceRepository> repo_;
 };
 
 }  // namespace org::apache::nifi::minifi::provenance
diff --git a/libminifi/src/FlowController.cpp b/libminifi/src/FlowController.cpp
index e315ae3b5..040196cec 100644
--- a/libminifi/src/FlowController.cpp
+++ b/libminifi/src/FlowController.cpp
@@ -46,7 +46,7 @@
 
 namespace org::apache::nifi::minifi {
 
-FlowController::FlowController(std::shared_ptr<core::Repository> 
provenance_repo, std::shared_ptr<core::Repository> flow_file_repo,
+FlowController::FlowController(std::shared_ptr<provenance::ProvenanceRepository>
 provenance_repo, std::shared_ptr<core::Repository> flow_file_repo,
                                std::shared_ptr<Configure> configure, 
std::shared_ptr<core::FlowConfiguration> flow_configuration,
                                std::shared_ptr<core::ContentRepository> 
content_repo, std::unique_ptr<state::MetricsPublisherStore> 
metrics_publisher_store,
                                std::shared_ptr<utils::file::FileSystem> 
filesystem, RequestShutdownCallback request_shutdown,
diff --git a/libminifi/src/ThreadedSchedulingAgent.cpp 
b/libminifi/src/ThreadedSchedulingAgent.cpp
index 8750af781..9b298b235 100644
--- a/libminifi/src/ThreadedSchedulingAgent.cpp
+++ b/libminifi/src/ThreadedSchedulingAgent.cpp
@@ -72,7 +72,7 @@ void ThreadedSchedulingAgent::schedule(core::Processor* 
processor) {
   }
 
   auto state_storage = core::ProcessContextImpl::getStateStorage(logger_, 
controller_service_provider_, configure_);
-  auto process_context = 
std::make_shared<core::ProcessContextImpl>(*processor, 
controller_service_provider_, state_storage, repo_, flow_repo_, configure_, 
content_repo_);
+  auto process_context = 
std::make_shared<core::ProcessContextImpl>(*processor, 
controller_service_provider_, state_storage, provenance_repo_, flow_repo_, 
configure_, content_repo_);
 
   auto session_factory = 
std::make_shared<core::ProcessSessionFactoryImpl>(process_context);
 
@@ -81,7 +81,7 @@ void ThreadedSchedulingAgent::schedule(core::Processor* 
processor) {
   ThreadedSchedulingAgent *agent = this;
   for (uint8_t i = 0; i < processor->getMaxConcurrentTasks(); i++) {
     processor->incrementActiveTasks();
-    auto thread_process_context = 
std::make_shared<core::ProcessContextImpl>(*processor, 
controller_service_provider_, state_storage, repo_, flow_repo_, configure_, 
content_repo_);
+    auto thread_process_context = 
std::make_shared<core::ProcessContextImpl>(*processor, 
controller_service_provider_, state_storage, provenance_repo_, flow_repo_, 
configure_, content_repo_);
     auto thread_session_factory = 
std::make_shared<core::ProcessSessionFactoryImpl>(thread_process_context);
     std::function<utils::TaskRescheduleInfo()> f_ex = [agent, processor, 
thread_process_context, thread_session_factory] () {
       return agent->run(processor, thread_process_context, 
thread_session_factory);
diff --git a/libminifi/src/core/ProcessContextImpl.cpp 
b/libminifi/src/core/ProcessContextImpl.cpp
index c732f32bc..bb8399645 100644
--- a/libminifi/src/core/ProcessContextImpl.cpp
+++ b/libminifi/src/core/ProcessContextImpl.cpp
@@ -41,13 +41,13 @@ class StandardProcessorInfo : public ProcessorInfo {
 }  // namespace
 
 ProcessContextImpl::ProcessContextImpl(
-    Processor& processor, controller::ControllerServiceProvider* 
controller_service_provider, const std::shared_ptr<core::StateStorage>& 
state_storage, const std::shared_ptr<core::Repository>& repo,
-    const std::shared_ptr<core::Repository>& flow_repo, const 
std::shared_ptr<core::ContentRepository>& content_repo)
+    Processor& processor, controller::ControllerServiceProvider* 
controller_service_provider, const std::shared_ptr<core::StateStorage>& 
state_storage,
+    const std::shared_ptr<provenance::ProvenanceRepository>& provenance_repo, 
const std::shared_ptr<core::Repository>& flow_repo, const 
std::shared_ptr<core::ContentRepository>& content_repo)
     : 
VariableRegistryImpl(static_cast<std::shared_ptr<Configure>>(minifi::Configure::create())),
       logger_(logging::LoggerFactory<ProcessContext>::getLogger()),
       controller_service_provider_(controller_service_provider),
       state_storage_(state_storage),
-      repo_(repo),
+      provenance_repo_(provenance_repo),
       flow_repo_(flow_repo),
       content_repo_(content_repo),
       processor_(processor),
@@ -55,14 +55,14 @@ ProcessContextImpl::ProcessContextImpl(
       info_(std::make_unique<StandardProcessorInfo>(processor)) {}
 
 ProcessContextImpl::ProcessContextImpl(
-    Processor& processor, controller::ControllerServiceProvider* 
controller_service_provider, const std::shared_ptr<core::StateStorage>& 
state_storage, const std::shared_ptr<core::Repository>& repo,
-    const std::shared_ptr<core::Repository>& flow_repo, const 
std::shared_ptr<minifi::Configure>& configuration,
+    Processor& processor, controller::ControllerServiceProvider* 
controller_service_provider, const std::shared_ptr<core::StateStorage>& 
state_storage,
+    const std::shared_ptr<provenance::ProvenanceRepository>& provenance_repo, 
const std::shared_ptr<core::Repository>& flow_repo, const 
std::shared_ptr<minifi::Configure>& configuration,
     const std::shared_ptr<core::ContentRepository>& content_repo)
     : VariableRegistryImpl(configuration),
       logger_(logging::LoggerFactory<ProcessContext>::getLogger()),
       controller_service_provider_(controller_service_provider),
       state_storage_(state_storage),
-      repo_(repo),
+      provenance_repo_(provenance_repo),
       flow_repo_(flow_repo),
       content_repo_(content_repo),
       processor_(processor),
diff --git a/libminifi/src/core/ProcessSession.cpp 
b/libminifi/src/core/ProcessSession.cpp
index 4352f7b41..b6d4fce1a 100644
--- a/libminifi/src/core/ProcessSession.cpp
+++ b/libminifi/src/core/ProcessSession.cpp
@@ -1065,7 +1065,7 @@ void ProcessSessionImpl::persistFlowFilesBeforeTransfer(
     return;
   }
 
-  std::vector<std::pair<std::string, std::unique_ptr<io::BufferStream>>> 
flowData;
+  Repository::EntryStreams flowData;
 
   enum class Type {
     Dropped, Transferred
diff --git a/libminifi/src/core/flow/StructuredConfiguration.cpp 
b/libminifi/src/core/flow/StructuredConfiguration.cpp
index 47324cb62..ef68db941 100644
--- a/libminifi/src/core/flow/StructuredConfiguration.cpp
+++ b/libminifi/src/core/flow/StructuredConfiguration.cpp
@@ -587,6 +587,10 @@ void 
StructuredConfiguration::parseProvenanceReporting(const Node& node, core::P
 
   auto report_task = createProvenanceReportTask();
 
+  utils::Identifier task_uuid;
+  task_uuid = getOrGenerateId(node);
+  report_task->setUUID(task_uuid);
+
   checkRequiredField(node, schema_.scheduling_strategy);
   auto schedulingStrategyStr = 
node[schema_.scheduling_strategy].getString().value();
   checkRequiredField(node, schema_.scheduling_period);
diff --git a/libminifi/src/core/reporting/SiteToSiteProvenanceReportingTask.cpp 
b/libminifi/src/core/reporting/SiteToSiteProvenanceReportingTask.cpp
index 363c81f80..c7c004695 100644
--- a/libminifi/src/core/reporting/SiteToSiteProvenanceReportingTask.cpp
+++ b/libminifi/src/core/reporting/SiteToSiteProvenanceReportingTask.cpp
@@ -17,35 +17,59 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <vector>
-#include <queue>
+#include "core/reporting/SiteToSiteProvenanceReportingTask.h"
+
+#include <functional>
+#include <iostream>
 #include <map>
-#include <set>
-#include <string>
 #include <memory>
+#include <queue>
+#include <set>
 #include <sstream>
-#include <functional>
-#include <iostream>
+#include <string>
 #include <utility>
+#include <vector>
 
-#include "rapidjson/document.h"
-#include "rapidjson/writer.h"
-#include "rapidjson/stringbuffer.h"
-#include "rapidjson/prettywriter.h"
-
-#include "minifi-cpp/core/Repository.h"
-#include "core/reporting/SiteToSiteProvenanceReportingTask.h"
-#include "utils/TimeUtil.h"
-#include "minifi-cpp/core/ProcessContext.h"
+#include "FlowController.h"
 #include "core/ProcessSession.h"
+#include "minifi-cpp/core/ProcessContext.h"
+#include "minifi-cpp/core/Repository.h"
 #include "minifi-cpp/provenance/Provenance.h"
-#include "FlowController.h"
 #include "minifi-cpp/utils/gsl.h"
+#include "rapidjson/document.h"
+#include "rapidjson/prettywriter.h"
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/writer.h"
+#include "utils/TimeUtil.h"
+#include "utils/net/NetworkInterfaceInfo.h"
 
 namespace org::apache::nifi::minifi::core::reporting {
 
 const char *SiteToSiteProvenanceReportingTask::ProvenanceAppStr = "MiNiFi 
Flow";
 
+namespace {
+
+std::unique_ptr<provenance::ProvenanceRepository::Cursor> 
getCursor(StateManager& state_manager, provenance::ProvenanceRepository& 
provenance_repository, logging::Logger& logger) {
+  std::string cursor_str;
+  {
+    std::unordered_map<std::string, std::string> state_map;
+    if (state_manager.get(state_map)) {
+      if (auto it = state_map.find("cursor"); it != state_map.end()) {
+        cursor_str = it->second;
+      }
+    }
+  }
+  std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>> records;
+  auto cursor = provenance_repository.cursorFromString(cursor_str);
+  if (!cursor_str.empty() && !cursor) {
+    logger.log_error("Failed to parse cursor, falling back to enumerating from 
the beginning");
+    cursor = provenance_repository.cursorFromString("");
+  }
+  return cursor;
+}
+
+}  // namespace
+
 void SiteToSiteProvenanceReportingTask::initialize() {
   RemoteProcessGroupPort::initialize();
 }
@@ -88,7 +112,7 @@ void appendJsonStr(const utils::SmallString<N>& value, 
rapidjson::Value& parent,
   parent.PushBack(valueVal, alloc);
 }
 
-std::string 
SiteToSiteProvenanceReportingTask::getJsonReport(core::ProcessContext&, 
core::ProcessSession&, const 
std::vector<std::shared_ptr<core::SerializableComponent>> &records) {
+std::string 
SiteToSiteProvenanceReportingTask::getJsonReport(core::ProcessContext&, 
core::ProcessSession&, const 
std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>> &records) {
   rapidjson::Document array(rapidjson::kArrayType);
   rapidjson::Document::AllocatorType &alloc = array.GetAllocator();
 
@@ -111,6 +135,8 @@ std::string 
SiteToSiteProvenanceReportingTask::getJsonReport(core::ProcessContex
 
     recordJson.AddMember("entityType", "org.apache.nifi.flowfile.FlowFile", 
alloc);
 
+    recordJson.AddMember("eventOrdinal", record->getEventOrdinal(), alloc);
+
     recordJson.AddMember("eventId", 
getStringValue(record->getEventId().to_string(), alloc), alloc);
     recordJson.AddMember("eventType", 
getStringValue(provenance::ProvenanceEventRecord::ProvenanceEventTypeStr[record->getEventType()],
 alloc), alloc);
     recordJson.AddMember("details", getStringValue(record->getDetails(), 
alloc), alloc);
@@ -156,11 +182,23 @@ void 
SiteToSiteProvenanceReportingTask::onSchedule(core::ProcessContext& context
 }
 
 void SiteToSiteProvenanceReportingTask::onTrigger(core::ProcessContext& 
context, core::ProcessSession& session) {
-  std::shared_ptr<core::Repository> repo = context.getProvenanceRepository();
+  std::shared_ptr<provenance::ProvenanceRepository> repo = 
context.getProvenanceRepository();
   if (!repo) {
     throw minifi::Exception(ExceptionType::REPOSITORY_EXCEPTION, "Failed to 
retrieve provenance repository");
   }
-  auto records = repo->getElements(batch_size_);
+  auto* state_manager = context.getStateManager();
+  if (!state_manager) {
+    logger_->log_error("Failed to get StateManager");
+    context.yield();
+    return;
+  }
+  auto cursor = getCursor(*state_manager, *repo, *logger_);
+  std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>> records;
+  if (auto result = repo->getEvents(batch_size_, cursor.get())) {
+    records = std::move(result.value());
+  } else {
+    throw minifi::Exception(GENERAL_EXCEPTION, "Failed to retrieve records: " 
+ result.error());
+  }
   if (records.empty()) {
     logger_->log_debug("No new provenance records");
     return;
@@ -182,14 +220,29 @@ void 
SiteToSiteProvenanceReportingTask::onTrigger(core::ProcessContext& context,
     std::map<std::string, std::string> attributes;
     if (!protocol_->transmitPayload(context, jsonStr, attributes)) {
       context.yield();
+      return;
     }
   } catch (...) {
     // if transfer bytes failed, return instead of purge the provenance records
     return;
   }
 
-  // we transfer the record, purge the record from DB
-  repo->Delete(records);
+  if (cursor) {
+    // no need to delete just update the state
+    std::unordered_map<std::string, std::string> state_map;
+    state_map["cursor"] = cursor->toString();
+    if (!state_manager->set(state_map)) {
+      logger_->log_error("Failed to update cursor state");
+    }
+  } else {
+    // we transfer the record, purge the record from DB
+    std::vector<std::shared_ptr<core::SerializableComponent>> entries;
+    entries.reserve(records.size());
+    for (const auto& record : records) {
+      entries.push_back(record);
+    }
+    repo->Delete(entries);
+  }
   returnProtocol(context, std::move(protocol_));
 }
 
diff --git a/libminifi/src/core/repository/VolatileProvenanceRepository.cpp 
b/libminifi/src/core/repository/VolatileProvenanceRepository.cpp
new file mode 100644
index 000000000..31adcbf54
--- /dev/null
+++ b/libminifi/src/core/repository/VolatileProvenanceRepository.cpp
@@ -0,0 +1,112 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "core/repository/VolatileProvenanceRepository.h"
+
+#include <algorithm>
+#include <memory>
+#include <span>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "io/BufferStream.h"
+#include "utils/ParsingUtils.h"
+
+namespace org::apache::nifi::minifi::core::repository {
+
+namespace {
+class EventCursor : public provenance::ProvenanceRepository::Cursor {
+ public:
+  explicit EventCursor(uint64_t last_event_ordinal): 
last_event_ordinal_(last_event_ordinal) {}
+
+  // the ordinal of the last event observed, 0 if no event has been read yet
+  [[nodiscard]]
+  std::string toString() const override {
+    return std::to_string(last_event_ordinal_);
+  }
+
+  uint64_t last_event_ordinal_;
+};
+}  // namespace
+
+std::unique_ptr<provenance::ProvenanceRepository::Cursor> 
VolatileProvenanceRepository::cursorFromString(std::string_view cursor_str) {
+  if (cursor_str.empty()) {
+    return std::make_unique<EventCursor>(0);
+  }
+  if (auto last_event_ordinal = parsing::parseIntegral<uint64_t>(cursor_str)) {
+    return std::make_unique<EventCursor>(*last_event_ordinal);
+  }
+  logger_->log_warn("Could not interpret provenance cursor '{}', reading from 
the first event", cursor_str);
+  return std::make_unique<EventCursor>(0);
+}
+
+std::expected<std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>>, 
std::string> VolatileProvenanceRepository::getEvents(size_t max_size, Cursor* 
cursor) {
+  auto* event_cursor = dynamic_cast<EventCursor*>(cursor);
+  if (cursor && !event_cursor) {
+    return std::unexpected{"Invalid cursor"};
+  }
+  if (max_size == 0) {
+    return {};
+  }
+  const uint64_t first_event_ordinal = event_cursor ? 
event_cursor->last_event_ordinal_ + 1 : 0;
+
+  // The entries are stored in no particular order, but each of them is keyed 
by the ordinal of
+  // the event it holds, so the events to be returned can be selected without 
deserializing all
+  // of them.
+  std::vector<std::pair<uint64_t, AtomicEntry<std::string>*>> candidates;
+  const auto event_ordinal_of = [] (const std::pair<uint64_t, 
AtomicEntry<std::string>*>& candidate) { return candidate.first; };
+  for (auto* entry : repo_data_.value_vector) {
+    entry->visitValue([&] (const std::string& key, std::span<const std::byte> 
/*buffer*/) {
+      if (auto event_ordinal = parsing::parseIntegral<uint64_t>(key); 
event_ordinal && *event_ordinal >= first_event_ordinal) {
+        candidates.emplace_back(*event_ordinal, entry);
+      }
+    });
+  }
+  std::ranges::sort(candidates, std::less<>{}, event_ordinal_of);
+  if (candidates.size() > max_size) {
+    candidates.resize(max_size);
+  }
+
+  std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>> records;
+  records.reserve(candidates.size());
+  for (const auto& [event_ordinal, entry] : candidates) {
+    auto event = provenance::ProvenanceEventRecord::create();
+    bool deserialized = false;
+    entry->visitValue([&] (const std::string& key, std::span<const std::byte> 
buffer) {
+      if (key != std::to_string(event_ordinal)) {
+        // the entry has been overwritten since it was selected
+        return;
+      }
+      io::BufferStream stream{buffer};
+      deserialized = event->deserialize(stream);
+    });
+    if (deserialized) {
+      records.push_back(std::move(event));
+    } else {
+      logger_->log_warn("Could not read provenance event '{}'", event_ordinal);
+    }
+  }
+  if (event_cursor && !candidates.empty()) {
+    // advanced even for the events we failed to read, otherwise they would 
stall the iteration
+    event_cursor->last_event_ordinal_ = candidates.back().first;
+  }
+
+  return records;
+}
+
+}  // namespace org::apache::nifi::minifi::core::repository
diff --git a/libminifi/src/core/repository/VolatileRepository.cpp 
b/libminifi/src/core/repository/VolatileRepository.cpp
index f08cad67a..fc237a1b9 100644
--- a/libminifi/src/core/repository/VolatileRepository.cpp
+++ b/libminifi/src/core/repository/VolatileRepository.cpp
@@ -78,7 +78,7 @@ bool VolatileRepository::Put(const std::string& key, const 
uint8_t *buf, size_t
   return true;
 }
 
-bool VolatileRepository::MultiPut(const std::vector<std::pair<std::string, 
std::unique_ptr<io::BufferStream>>>& data) {
+bool VolatileRepository::MultiPut(const EntryStreams& data) {
   for (const auto& item : data) {
     if (!Put(item.first, reinterpret_cast<const 
uint8_t*>(item.second->getBuffer().data()), item.second->size())) {
       return false;
diff --git a/libminifi/src/provenance/Provenance.cpp 
b/libminifi/src/provenance/Provenance.cpp
index 7a01638a3..7d0e80916 100644
--- a/libminifi/src/provenance/Provenance.cpp
+++ b/libminifi/src/provenance/Provenance.cpp
@@ -26,7 +26,6 @@
 #include <utility>
 
 #include "minifi-cpp/core/Repository.h"
-#include "io/BufferStream.h"
 #include "minifi-cpp/core/logging/Logger.h"
 #include "core/Relationship.h"
 #include "FlowController.h"
@@ -36,9 +35,6 @@ namespace org::apache::nifi::minifi::provenance {
 
 constexpr auto MAX_COMPONENT_NAME_LENGTH = 1_KiB;
 
-std::shared_ptr<utils::IdGenerator> ProvenanceEventRecordImpl::id_generator_ = 
utils::IdGenerator::getIdGenerator();
-std::shared_ptr<core::logging::Logger> ProvenanceEventRecordImpl::logger_ = 
core::logging::LoggerFactory<ProvenanceEventRecord>::getLogger();
-
 const char *ProvenanceEventRecord::ProvenanceEventTypeStr[REPLAY + 1] = { 
"CREATE", "RECEIVE", "FETCH", "SEND", "DOWNLOAD",  // 
NOLINT(cppcoreguidelines-avoid-c-arrays)
     "DROP", "EXPIRE", "FORK", "JOIN", "CLONE", "CONTENT_MODIFIED", 
"ATTRIBUTES_MODIFIED", "ROUTE", "ADDINFO", "REPLAY" };
 
@@ -50,36 +46,6 @@ 
ProvenanceEventRecordImpl::ProvenanceEventRecordImpl(ProvenanceEventRecord::Prov
       component_type_(std::move(component_type)) {
 }
 
-bool ProvenanceEventRecordImpl::loadFromRepository(const 
std::shared_ptr<core::Repository> &repo) {
-  std::string value;
-  bool ret = false;
-
-  if (nullptr == repo || uuid_.isNil()) {
-    logger_->log_error("Repo could not be assigned");
-    return false;
-  }
-  ret = repo->Get(getUUIDStr(), value);
-
-  if (!ret) {
-    logger_->log_error("NiFi Provenance Store event {} can not be found", 
getUUIDStr());
-    return false;
-  } else {
-    logger_->log_debug("NiFi Provenance Read event {}", getUUIDStr());
-  }
-
-  org::apache::nifi::minifi::io::BufferStream stream(value);
-
-  ret = deserialize(stream);
-
-  if (ret) {
-    logger_->log_debug("NiFi Provenance retrieve event {} size {} eventType {} 
success", getUUIDStr(), stream.size(), magic_enum::enum_name(event_type_));
-  } else {
-    logger_->log_debug("NiFi Provenance retrieve event {} size {} eventType {} 
fail", getUUIDStr(), stream.size(), magic_enum::enum_name(event_type_));
-  }
-
-  return ret;
-}
-
 bool ProvenanceEventRecordImpl::serialize(io::OutputStream& output_stream) {
   {
     const auto ret = output_stream.write(uuid_);
@@ -240,6 +206,13 @@ bool 
ProvenanceEventRecordImpl::serialize(io::OutputStream& output_stream) {
     }
   }
 
+  {
+    const auto ret = output_stream.write(event_ordinal_);
+    if (ret != 8) {
+      return false;
+    }
+  }
+
   return true;
 }
 
@@ -443,6 +416,17 @@ bool 
ProvenanceEventRecordImpl::deserialize(io::InputStream &input_stream) {
     }
   }
 
+  {
+    uint64_t event_ordinal = 0;
+    const auto ret = input_stream.read(event_ordinal);
+    if (ret != 8) {
+      // backwards compatibility to be able to deserialize older provenance 
events
+      event_ordinal_ = 0;
+    } else {
+      event_ordinal_ = event_ordinal;
+    }
+  }
+
   return true;
 }
 
@@ -452,19 +436,15 @@ void ProvenanceReporterImpl::commit() {
   }
 
   if (repo_->isFull()) {
-    logger_->log_debug("Provenance Repository is full");
+    logger_->log_error("Provenance Repository is full");
     return;
   }
 
-  std::vector<std::pair<std::string, std::unique_ptr<io::BufferStream>>> 
flowData;
-
-  for (auto& event : events_) {
-    auto stramptr = std::make_unique<io::BufferStream>();
-    event->serialize(*stramptr);
-
-    flowData.emplace_back(event->getUUIDStr(), std::move(stramptr));
+  if (auto append_result = repo_->appendEvents(events_); !append_result) {
+    // do not let failed provenance repo stop all processing
+    logger_->log_error("Failed to append provenance events: {}", 
append_result);
+    return;
   }
-  repo_->MultiPut(flowData);
 }
 
 void ProvenanceReporterImpl::create(const core::FlowFile& flow_file, const 
std::string& detail) {
@@ -534,19 +514,14 @@ void ProvenanceReporterImpl::drop(const core::FlowFile& 
flow_file, const std::st
   }
 }
 
-void ProvenanceReporterImpl::send(const core::FlowFile& flow_file, const 
std::string& transitUri, const std::string& detail, std::chrono::milliseconds 
processingDuration, bool force) {
+void ProvenanceReporterImpl::send(const core::FlowFile& flow_file, const 
std::string& transitUri, const std::string& detail, std::chrono::milliseconds 
processingDuration) {
   auto event = allocate(ProvenanceEventRecord::SEND, flow_file);
 
   if (event) {
     event->setTransitUri(transitUri);
     event->setDetails(detail);
     event->setEventDuration(processingDuration);
-    if (!force) {
-      add(event);
-    } else {
-      if (!repo_->isFull())
-        repo_->storeElement(event);
-    }
+    add(event);
   }
 }
 
diff --git a/libminifi/src/sitetosite/SiteToSiteClient.cpp 
b/libminifi/src/sitetosite/SiteToSiteClient.cpp
index 3871e666f..4586329d2 100644
--- a/libminifi/src/sitetosite/SiteToSiteClient.cpp
+++ b/libminifi/src/sitetosite/SiteToSiteClient.cpp
@@ -152,7 +152,7 @@ bool 
SiteToSiteClient::transferFlowFiles(core::ProcessContext& context, core::Pr
       auto end_time = std::chrono::steady_clock::now();
       std::string transit_uri = peer_->getURL() + "/" + flow->getUUIDStr();
       std::string details = "urn:nifi:" + flow->getUUIDStr() + "Remote Host=" 
+ peer_->getHostName();
-      session.getProvenanceReporter()->send(*flow, transit_uri, details, 
std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time), 
false);
+      session.getProvenanceReporter()->send(*flow, transit_uri, details, 
std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time));
       session.remove(flow);
 
       std::chrono::nanoseconds transfer_duration = 
std::chrono::high_resolution_clock::now() - transaction_started_at;
diff --git a/libminifi/test/flow-tests/SessionTests.cpp 
b/libminifi/test/flow-tests/SessionTests.cpp
index 533e3bf56..876a22375 100644
--- a/libminifi/test/flow-tests/SessionTests.cpp
+++ b/libminifi/test/flow-tests/SessionTests.cpp
@@ -62,7 +62,7 @@ TEST_CASE("Import null data") {
   config->set(minifi::Configure::nifi_dbcontent_repository_directory_default, 
(dir / "content_repository").string());
   config->set(minifi::Configure::nifi_flowfile_repository_directory_default, 
(dir / "flowfile_repository").string());
 
-  std::shared_ptr<core::Repository> prov_repo = 
core::createRepository("nooprepository");
+  std::shared_ptr prov_repo = 
utils::dynamic_unique_cast<minifi::provenance::ProvenanceRepository>(core::createRepository("nooprepository"));
   std::shared_ptr<core::Repository> ff_repository = 
std::make_shared<core::repository::FlowFileRepository>("flowFileRepository");
   std::shared_ptr<core::ContentRepository> content_repo;
   SECTION("VolatileContentRepository") {
diff --git a/libminifi/test/integration/C2PauseResumeTest.cpp 
b/libminifi/test/integration/C2PauseResumeTest.cpp
index b9e6efe15..39afd2bef 100644
--- a/libminifi/test/integration/C2PauseResumeTest.cpp
+++ b/libminifi/test/integration/C2PauseResumeTest.cpp
@@ -122,7 +122,7 @@ TEST_CASE("C2PauseResumeTest", "[c2test]") {
   VerifyC2PauseResume harness{test_file_path, flow_resumed_successfully};
   PauseResumeHandler responder{flow_resumed_successfully, 
harness.getConfiguration()};
 
-  std::shared_ptr<core::Repository> test_repo = 
std::make_shared<TestThreadedRepository>();
+  auto test_repo = std::make_shared<TestThreadedRepository>();
   std::shared_ptr<core::Repository> test_flow_repo = 
std::make_shared<TestFlowRepository>();
   std::shared_ptr<minifi::Configure> configuration = 
std::make_shared<minifi::ConfigureImpl>();
   configuration->set(minifi::Configure::nifi_flow_configuration_file, 
test_file_path.string());
diff --git a/libminifi/test/integration/ControllerServiceIntegrationTests.cpp 
b/libminifi/test/integration/ControllerServiceIntegrationTests.cpp
index d5586ca73..99f80b401 100644
--- a/libminifi/test/integration/ControllerServiceIntegrationTests.cpp
+++ b/libminifi/test/integration/ControllerServiceIntegrationTests.cpp
@@ -52,7 +52,7 @@ TEST_CASE("ControllerServiceIntegrationTests", 
"[controller]") {
   using org::apache::nifi::minifi::test::utils::verifyEventHappenedInPollTime;
   std::shared_ptr<minifi::Configure> configuration = 
std::make_shared<minifi::ConfigureImpl>();
 
-  std::shared_ptr<core::Repository> test_repo = 
std::make_shared<TestThreadedRepository>();
+  auto test_repo = std::make_shared<TestThreadedRepository>();
   std::shared_ptr<core::Repository> test_flow_repo = 
std::make_shared<TestFlowRepository>();
 
   const auto test_file_path = std::filesystem::path(TEST_RESOURCES) / 
"TestControllerServices.yml";
diff --git a/libminifi/test/libtest/integration/IntegrationBase.cpp 
b/libminifi/test/libtest/integration/IntegrationBase.cpp
index c825ef6d6..28c7bf6eb 100644
--- a/libminifi/test/libtest/integration/IntegrationBase.cpp
+++ b/libminifi/test/libtest/integration/IntegrationBase.cpp
@@ -59,7 +59,7 @@ void IntegrationBase::run() {
   using namespace std::literals::chrono_literals;
   testSetup();
 
-  std::shared_ptr<core::Repository> test_repo = 
std::make_shared<TestThreadedRepository>();
+  auto test_repo = std::make_shared<TestThreadedRepository>();
   std::shared_ptr<core::Repository> test_flow_repo = 
std::make_shared<TestFlowRepository>();
 
   if (flow_config_path_.config_path) {
diff --git a/libminifi/test/libtest/unit/ProvenanceTestHelper.h 
b/libminifi/test/libtest/unit/ProvenanceTestHelper.h
index 03d6e0c32..c1df15a1f 100644
--- a/libminifi/test/libtest/unit/ProvenanceTestHelper.h
+++ b/libminifi/test/libtest/unit/ProvenanceTestHelper.h
@@ -27,6 +27,7 @@
 #include <string>
 #include <utility>
 #include <vector>
+#include "fmt/format.h"
 #include "core/repository/VolatileContentRepository.h"
 #include "core/Processor.h"
 #include "core/ThreadedRepository.h"
@@ -41,7 +42,7 @@ using namespace std::literals::chrono_literals;
 const int64_t TEST_MAX_REPOSITORY_STORAGE_SIZE = 100;
 
 template <typename T_BaseRepository>
-class TestRepositoryBase : public T_BaseRepository {
+class TestRepositoryBase : public T_BaseRepository, public 
org::apache::nifi::minifi::provenance::ProvenanceRepository {
  public:
   TestRepositoryBase()
     : T_BaseRepository("repo_name", "./dir", 1s, 
TEST_MAX_REPOSITORY_STORAGE_SIZE, 0ms) {
@@ -89,11 +90,14 @@ class TestRepositoryBase : public T_BaseRepository {
     }
   }
 
-  
std::vector<std::shared_ptr<org::apache::nifi::minifi::core::SerializableComponent>>
 getElements(size_t max_size) override {
+  
std::expected<std::vector<std::shared_ptr<org::apache::nifi::minifi::provenance::ProvenanceEventRecord>>,
 std::string> getEvents(size_t max_size, Cursor* cursor) override {
+    if (cursor) {
+      return std::unexpected{"Cursor based query is not supported"};
+    }
     if (max_size == 0) {
       return {};
     }
-    
std::vector<std::shared_ptr<org::apache::nifi::minifi::core::SerializableComponent>>
 store;
+    
std::vector<std::shared_ptr<org::apache::nifi::minifi::provenance::ProvenanceEventRecord>>
 store;
     std::lock_guard<std::mutex> lock{repository_results_mutex_};
     for (const auto &entry : repository_results_) {
       const auto eventRead = 
org::apache::nifi::minifi::provenance::ProvenanceEventRecord::create();
@@ -113,9 +117,32 @@ class TestRepositoryBase : public T_BaseRepository {
     return repository_results_;
   }
 
+  std::expected<void, std::string> appendEvents(const 
std::vector<std::shared_ptr<org::apache::nifi::minifi::provenance::ProvenanceEventRecord>>&
 events) override {
+    std::vector<std::pair<std::string, 
std::unique_ptr<org::apache::nifi::minifi::io::BufferStream>>> data;
+    data.reserve(events.size());
+    std::lock_guard guard{next_event_key_mtx_};
+    for (auto& event : events) {
+      event->setEventOrdinal(next_event_key_++);
+      // zero padded so that the keys of the underlying map are ordered by the 
event ordinal
+      data.emplace_back(fmt::format("{:020}", event->getEventOrdinal()), 
std::make_unique<org::apache::nifi::minifi::io::BufferStream>());
+      event->serialize(*data.back().second);
+    }
+    if (!MultiPut(data)) {
+      return std::unexpected{"Failed to store provenance events"};
+    }
+
+    return {};
+  }
+
+  std::unique_ptr<ProvenanceRepository::Cursor> 
cursorFromString(std::string_view /*cursor_str*/) override {
+    return nullptr;
+  }
+
  protected:
   mutable std::mutex repository_results_mutex_;
   std::map<std::string, std::string> repository_results_;
+  std::mutex next_event_key_mtx_;
+  uint64_t next_event_key_{1};
 };
 
 class TestRepository : public 
TestRepositoryBase<org::apache::nifi::minifi::core::RepositoryImpl> {
diff --git a/libminifi/test/libtest/unit/TestBase.cpp 
b/libminifi/test/libtest/unit/TestBase.cpp
index 23a967ae9..e69fecbb9 100644
--- a/libminifi/test/libtest/unit/TestBase.cpp
+++ b/libminifi/test/libtest/unit/TestBase.cpp
@@ -208,8 +208,9 @@ LogTestController::LogTestController(const 
std::shared_ptr<logging::LoggerProper
   init(loggerProps);
 }
 
-TestPlan::TestPlan(std::shared_ptr<minifi::core::ContentRepository> 
content_repo, std::shared_ptr<minifi::core::Repository> flow_repo, 
std::shared_ptr<minifi::core::Repository> prov_repo,
-                   std::shared_ptr<minifi::state::response::FlowVersion> 
flow_version, std::shared_ptr<minifi::Configure> configuration, const char* 
state_dir)
+TestPlan::TestPlan(std::shared_ptr<minifi::core::ContentRepository> 
content_repo, std::shared_ptr<minifi::core::Repository> flow_repo,
+                   std::shared_ptr<minifi::provenance::ProvenanceRepository> 
prov_repo, std::shared_ptr<minifi::state::response::FlowVersion> flow_version,
+                   std::shared_ptr<minifi::Configure> configuration, const 
char* state_dir)
     : configuration_(std::move(configuration)),
       content_repo_(std::move(content_repo)),
       flow_repo_(std::move(flow_repo)),
@@ -637,7 +638,7 @@ std::shared_ptr<minifi::core::FlowFile> 
TestPlan::getFlowFileProducedByCurrentPr
   return nullptr;
 }
 
-std::set<std::shared_ptr<minifi::provenance::ProvenanceEventRecord>> 
TestPlan::getProvenanceRecords() {
+std::vector<std::shared_ptr<minifi::provenance::ProvenanceEventRecord>> 
TestPlan::getProvenanceRecords() {
   return process_sessions_.at(location)->getProvenanceReporter()->getEvents();
 }
 
diff --git a/libminifi/test/libtest/unit/TestBase.h 
b/libminifi/test/libtest/unit/TestBase.h
index d1e87f743..3bf8a31a1 100644
--- a/libminifi/test/libtest/unit/TestBase.h
+++ b/libminifi/test/libtest/unit/TestBase.h
@@ -243,8 +243,9 @@ class TypedProcessorWrapper {
 
 class TestPlan {
  public:
-  explicit TestPlan(std::shared_ptr<minifi::core::ContentRepository> 
content_repo, std::shared_ptr<minifi::core::Repository> flow_repo, 
std::shared_ptr<minifi::core::Repository> prov_repo,
-                    std::shared_ptr<minifi::state::response::FlowVersion> 
flow_version, std::shared_ptr<minifi::Configure> configuration, const char* 
state_dir);
+  explicit TestPlan(std::shared_ptr<minifi::core::ContentRepository> 
content_repo, std::shared_ptr<minifi::core::Repository> flow_repo,
+                    std::shared_ptr<minifi::provenance::ProvenanceRepository> 
prov_repo, std::shared_ptr<minifi::state::response::FlowVersion> flow_version,
+                    std::shared_ptr<minifi::Configure> configuration, const 
char* state_dir);
 
   virtual ~TestPlan();
 
@@ -305,7 +306,7 @@ class TestPlan {
   bool runCurrentProcessor();
   bool runCurrentProcessorUntilFlowfileIsProduced(std::chrono::milliseconds 
wait_duration);
 
-  std::set<std::shared_ptr<minifi::provenance::ProvenanceEventRecord>> 
getProvenanceRecords();
+  std::vector<std::shared_ptr<minifi::provenance::ProvenanceEventRecord>> 
getProvenanceRecords();
 
   std::shared_ptr<minifi::core::FlowFile> getCurrentFlowFile();
   std::vector<minifi::Connection*> 
getProcessorOutboundConnections(minifi::core::Processor* processor);
@@ -357,7 +358,7 @@ class TestPlan {
   std::shared_ptr<minifi::core::ContentRepository> content_repo_;
 
   std::shared_ptr<minifi::core::Repository> flow_repo_;
-  std::shared_ptr<minifi::core::Repository> prov_repo_;
+  std::shared_ptr<minifi::provenance::ProvenanceRepository> prov_repo_;
 
   std::shared_ptr<minifi::core::controller::ControllerServiceProvider> 
controller_services_provider_;
 
diff --git a/libminifi/test/libtest/unit/TestControllerWithFlow.cpp 
b/libminifi/test/libtest/unit/TestControllerWithFlow.cpp
index 26f5408b4..4d8b6988a 100644
--- a/libminifi/test/libtest/unit/TestControllerWithFlow.cpp
+++ b/libminifi/test/libtest/unit/TestControllerWithFlow.cpp
@@ -55,7 +55,7 @@ TestControllerWithFlow::TestControllerWithFlow(const char* 
yamlConfigContent, bo
 }
 
 void TestControllerWithFlow::setupFlow() {
-  std::shared_ptr<core::Repository> prov_repo = 
std::make_shared<TestThreadedRepository>();
+  auto prov_repo = std::make_shared<TestThreadedRepository>();
   std::shared_ptr<core::Repository> ff_repo = 
std::make_shared<TestFlowRepository>();
   std::shared_ptr<core::ContentRepository> content_repo = 
std::make_shared<core::repository::VolatileContentRepository>();
 
diff --git a/libminifi/test/persistence-tests/PersistenceTests.cpp 
b/libminifi/test/persistence-tests/PersistenceTests.cpp
index 11538a8f0..2dc659fde 100644
--- a/libminifi/test/persistence-tests/PersistenceTests.cpp
+++ b/libminifi/test/persistence-tests/PersistenceTests.cpp
@@ -60,8 +60,9 @@ class TestProcessor : public minifi::core::ProcessorImpl {
 };
 
 struct TestFlow{
-  TestFlow(const std::shared_ptr<core::Repository>& ff_repository, const 
std::shared_ptr<core::ContentRepository>& content_repo, const 
std::shared_ptr<core::Repository>& prov_repo,
-        const 
std::function<std::unique_ptr<core::Processor>(utils::Identifier&)>& 
processorGenerator, const core::Relationship& relationshipToOutput)
+  TestFlow(const std::shared_ptr<core::Repository>& ff_repository, const 
std::shared_ptr<core::ContentRepository>& content_repo,
+        const std::shared_ptr<minifi::provenance::ProvenanceRepository>& 
prov_repo, const 
std::function<std::unique_ptr<core::Processor>(utils::Identifier&)>& 
processorGenerator,
+        const core::Relationship& relationshipToOutput)
       : ff_repository(ff_repository), content_repo(content_repo), 
prov_repo(prov_repo) {
     // setup processor
     auto processor = processorGenerator(mainProcUUID());
@@ -178,7 +179,7 @@ TEST_CASE("Processors Can Store FlowFiles", "[TestP1]") {
   config->set(minifi::Configure::nifi_dbcontent_repository_directory_default, 
(dir / "content_repository").string());
   config->set(minifi::Configure::nifi_flowfile_repository_directory_default, 
(dir / "flowfile_repository").string());
 
-  std::shared_ptr<core::Repository> prov_repo = 
std::make_shared<TestThreadedRepository>();
+  auto prov_repo = std::make_shared<TestThreadedRepository>();
   auto ff_repository = 
std::make_shared<core::repository::FlowFileRepository>("flowFileRepository");
   std::shared_ptr<core::ContentRepository> content_repo = 
std::make_shared<core::repository::FileSystemRepository>();
   ff_repository->initialize(config);
@@ -292,7 +293,7 @@ TEST_CASE("Persisted flowFiles are updated on 
modification", "[TestP1]") {
   config->set(minifi::Configure::nifi_flowfile_repository_directory_default, 
(dir / "flowfile_repository").string());
   config->set(minifi::Configure::nifi_dbcontent_repository_purge_period, "0 
s");
 
-  std::shared_ptr<core::Repository> prov_repo = 
std::make_shared<TestThreadedRepository>();
+  auto prov_repo = std::make_shared<TestThreadedRepository>();
   std::shared_ptr<core::Repository> ff_repository = 
std::make_shared<core::repository::FlowFileRepository>("flowFileRepository");
   std::shared_ptr<core::ContentRepository> content_repo;
   SECTION("VolatileContentRepository") {
diff --git a/libminifi/test/unit/LogMetricsPublisherTests.cpp 
b/libminifi/test/unit/LogMetricsPublisherTests.cpp
index aa9765d64..429c8447c 100644
--- a/libminifi/test/unit/LogMetricsPublisherTests.cpp
+++ b/libminifi/test/unit/LogMetricsPublisherTests.cpp
@@ -50,8 +50,15 @@ class LogPublisherTestFixture {
 
   ~LogPublisherTestFixture() {
     publisher_.reset();  // explicit because LogTestController should outlive 
the thread in publisher_
-    minifi::utils::file::delete_dir(provenance_repo_->getDirectory());
-    minifi::utils::file::delete_dir(flow_file_repo_->getDirectory());
+    const auto provenance_repo_directory = provenance_repo_->getDirectory();
+    const auto flow_file_repo_directory = flow_file_repo_->getDirectory();
+    // the repositories have to be closed before their directories are 
removed, as the files of an
+    // open database cannot be deleted on every platform
+    response_node_loader_.reset();
+    provenance_repo_.reset();
+    flow_file_repo_.reset();
+    minifi::utils::file::delete_dir(provenance_repo_directory);
+    minifi::utils::file::delete_dir(flow_file_repo_directory);
     LogTestController::getInstance().reset();
   }
 
diff --git a/libminifi/test/unit/SchedulingAgentTests.cpp 
b/libminifi/test/unit/SchedulingAgentTests.cpp
index 0e087cf1a..8c2f0fee4 100644
--- a/libminifi/test/unit/SchedulingAgentTests.cpp
+++ b/libminifi/test/unit/SchedulingAgentTests.cpp
@@ -78,7 +78,7 @@ class SchedulingAgentTestFixture {
   }
 
  protected:
-  std::shared_ptr<core::Repository> test_repo_ = 
std::make_shared<TestThreadedRepository>();
+  std::shared_ptr<provenance::ProvenanceRepository> test_repo_ = 
std::make_shared<TestThreadedRepository>();
   std::shared_ptr<core::ContentRepository> content_repo_ = 
std::make_shared<core::repository::VolatileContentRepository>();
 
   TestController test_controller_;
diff --git a/libminifi/test/unit/VolatileProvenanceRepositoryTests.cpp 
b/libminifi/test/unit/VolatileProvenanceRepositoryTests.cpp
new file mode 100644
index 000000000..6a4f0566c
--- /dev/null
+++ b/libminifi/test/unit/VolatileProvenanceRepositoryTests.cpp
@@ -0,0 +1,169 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "core/repository/VolatileProvenanceRepository.h"
+#include "properties/Configure.h"
+#include "utils/StringUtils.h"
+#include "unit/TestBase.h"
+#include "unit/Catch.h"
+
+namespace org::apache::nifi::minifi::core::repository::test {
+
+std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>> 
createEvents(size_t count) {
+  std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>> events;
+  events.reserve(count);
+  for (size_t i = 0; i < count; ++i) {
+    events.push_back(provenance::ProvenanceEventRecord::create());
+  }
+  return events;
+}
+
+std::vector<uint64_t> getEventOrdinals(const 
std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>>& events) {
+  std::vector<uint64_t> ordinals;
+  ordinals.reserve(events.size());
+  for (const auto& event : events) {
+    ordinals.push_back(event->getEventOrdinal());
+  }
+  return ordinals;
+}
+
+std::shared_ptr<VolatileProvenanceRepository> createRepository(uint32_t 
max_count = 0) {
+  auto repository = std::make_shared<VolatileProvenanceRepository>();
+  std::shared_ptr<Configure> configuration;
+  if (max_count > 0) {
+    configuration = std::make_shared<ConfigureImpl>();
+    
configuration->set(utils::string::join_pack(Configure::nifi_volatile_repository_options,
 repository->getName(), ".", VOLATILE_REPO_MAX_COUNT), 
std::to_string(max_count));
+  }
+  REQUIRE(repository->initialize(configuration));
+  return repository;
+}
+
+TEST_CASE("VolatileProvenanceRepository assigns consecutive ordinals", 
"[volatileProvenanceRepository]") {
+  auto repository = createRepository();
+
+  auto first_batch = createEvents(3);
+  REQUIRE(repository->appendEvents(first_batch));
+  REQUIRE(getEventOrdinals(first_batch) == std::vector<uint64_t>{1, 2, 3});
+
+  auto second_batch = createEvents(2);
+  REQUIRE(repository->appendEvents(second_batch));
+  REQUIRE(getEventOrdinals(second_batch) == std::vector<uint64_t>{4, 5});
+}
+
+TEST_CASE("VolatileProvenanceRepository restarts the ordinals in a new 
instance", "[volatileProvenanceRepository]") {
+  auto events = createEvents(3);
+  {
+    auto repository = createRepository();
+    REQUIRE(repository->appendEvents(events));
+    REQUIRE(events.back()->getEventOrdinal() == 3);
+  }
+
+  // unlike the persistent repository, this one does not remember the ordinals 
it handed out
+  auto new_events = createEvents(1);
+  auto new_repository = createRepository();
+  REQUIRE(new_repository->appendEvents(new_events));
+  REQUIRE(new_events.front()->getEventOrdinal() == 1);
+}
+
+TEST_CASE("VolatileProvenanceRepository returns the events in ordinal order", 
"[volatileProvenanceRepository]") {
+  auto repository = createRepository();
+
+  // more than nine events, so that the ordinals do not all have the same 
number of digits
+  auto events = createEvents(12);
+  REQUIRE(repository->appendEvents(events));
+
+  auto cursor = repository->cursorFromString("");
+  REQUIRE(cursor);
+
+  auto queried_events = repository->getEvents(12, cursor.get());
+  REQUIRE(queried_events);
+  REQUIRE(queried_events->size() == 12);
+  REQUIRE(getEventOrdinals(*queried_events) == std::vector<uint64_t>{1, 2, 3, 
4, 5, 6, 7, 8, 9, 10, 11, 12});
+  for (size_t i = 0; i < queried_events->size(); ++i) {
+    REQUIRE(queried_events->at(i)->getEventId() == events.at(i)->getEventId());
+  }
+}
+
+TEST_CASE("VolatileProvenanceRepository continues the iteration from the 
cursor", "[volatileProvenanceRepository]") {
+  auto repository = createRepository();
+
+  REQUIRE(repository->appendEvents(createEvents(8)));
+
+  auto cursor = repository->cursorFromString("");
+  REQUIRE(cursor);
+
+  auto first_page = repository->getEvents(3, cursor.get());
+  REQUIRE(first_page);
+  REQUIRE(getEventOrdinals(*first_page) == std::vector<uint64_t>{1, 2, 3});
+  REQUIRE(cursor->toString() == "3");
+
+  // the cursor can be persisted and restored
+  cursor = repository->cursorFromString(cursor->toString());
+  REQUIRE(cursor);
+
+  auto second_page = repository->getEvents(3, cursor.get());
+  REQUIRE(second_page);
+  REQUIRE(getEventOrdinals(*second_page) == std::vector<uint64_t>{4, 5, 6});
+
+  // events appended after the cursor was created are picked up as well
+  REQUIRE(repository->appendEvents(createEvents(2)));
+
+  auto third_page = repository->getEvents(10, cursor.get());
+  REQUIRE(third_page);
+  REQUIRE(getEventOrdinals(*third_page) == std::vector<uint64_t>{7, 8, 9, 10});
+
+  auto fourth_page = repository->getEvents(10, cursor.get());
+  REQUIRE(fourth_page);
+  REQUIRE(fourth_page->empty());
+}
+
+TEST_CASE("VolatileProvenanceRepository does not consume the events it 
returns", "[volatileProvenanceRepository]") {
+  auto repository = createRepository();
+
+  REQUIRE(repository->appendEvents(createEvents(3)));
+
+  auto cursor = repository->cursorFromString("");
+  REQUIRE(cursor);
+  REQUIRE(getEventOrdinals(repository->getEvents(3, cursor.get()).value()) == 
std::vector<uint64_t>{1, 2, 3});
+
+  auto new_cursor = repository->cursorFromString("");
+  REQUIRE(new_cursor);
+  REQUIRE(getEventOrdinals(repository->getEvents(3, new_cursor.get()).value()) 
== std::vector<uint64_t>{1, 2, 3});
+}
+
+TEST_CASE("VolatileProvenanceRepository returns the events that survived an 
overflow", "[volatileProvenanceRepository]") {
+  auto repository = createRepository(4);
+
+  REQUIRE(repository->appendEvents(createEvents(6)));
+
+  auto queried_events = repository->getEvents(10, nullptr);
+  REQUIRE(queried_events);
+  // the oldest events have been overwritten, the remaining ones are still 
ordered
+  REQUIRE(queried_events->size() <= 4);
+  REQUIRE(!queried_events->empty());
+  auto ordinals = getEventOrdinals(*queried_events);
+  REQUIRE(std::ranges::is_sorted(ordinals));
+  REQUIRE(ordinals.back() == 6);
+}
+
+}  // namespace org::apache::nifi::minifi::core::repository::test
diff --git a/minifi-api/include/minifi-cpp/core/ProcessContext.h 
b/minifi-api/include/minifi-cpp/core/ProcessContext.h
index 7cecd92f1..a2dafc45c 100644
--- a/minifi-api/include/minifi-cpp/core/ProcessContext.h
+++ b/minifi-api/include/minifi-cpp/core/ProcessContext.h
@@ -29,6 +29,7 @@
 #include "minifi-cpp/core/StateStorage.h"
 #include "minifi-cpp/core/VariableRegistry.h"
 #include "minifi-cpp/core/controller/ControllerServiceHandle.h"
+#include "minifi-cpp/provenance/ProvenanceRepository.h"
 
 namespace org::apache::nifi::minifi::core {
 
@@ -79,7 +80,7 @@ class ProcessContext : public virtual core::VariableRegistry, 
public virtual uti
   virtual bool isRunning() const = 0;
   virtual bool isAutoTerminated(Relationship relationship) const = 0;
   virtual uint8_t getMaxConcurrentTasks() const = 0;
-  virtual std::shared_ptr<core::Repository> getProvenanceRepository() = 0;
+  virtual std::shared_ptr<provenance::ProvenanceRepository> 
getProvenanceRepository() = 0;
   virtual std::shared_ptr<core::ContentRepository> getContentRepository() 
const = 0;
   virtual std::shared_ptr<core::Repository> getFlowFileRepository() const = 0;
 
diff --git a/minifi-api/include/minifi-cpp/core/Repository.h 
b/minifi-api/include/minifi-cpp/core/Repository.h
index 33b5c3747..accfc695c 100644
--- a/minifi-api/include/minifi-cpp/core/Repository.h
+++ b/minifi-api/include/minifi-cpp/core/Repository.h
@@ -36,6 +36,8 @@ namespace org::apache::nifi::minifi::core {
 
 class Repository : public virtual core::CoreComponent, public virtual 
core::RepositoryMetricsSource {
  public:
+  using EntryStreams = std::vector<std::pair<std::string, 
std::unique_ptr<io::BufferStream>>>;
+
   virtual bool initialize(const std::shared_ptr<Configure> &configure) = 0;
   virtual bool start() = 0;
   virtual bool stop() = 0;
@@ -43,7 +45,7 @@ class Repository : public virtual core::CoreComponent, public 
virtual core::Repo
   virtual void flush() = 0;
 
   virtual bool Put(const std::string& /*key*/, const uint8_t* /*buf*/, size_t 
/*bufLen*/) = 0;
-  virtual bool MultiPut(const std::vector<std::pair<std::string, 
std::unique_ptr<io::BufferStream>>>& /*data*/) = 0;
+  virtual bool MultiPut(const EntryStreams& /*data*/) = 0;
   virtual bool Delete(const std::string& /*key*/) = 0;
   virtual bool Delete(const std::shared_ptr<core::CoreComponent>& item) = 0;
   virtual bool 
Delete(std::vector<std::shared_ptr<core::SerializableComponent>> &storedValues) 
= 0;
@@ -54,10 +56,6 @@ class Repository : public virtual core::CoreComponent, 
public virtual core::Repo
 
   virtual bool Get(const std::string& /*key*/, std::string& /*value*/) = 0;
 
-  virtual std::vector<std::shared_ptr<core::SerializableComponent>> 
getElements(size_t max_size) = 0;
-
-  virtual bool storeElement(const 
std::shared_ptr<core::SerializableComponent>& element) = 0;
-
   virtual void loadComponent(const std::shared_ptr<core::ContentRepository>& 
/*content_repo*/) = 0;
 
   virtual std::string getDirectory() const = 0;
diff --git a/minifi-api/include/minifi-cpp/provenance/Provenance.h 
b/minifi-api/include/minifi-cpp/provenance/Provenance.h
index ef396958a..66d88d510 100644
--- a/minifi-api/include/minifi-cpp/provenance/Provenance.h
+++ b/minifi-api/include/minifi-cpp/provenance/Provenance.h
@@ -148,6 +148,8 @@ class ProvenanceEventRecord : public virtual 
core::SerializableComponent {
 
   ~ProvenanceEventRecord() override = default;
 
+  virtual uint64_t getEventOrdinal() const = 0;
+  virtual void setEventOrdinal(uint64_t value) = 0;
   virtual utils::Identifier getEventId() const = 0;
   virtual void setEventId(const utils::Identifier &id) = 0;
   virtual std::map<std::string, std::string> getAttributes() const = 0;
@@ -183,7 +185,6 @@ class ProvenanceEventRecord : public virtual 
core::SerializableComponent {
   virtual std::string getSourceQueueIdentifier() const = 0;
   virtual void setSourceQueueIdentifier(const std::string& identifier) = 0;
   virtual void fromFlowFile(const core::FlowFile& flow_file) = 0;
-  virtual bool loadFromRepository(const std::shared_ptr<core::Repository> 
&repo) = 0;
 
   static std::shared_ptr<ProvenanceEventRecord> create();
 };
@@ -192,9 +193,8 @@ class ProvenanceReporter {
  public:
   virtual ~ProvenanceReporter() = default;
 
-  virtual std::set<std::shared_ptr<ProvenanceEventRecord>> getEvents() const = 
0;
+  virtual std::vector<std::shared_ptr<ProvenanceEventRecord>> getEvents() 
const = 0;
   virtual void add(const std::shared_ptr<ProvenanceEventRecord> &event) = 0;
-  virtual void remove(const std::shared_ptr<ProvenanceEventRecord> &event) = 0;
   virtual void clear() = 0;
 
   virtual void commit() = 0;
@@ -205,7 +205,7 @@ class ProvenanceReporter {
   virtual void clone(const core::FlowFile& parent, const core::FlowFile& 
child) = 0;
   virtual void expire(const core::FlowFile& flow_file, const std::string& 
detail) = 0;
   virtual void drop(const core::FlowFile& flow_file, const std::string& 
reason) = 0;
-  virtual void send(const core::FlowFile& flow_file, const std::string& 
transitUri, const std::string& detail, std::chrono::milliseconds 
processingDuration, bool force) = 0;
+  virtual void send(const core::FlowFile& flow_file, const std::string& 
transitUri, const std::string& detail, std::chrono::milliseconds 
processingDuration) = 0;
   virtual void fetch(const core::FlowFile& flow_file, const std::string& 
transitUri, const std::string& detail, std::chrono::milliseconds 
processingDuration) = 0;
   virtual void receive(const core::FlowFile& flow_file, const std::string& 
transitUri,
     const std::string& sourceSystemFlowFileIdentifier, const std::string& 
detail, std::chrono::milliseconds processingDuration) = 0;
diff --git a/minifi-api/include/minifi-cpp/provenance/ProvenanceRepository.h 
b/minifi-api/include/minifi-cpp/provenance/ProvenanceRepository.h
new file mode 100644
index 000000000..bf436b90c
--- /dev/null
+++ b/minifi-api/include/minifi-cpp/provenance/ProvenanceRepository.h
@@ -0,0 +1,44 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "minifi-cpp/core/Repository.h"
+#include "utils/Id.h"
+#include "minifi-cpp/provenance/Provenance.h"
+
+namespace org::apache::nifi::minifi::provenance {
+
+class ProvenanceRepository : public virtual core::Repository {
+ public:
+  class Cursor {
+   public:
+    [[nodiscard]]
+    virtual std::string toString() const = 0;
+    virtual ~Cursor() = default;
+  };
+
+  virtual std::expected<void, std::string> appendEvents(const 
std::vector<std::shared_ptr<ProvenanceEventRecord>>& events) = 0;
+
+  // if @cursor_str is the empty string it creates a cursor to the beginning 
if the underlying repository supports it
+  virtual std::unique_ptr<Cursor> cursorFromString(std::string_view 
cursor_str) = 0;
+
+  virtual 
std::expected<std::vector<std::shared_ptr<provenance::ProvenanceEventRecord>>, 
std::string> getEvents(size_t max_size, Cursor* cursor) = 0;
+};
+
+}  // namespace org::apache::nifi::minifi::provenance
diff --git a/minifi_main/MiNiFiMain.cpp b/minifi_main/MiNiFiMain.cpp
index 1516a22f8..53fa79d82 100644
--- a/minifi_main/MiNiFiMain.cpp
+++ b/minifi_main/MiNiFiMain.cpp
@@ -382,7 +382,7 @@ int main(int argc, char **argv) {
 
     configure->get(minifi::Configure::nifi_provenance_repository_class_name, 
prov_repo_class);
     // Create repos for flow record and provenance
-    std::shared_ptr prov_repo = core::createRepository(prov_repo_class, 
"provenance");
+    std::shared_ptr prov_repo = 
utils::dynamic_unique_cast<minifi::provenance::ProvenanceRepository>(core::createRepository(prov_repo_class,
 "provenance"));
 
     if (!prov_repo || !prov_repo->initialize(configure)) {
       logger->log_error("Provenance repository failed to initialize, 
exiting..");

Reply via email to