6#if !defined(SIGNET_ENABLE_COMMERCIAL) || !SIGNET_ENABLE_COMMERCIAL
7#error "signet/ai/human_oversight.hpp requires SIGNET_ENABLE_COMMERCIAL=ON (AGPL-3.0 commercial tier). See LICENSE_COMMERCIAL."
114 [[nodiscard]]
inline std::vector<uint8_t>
serialize()
const {
115 std::vector<uint8_t> buf;
121 append_le32(buf,
static_cast<uint32_t
>(
source));
122 append_le32(buf,
static_cast<uint32_t
>(
action));
129 append_le32(buf,
static_cast<uint32_t
>(
urgency));
135 const uint8_t* data,
size_t size) {
141 if (!read_string(data, size, offset, rec.
operator_id))
147 if (!read_le32(data, size, offset, src_val))
150 if (src_val < 0 || src_val > 2)
154 if (!read_le32(data, size, offset, act_val))
157 if (act_val < 0 || act_val > 4)
160 if (!read_string(data, size, offset, rec.
system_id))
170 if (!read_string(data, size, offset, rec.
rationale))
174 if (!read_le32(data, size, offset, urg_val))
184 static inline void append_le32(std::vector<uint8_t>& buf, uint32_t v) {
185 buf.push_back(
static_cast<uint8_t
>(v));
186 buf.push_back(
static_cast<uint8_t
>(v >> 8));
187 buf.push_back(
static_cast<uint8_t
>(v >> 16));
188 buf.push_back(
static_cast<uint8_t
>(v >> 24));
191 static inline void append_le64(std::vector<uint8_t>& buf, uint64_t v) {
192 for (
int i = 0; i < 8; ++i)
193 buf.push_back(
static_cast<uint8_t
>(v >> (i * 8)));
196 static inline void append_float(std::vector<uint8_t>& buf,
float v) {
198 std::memcpy(&bits, &v, 4);
199 append_le32(buf, bits);
202 static inline void append_string(std::vector<uint8_t>& buf,
const std::string& s) {
203 const size_t clamped = (std::min)(s.size(),
static_cast<size_t>(UINT32_MAX));
204 append_le32(buf,
static_cast<uint32_t
>(clamped));
205 buf.insert(buf.end(), s.begin(), s.begin() +
static_cast<ptrdiff_t
>(clamped));
210 static inline bool read_le64(
const uint8_t* data,
size_t size,
size_t& offset, int64_t& out) {
211 if (offset + 8 > size)
return false;
213 for (
int i = 0; i < 8; ++i)
214 v |=
static_cast<uint64_t
>(data[offset + i]) << (i * 8);
215 out =
static_cast<int64_t
>(v);
220 static inline bool read_le32(
const uint8_t* data,
size_t size,
size_t& offset, int32_t& out) {
221 if (offset + 4 > size)
return false;
223 for (
int i = 0; i < 4; ++i)
224 v |=
static_cast<uint32_t
>(data[offset + i]) << (i * 8);
225 out =
static_cast<int32_t
>(v);
230 static inline bool read_float(
const uint8_t* data,
size_t size,
size_t& offset,
float& out) {
231 if (offset + 4 > size)
return false;
233 for (
int i = 0; i < 4; ++i)
234 bits |=
static_cast<uint32_t
>(data[offset + i]) << (i * 8);
235 std::memcpy(&out, &bits, 4);
240 static inline bool read_string(
const uint8_t* data,
size_t size,
size_t& offset, std::string& out) {
242 if (!read_le32(data, size, offset, len))
return false;
243 if (len < 0)
return false;
244 auto ulen =
static_cast<size_t>(len);
245 if (offset + ulen > size)
return false;
246 out.assign(
reinterpret_cast<const char*
>(data + offset), ulen);
262 .column<std::string>(
"operator_id")
263 .
column<std::string>(
"operator_role")
264 .column<int32_t>(
"source")
265 .
column<int32_t>(
"action")
266 .column<std::string>(
"system_id")
267 .
column<std::string>(
"original_decision_id")
268 .column<std::string>(
"original_output")
269 .
column<
double>(
"original_confidence")
270 .column<std::string>(
"override_output")
271 .
column<std::string>(
"rationale")
272 .column<int32_t>(
"urgency")
273 .
column<int64_t>(
"chain_seq")
274 .column<std::string>(
"chain_hash")
275 .
column<std::string>(
"prev_hash")
276 .column<int64_t>(
"row_id")
277 .
column<int32_t>(
"row_version")
278 .column<std::string>(
"row_origin_file")
279 .
column<std::string>(
"row_prev_hash")
311 using AlertCallback = std::function<void(int64_t override_count, int64_t window_ns)>;
315 : opts_(std::move(opts)) {}
319 std::lock_guard<std::mutex> lock(mu_);
320 alert_cb_ = std::move(cb);
325 std::lock_guard<std::mutex> lock(mu_);
326 halt_cb_ = std::move(cb);
332 std::lock_guard<std::mutex> lock(mu_);
335 if (!timestamps_.empty() && timestamp_ns < timestamps_.back()) {
339 timestamps_.push_back(timestamp_ns);
340 evict_old(timestamp_ns);
342 auto count =
static_cast<int64_t
>(timestamps_.size());
350 "Override rate " + std::to_string(count) +
360 std::lock_guard<std::mutex> lock(mu_);
362 return static_cast<int64_t
>(timestamps_.size());
367 std::lock_guard<std::mutex> lock(mu_);
369 halt_cb_(reason, detail);
377 void evict_old(int64_t
now_ns) {
379 while (!timestamps_.empty() && timestamps_.front() < cutoff) {
380 timestamps_.pop_front();
384 OverrideRateMonitorOptions opts_;
385 std::deque<int64_t> timestamps_;
418 const std::string& chain_id =
"",
419 size_t max_records = 100000)
420 : output_dir_(output_dir)
422 , max_records_(max_records)
424 , lineage_tracker_(chain_id.empty() ? chain_id_ : chain_id, 1)
426 auto license = commercial::require_feature(
"HumanOverrideLogWriter");
428 throw std::runtime_error(license.error().message);
432 if (output_dir_.empty())
433 throw std::invalid_argument(
"HumanOverrideLogWriter: output_dir must not be empty");
434 for (
size_t s = 0, e; s <= output_dir_.size(); s = e + 1) {
435 e = output_dir_.find_first_of(
"/\\", s);
436 if (e == std::string::npos) e = output_dir_.size();
437 if (output_dir_.substr(s, e - s) ==
"..")
438 throw std::invalid_argument(
439 "HumanOverrideLogWriter: output_dir must not contain '..' path traversal");
442 for (
char c : chain_id_) {
443 if (!std::isalnum(
static_cast<unsigned char>(c)) && c !=
'_' && c !=
'-')
444 throw std::invalid_argument(
445 "HumanOverrideLogWriter: chain_id must only contain [a-zA-Z0-9_-]");
451 std::lock_guard<std::mutex> lock(write_mutex_);
452 auto usage = commercial::record_usage_rows(
"HumanOverrideLogWriter::log", 1);
453 if (!usage)
return usage.error();
458 if (ts == 0) ts =
now_ns();
460 auto entry = chain_.
append(data.data(), data.size(), ts);
462 pending_records_.push_back(record);
463 pending_entries_.push_back(entry);
464 pending_data_.push_back(std::move(data));
466 if (pending_records_.size() >= max_records_) {
467 auto result = flush_unlocked();
468 if (!result)
return result.error();
477 std::lock_guard<std::mutex> lock(write_mutex_);
478 return flush_unlocked();
483 std::lock_guard<std::mutex> lock(write_mutex_);
484 if (!pending_records_.empty()) {
485 return flush_unlocked();
492 if (pending_records_.empty()) {
496 int64_t start_seq = pending_entries_.front().sequence_number;
497 int64_t end_seq = pending_entries_.back().sequence_number;
499 current_file_path_ = output_dir_ +
"/human_override_log_" + chain_id_ +
"_"
500 + std::to_string(start_seq) +
"_"
501 + std::to_string(end_seq) +
".parquet";
504 opts.created_by =
"SignetStack signet-forge human_override_log v1.0";
507 auto meta_kvs = meta.to_key_values();
508 for (
auto& [k, v] : meta_kvs) {
509 opts.file_metadata.push_back(thrift::KeyValue(std::move(k), std::move(v)));
513 if (!writer_result)
return writer_result.error();
514 auto& writer = *writer_result;
516 size_t n = pending_records_.size();
517 for (
size_t i = 0; i < n; ++i) {
518 const auto& rec = pending_records_[i];
519 const auto& entry = pending_entries_[i];
520 const auto& row_data = pending_data_[i];
521 auto lineage = lineage_tracker_.
next(row_data.data(), row_data.size());
523 std::vector<std::string> row;
526 row.push_back(std::to_string(rec.timestamp_ns));
527 row.push_back(rec.operator_id);
528 row.push_back(rec.operator_role);
529 row.push_back(std::to_string(
static_cast<int32_t
>(rec.source)));
530 row.push_back(std::to_string(
static_cast<int32_t
>(rec.action)));
531 row.push_back(rec.system_id);
532 row.push_back(rec.original_decision_id);
533 row.push_back(rec.original_output);
534 row.push_back(double_to_string(
static_cast<double>(rec.original_confidence)));
535 row.push_back(rec.override_output);
536 row.push_back(rec.rationale);
537 row.push_back(std::to_string(rec.urgency));
538 row.push_back(std::to_string(entry.sequence_number));
541 row.push_back(std::to_string(lineage.row_id));
542 row.push_back(std::to_string(lineage.row_version));
543 row.push_back(lineage.row_origin_file);
544 row.push_back(lineage.row_prev_hash);
546 auto write_result = writer.write_row(row);
547 if (!write_result)
return write_result.error();
550 auto close_result = writer.close();
551 if (!close_result)
return close_result.error();
553 pending_records_.clear();
554 pending_entries_.clear();
555 pending_data_.clear();
558 return expected<void>{};
567 if (!pending_entries_.empty()) {
569 meta.
end_sequence = pending_entries_.back().sequence_number;
573 }
else if (!chain_.
entries().empty()) {
574 const auto& last = chain_.
entries().back();
581 meta.
record_count =
static_cast<int64_t
>(pending_entries_.size());
586 [[nodiscard]]
inline size_t pending_records()
const {
return pending_records_.size(); }
587 [[nodiscard]]
inline int64_t
total_records()
const {
return total_records_; }
591 std::string output_dir_;
592 std::string chain_id_;
596 std::vector<HumanOverrideRecord> pending_records_;
597 std::vector<HashChainEntry> pending_entries_;
598 std::vector<std::vector<uint8_t>> pending_data_;
600 std::string current_file_path_;
601 int64_t total_records_{0};
602 int64_t file_count_{0};
603 mutable std::mutex write_mutex_;
605 static inline std::string double_to_string(
double v) {
607 std::snprintf(buf,
sizeof(buf),
"%.17g", v);
625 auto license = commercial::require_feature(
"HumanOverrideLogReader");
626 if (!license)
return license.error();
629 if (!reader_result)
return reader_result.error();
632 hlr.reader_ = std::make_unique<ParquetReader>(std::move(*reader_result));
635 auto load_result = hlr.load_columns();
636 if (!load_result)
return load_result.
error();
652 size_t n = col_timestamp_ns_.size();
653 std::vector<HumanOverrideRecord> records;
656 for (
size_t i = 0; i < n; ++i) {
662 if (col_source_[i] < 0 || col_source_[i] > 2)
665 if (col_action_[i] < 0 || col_action_[i] > 4)
674 records.push_back(std::move(rec));
682 const auto& kvs = reader_->key_value_metadata();
685 for (
const auto& kv : kvs) {
686 if (!kv.value.has_value())
continue;
687 const auto& val = *kv.value;
689 if (kv.key ==
"signetstack.audit.chain_id") meta.
chain_id = val;
690 else if (kv.key ==
"signetstack.audit.first_seq") {
try { meta.
start_sequence = std::stoll(val); }
catch (...) {} }
691 else if (kv.key ==
"signetstack.audit.last_seq") {
try { meta.
end_sequence = std::stoll(val); }
catch (...) {} }
692 else if (kv.key ==
"signetstack.audit.first_hash") meta.
first_hash = val;
693 else if (kv.key ==
"signetstack.audit.last_hash") meta.
last_hash = val;
694 else if (kv.key ==
"signetstack.audit.prev_file_hash") meta.
prev_file_hash = val;
695 else if (kv.key ==
"signetstack.audit.record_count") {
try { meta.
record_count = std::stoll(val); }
catch (...) {} }
696 else if (kv.key ==
"signetstack.audit.record_type") meta.
record_type = val;
704 size_t n = col_timestamp_ns_.size();
707 empty_ok.
valid =
true;
709 empty_ok.
error_message =
"Empty file — no entries to verify";
714 std::vector<HashChainEntry> entries;
717 for (
size_t i = 0; i < n; ++i) {
729 bad.
error_message = !eh ?
"entry_hash deserialization failed at record "
731 :
"prev_hash deserialization failed at record "
744 if (col_source_[i] < 0 || col_source_[i] > 2)
747 if (col_action_[i] < 0 || col_action_[i] > 4)
760 entries.push_back(std::move(entry));
764 return verifier.
verify(entries);
768 [[nodiscard]]
inline size_t record_count()
const {
return col_timestamp_ns_.size(); }
771 [[nodiscard]]
inline const std::string&
path()
const {
return path_; }
774 std::unique_ptr<ParquetReader> reader_;
778 std::vector<int64_t> col_timestamp_ns_;
779 std::vector<std::string> col_operator_id_;
780 std::vector<std::string> col_operator_role_;
781 std::vector<int32_t> col_source_;
782 std::vector<int32_t> col_action_;
783 std::vector<std::string> col_system_id_;
784 std::vector<std::string> col_original_decision_id_;
785 std::vector<std::string> col_original_output_;
786 std::vector<double> col_original_confidence_;
787 std::vector<std::string> col_override_output_;
788 std::vector<std::string> col_rationale_;
789 std::vector<int32_t> col_urgency_;
790 std::vector<int64_t> col_chain_seq_;
791 std::vector<std::string> col_chain_hash_;
792 std::vector<std::string> col_prev_hash_;
793 std::vector<int64_t> col_row_id_;
794 std::vector<int32_t> col_row_version_;
795 std::vector<std::string> col_row_origin_file_;
796 std::vector<std::string> col_row_prev_hash_;
799 int64_t num_rgs = reader_->num_row_groups();
801 for (int64_t rg = 0; rg < num_rgs; ++rg) {
802 size_t rg_idx =
static_cast<size_t>(rg);
805 auto r0 = reader_->read_column<int64_t>(rg_idx, 0);
806 if (!r0)
return r0.
error();
807 col_timestamp_ns_.insert(col_timestamp_ns_.end(), r0->begin(), r0->end());
810 auto r1 = reader_->read_column<std::string>(rg_idx, 1);
811 if (!r1)
return r1.error();
812 col_operator_id_.insert(col_operator_id_.end(),
813 std::make_move_iterator(r1->begin()), std::make_move_iterator(r1->end()));
816 auto r2 = reader_->read_column<std::string>(rg_idx, 2);
817 if (!r2)
return r2.error();
818 col_operator_role_.insert(col_operator_role_.end(),
819 std::make_move_iterator(r2->begin()), std::make_move_iterator(r2->end()));
822 auto r3 = reader_->read_column<int32_t>(rg_idx, 3);
823 if (!r3)
return r3.error();
824 col_source_.insert(col_source_.end(), r3->begin(), r3->end());
827 auto r4 = reader_->read_column<int32_t>(rg_idx, 4);
828 if (!r4)
return r4.error();
829 col_action_.insert(col_action_.end(), r4->begin(), r4->end());
832 auto r5 = reader_->read_column<std::string>(rg_idx, 5);
833 if (!r5)
return r5.error();
834 col_system_id_.insert(col_system_id_.end(),
835 std::make_move_iterator(r5->begin()), std::make_move_iterator(r5->end()));
838 auto r6 = reader_->read_column<std::string>(rg_idx, 6);
839 if (!r6)
return r6.error();
840 col_original_decision_id_.insert(col_original_decision_id_.end(),
841 std::make_move_iterator(r6->begin()), std::make_move_iterator(r6->end()));
844 auto r7 = reader_->read_column<std::string>(rg_idx, 7);
845 if (!r7)
return r7.error();
846 col_original_output_.insert(col_original_output_.end(),
847 std::make_move_iterator(r7->begin()), std::make_move_iterator(r7->end()));
850 auto r8 = reader_->read_column<
double>(rg_idx, 8);
851 if (!r8)
return r8.error();
852 col_original_confidence_.insert(col_original_confidence_.end(), r8->begin(), r8->end());
855 auto r9 = reader_->read_column<std::string>(rg_idx, 9);
856 if (!r9)
return r9.error();
857 col_override_output_.insert(col_override_output_.end(),
858 std::make_move_iterator(r9->begin()), std::make_move_iterator(r9->end()));
861 auto r10 = reader_->read_column<std::string>(rg_idx, 10);
862 if (!r10)
return r10.error();
863 col_rationale_.insert(col_rationale_.end(),
864 std::make_move_iterator(r10->begin()), std::make_move_iterator(r10->end()));
867 auto r11 = reader_->read_column<int32_t>(rg_idx, 11);
868 if (!r11)
return r11.error();
869 col_urgency_.insert(col_urgency_.end(), r11->begin(), r11->end());
872 auto r12 = reader_->read_column<int64_t>(rg_idx, 12);
873 if (!r12)
return r12.error();
874 col_chain_seq_.insert(col_chain_seq_.end(), r12->begin(), r12->end());
877 auto r13 = reader_->read_column<std::string>(rg_idx, 13);
878 if (!r13)
return r13.error();
879 col_chain_hash_.insert(col_chain_hash_.end(),
880 std::make_move_iterator(r13->begin()), std::make_move_iterator(r13->end()));
883 auto r14 = reader_->read_column<std::string>(rg_idx, 14);
884 if (!r14)
return r14.error();
885 col_prev_hash_.insert(col_prev_hash_.end(),
886 std::make_move_iterator(r14->begin()), std::make_move_iterator(r14->end()));
889 if (reader_->schema().num_columns() > 15) {
890 auto r15 = reader_->read_column<int64_t>(rg_idx, 15);
891 if (!r15)
return r15.error();
892 col_row_id_.insert(col_row_id_.end(), r15->begin(), r15->end());
896 if (reader_->schema().num_columns() > 16) {
897 auto r16 = reader_->read_column<int32_t>(rg_idx, 16);
898 if (!r16)
return r16.error();
899 col_row_version_.insert(col_row_version_.end(), r16->begin(), r16->end());
903 if (reader_->schema().num_columns() > 17) {
904 auto r17 = reader_->read_column<std::string>(rg_idx, 17);
905 if (!r17)
return r17.error();
906 col_row_origin_file_.insert(col_row_origin_file_.end(),
907 std::make_move_iterator(r17->begin()), std::make_move_iterator(r17->end()));
911 if (reader_->schema().num_columns() > 18) {
912 auto r18 = reader_->read_column<std::string>(rg_idx, 18);
913 if (!r18)
return r18.error();
914 col_row_prev_hash_.insert(col_row_prev_hash_.end(),
915 std::make_move_iterator(r18->begin()), std::make_move_iterator(r18->end()));
918 return expected<void>{};
Verifies hash chain integrity.
static VerificationResult verify(const uint8_t *chain_data, size_t chain_size)
Verify a chain from serialized bytes.
Builds SHA-256 hash chains during Parquet writes.
const std::vector< HashChainEntry > & entries() const
Return a const reference to the internal entry list.
HashChainEntry append(const uint8_t *record_data, size_t record_size, int64_t timestamp_ns)
Append a record to the chain with an explicit timestamp.
Reads human override log Parquet files and verifies hash chain integrity.
expected< std::vector< HumanOverrideRecord > > read_all() const
Get all override records from the file.
const std::string & path() const
Get the file path.
HumanOverrideLogReader()=default
HumanOverrideLogReader(HumanOverrideLogReader &&)=default
HumanOverrideLogReader & operator=(HumanOverrideLogReader &&)=default
expected< AuditMetadata > audit_metadata() const
Get the audit chain metadata from the Parquet file's key-value metadata.
size_t record_count() const
Get number of records in the file.
HumanOverrideLogReader & operator=(const HumanOverrideLogReader &)=delete
static expected< HumanOverrideLogReader > open(const std::string &path)
HumanOverrideLogReader(const HumanOverrideLogReader &)=delete
AuditChainVerifier::VerificationResult verify_chain() const
Verify the hash chain integrity.
Writes human override events to Parquet files with cryptographic hash chaining for tamper-evident aud...
expected< void > flush()
Flush current records to a Parquet file.
size_t pending_records() const
expected< HashChainEntry > log(const HumanOverrideRecord &record)
Log a human override event. Returns the hash chain entry.
expected< void > close()
Close the writer (flushes remaining records).
std::string current_file_path() const
int64_t total_records() const
HumanOverrideLogWriter(const std::string &output_dir, const std::string &chain_id="", size_t max_records=100000)
Create a human override log writer.
AuditMetadata current_metadata() const
Get the chain metadata for the current batch.
Sliding-window override rate monitor — EU AI Act Art.14(5).
int64_t current_count(int64_t now_ns)
Get the current override count within the window.
std::function< void(int64_t override_count, int64_t window_ns)> AlertCallback
void trigger_halt(HaltReason reason, const std::string &detail="")
Manually trigger a system halt (Art.14(4) "stop button").
const OverrideRateMonitorOptions & options() const noexcept
Get the configured options.
void set_alert_callback(AlertCallback cb)
Register a callback for when override rate exceeds threshold.
std::function< void(HaltReason reason, const std::string &detail)> HaltCallback
int64_t record_override(int64_t timestamp_ns)
Record an override event at the given timestamp.
void set_halt_callback(HaltCallback cb)
Register a callback for system halt requests.
OverrideRateMonitor(OverrideRateMonitorOptions opts={})
static expected< ParquetReader > open(const std::filesystem::path &path)
Open and parse a Parquet file, returning a ready-to-query reader.
static expected< ParquetWriter > open(const std::filesystem::path &path, const Schema &schema, const Options &options=Options{})
Open a new Parquet file for writing.
Per-row lineage tracking inspired by Iceberg V3-style data governance.
RowLineage next(const uint8_t *row_data, size_t row_size)
Generate lineage for the next row.
SchemaBuilder & column(std::string col_name, LogicalType logical_type=LogicalType::NONE)
Add a typed column, deducing PhysicalType from T.
Immutable schema description for a Parquet file.
static SchemaBuilder builder(std::string name)
Create a SchemaBuilder for fluent column construction.
const Error & error() const
Access the error payload (valid for both success and failure; check ok() on the returned Error).
A lightweight result type that holds either a success value of type T or an Error.
const Error & error() const
Access the error payload.
std::array< uint8_t, 32 > sha256(const uint8_t *data, size_t size)
Compute SHA-256 hash of arbitrary-length input.
int64_t now_ns()
Return the current time as nanoseconds since the Unix epoch (UTC).
expected< std::array< uint8_t, 32 > > hex_to_hash(const std::string &hex)
Convert a 64-character lowercase hex string back to a 32-byte hash.
Schema human_override_log_schema()
Build the Parquet schema for human override log files.
HaltReason
Reason for system halt — EU AI Act Art.14(4) "stop button".
@ EXTERNAL
External event (market halt, circuit breaker)
@ ANOMALY_DETECTED
Anomalous behavior detected.
@ REGULATORY
Regulatory or compliance-driven halt.
@ SAFETY_THRESHOLD
Override rate exceeded safety threshold.
@ MAINTENANCE
Scheduled maintenance halt.
@ MANUAL
Operator manually halted the system.
OverrideAction
What action the human override took — EU AI Act Art.14(4).
@ ESCALATE
Human escalated to a higher authority.
@ REJECT
Human rejected the AI system's output entirely.
@ MODIFY
Human modified the AI system's output.
@ HALT
Human triggered system halt ("stop button")
@ APPROVE
Human approved the AI system's output as-is.
@ TIMESTAMP_NS
Timestamp — INT64, nanoseconds since Unix epoch.
std::string hash_to_hex(const std::array< uint8_t, 32 > &hash)
Convert a 32-byte SHA-256 hash to a lowercase hexadecimal string (64 chars).
@ CORRUPT_PAGE
A data page failed integrity checks (bad CRC, truncated, or exceeds size limits).
OverrideSource
Source of a decision or override — EU AI Act Art.14(4).
@ AUTOMATED
Automated safety system override (e.g. risk gate)
@ HUMAN
Human operator override.
@ ALGORITHMIC
Original AI system output (no human intervention)
std::string generate_chain_id()
Generate a simple chain identifier based on the current timestamp.
Per-row lineage tracking (Iceberg V3-style) with monotonic row IDs, mutation versioning,...
Schema definition types: Column<T>, SchemaBuilder, and Schema.
Result of a full chain verification.
bool valid
True if the entire chain passed all integrity checks.
int64_t first_bad_index
Index of the first entry that failed verification, or -1 if all entries are valid.
int64_t entries_checked
Number of entries that were successfully verified before a failure was detected (or the total count i...
std::string error_message
Human-readable description of the verification outcome.
Lightweight error value carrying an ErrorCode and a human-readable message.
A single link in the cryptographic hash chain.
int64_t sequence_number
0-indexed position in the chain, monotonically increasing.
std::array< uint8_t, 32 > entry_hash
SHA-256 commitment over (sequence_number, timestamp_ns, prev_hash, data_hash).
int64_t timestamp_ns
Nanoseconds since Unix epoch when this entry was created.
std::array< uint8_t, 32 > prev_hash
SHA-256 hash of the previous entry (all zeros for the first entry, or a user-supplied continuation ha...
std::array< uint8_t, 32 > data_hash
SHA-256 hash of the record/row data that this entry covers.
A single human oversight event with full provenance.
std::string rationale
Human-provided reason for the override (Art.14(5))
std::string system_id
AI system identifier (matches ReportOptions::system_id)
int32_t urgency
Override urgency level (0=routine, 1=elevated, 2=critical)
int64_t timestamp_ns
When the override occurred (ns since epoch)
std::string operator_role
Operator role (e.g. "trader", "risk_officer", "supervisor")
std::string operator_id
Human operator identifier (pseudonymised per GDPR Art.25)
OverrideSource source
Who initiated this action.
std::vector< uint8_t > serialize() const
float original_confidence
AI system's confidence in the original output.
std::string original_output
String representation of the AI system's original output.
static expected< HumanOverrideRecord > deserialize(const uint8_t *data, size_t size)
std::string override_output
The human's replacement output (if action == MODIFY)
OverrideAction action
What action was taken.
std::string original_decision_id
Reference to the DecisionRecord/order_id being overridden.
Options for the override rate monitor.
bool auto_halt_on_threshold
If true, automatically fire the halt callback when threshold is exceeded.
int64_t alert_threshold
Override rate threshold (overrides per window) that triggers an alert.
int64_t window_ns
Sliding window duration for rate calculation (nanoseconds).
Parquet format enumerations, type traits, and statistics structs.