///|
/// Reproducibility and provenance records for application runs.
/// Audit events contain no credentials and are suitable for a local export or
/// a structured log sink supplied by the host application.
pub(all) enum AuditEventKind {
AuditInputReceived
AuditInputRejected
AuditNormalization
AuditQualityGate
AuditAnalysis
AuditDecision
AuditExport
AuditWarning
AuditCompletion
} derive(FromJson, ToJson, Debug, Eq)
///|
pub(all) enum AuditOutcome {
AuditSuccess
AuditAcceptedWithWarning
AuditRejected
AuditError
} derive(FromJson, ToJson, Debug, Eq)
///|
pub(all) struct AuditEvent {
ordinal : Int
run_id : String
timestamp : String
kind : AuditEventKind
outcome : AuditOutcome
component : String
message : String
input_count : Int
output_count : Int
quality_score : Double
checksum : String
} derive(FromJson, ToJson, Debug, Eq)
///|
pub(all) struct AuditTrail {
run_id : String
started_at : String
mut finished_at : String
events : Array[AuditEvent]
mut outcome : AuditOutcome
input_checksum : String
mut output_checksum : String
mut feature_count : Int
} derive(FromJson, ToJson, Debug, Eq)
///|
pub(all) struct AuditFilter {
run_id : String?
component : String?
kind : AuditEventKind?
outcome : AuditOutcome?
minimum_quality : Double
} derive(FromJson, ToJson, Debug, Eq)
///|
pub(all) struct AuditSummary {
event_count : Int
success_count : Int
warning_count : Int
rejection_count : Int
error_count : Int
component_count : Int
mean_quality : Double
last_outcome : AuditOutcome
} derive(FromJson, ToJson, Debug, Eq)
///|
pub fn audit_kind_name(kind : AuditEventKind) -> String {
match kind {
AuditInputReceived => "input_received"
AuditInputRejected => "input_rejected"
AuditNormalization => "normalization"
AuditQualityGate => "quality_gate"
AuditAnalysis => "analysis"
AuditDecision => "decision"
AuditExport => "export"
AuditWarning => "warning"
AuditCompletion => "completion"
}
}
///|
pub fn audit_outcome_name(outcome : AuditOutcome) -> String {
match outcome {
AuditSuccess => "success"
AuditAcceptedWithWarning => "accepted_with_warning"
AuditRejected => "rejected"
AuditError => "error"
}
}
///|
fn audit_outcome_rank(outcome : AuditOutcome) -> Int {
match outcome {
AuditSuccess => 0
AuditAcceptedWithWarning => 1
AuditRejected => 2
AuditError => 3
}
}
///|
fn audit_bound(value : Double) -> Double {
if value.is_nan() || value.is_inf() {
0.0
} else {
value.clamp(min=0.0, max=1.0)
}
}
///|
fn audit_unique(values : Array[String]) -> Array[String] {
let result = []
for value in values {
if !result.any(item => item == value) {
result.push(value)
}
}
result
}
///|
pub fn make_audit_event(
ordinal : Int,
run_id : String,
timestamp : String,
kind : AuditEventKind,
outcome : AuditOutcome,
component : String,
message : String,
input_count : Int,
output_count : Int,
quality_score : Double,
checksum : String,
) -> AuditEvent {
{
ordinal: ordinal.max(0),
run_id,
timestamp,
kind,
outcome,
component,
message,
input_count: input_count.max(0),
output_count: output_count.max(0),
quality_score: audit_bound(quality_score),
checksum,
}
}
///|
pub fn AuditTrail::new(
run_id : String,
started_at : String,
input_checksum : String,
) -> AuditTrail {
{
run_id,
started_at,
finished_at: "",
events: [],
outcome: AuditSuccess,
input_checksum,
output_checksum: "",
feature_count: 0,
}
}
///|
pub fn AuditTrail::append(
self : AuditTrail,
kind : AuditEventKind,
outcome : AuditOutcome,
component : String,
message : String,
input_count : Int,
output_count : Int,
quality_score : Double,
checksum : String,
timestamp : String,
) -> Unit {
let event = make_audit_event(
self.events.length(),
self.run_id,
timestamp,
kind,
outcome,
component,
message,
input_count,
output_count,
quality_score,
checksum,
)
self.events.push(event)
if audit_outcome_rank(outcome) > audit_outcome_rank(self.outcome) {
self.outcome = outcome
}
}
///|
pub fn AuditTrail::finish(
self : AuditTrail,
finished_at : String,
output_checksum : String,
feature_count : Int,
) -> Unit {
self.finished_at = finished_at
self.output_checksum = output_checksum
self.feature_count = feature_count.max(0)
self.append(
AuditCompletion,
self.outcome,
"audit",
"run completed",
self.events.length(),
self.feature_count,
mean_value(self.events.map(event => event.quality_score)),
output_checksum,
finished_at,
)
}
///|
pub fn AuditTrail::event_count(self : AuditTrail) -> Int {
self.events.length()
}
///|
pub fn AuditTrail::has_errors(self : AuditTrail) -> Bool {
self.outcome == AuditError ||
self.events.any(event => event.outcome == AuditError)
}
///|
pub fn AuditTrail::has_warnings(self : AuditTrail) -> Bool {
self.outcome == AuditAcceptedWithWarning ||
self.events.any(event => {
event.outcome == AuditAcceptedWithWarning || event.kind == AuditWarning
})
}
///|
pub fn AuditTrail::is_reproducible(self : AuditTrail) -> Bool {
self.run_id.length() > 0 &&
self.input_checksum.length() > 0 &&
self.output_checksum.length() > 0 &&
self.feature_count >= 0 &&
self.events.length() > 0
}
///|
fn audit_matches(event : AuditEvent, filter : AuditFilter) -> Bool {
let run_ok = match filter.run_id {
Some(value) => event.run_id == value
None => true
}
let component_ok = match filter.component {
Some(value) => event.component == value
None => true
}
let kind_ok = match filter.kind {
Some(value) => event.kind == value
None => true
}
let outcome_ok = match filter.outcome {
Some(value) => event.outcome == value
None => true
}
run_ok &&
component_ok &&
kind_ok &&
outcome_ok &&
event.quality_score >= filter.minimum_quality
}
///|
pub fn audit_filter_all() -> AuditFilter {
{
run_id: None,
component: None,
kind: None,
outcome: None,
minimum_quality: 0.0,
}
}
///|
pub fn audit_filter_component(component : String) -> AuditFilter {
{
run_id: None,
component: Some(component),
kind: None,
outcome: None,
minimum_quality: 0.0,
}
}
///|
pub fn audit_query(
trails : Array[AuditTrail],
filter : AuditFilter,
) -> Array[AuditEvent] {
let result = []
for trail in trails {
for event in trail.events {
if audit_matches(event, filter) {
result.push(event)
}
}
}
result.sort_by((left, right) => left.ordinal - right.ordinal)
result
}
///|
pub fn audit_summary(events : Array[AuditEvent]) -> AuditSummary {
let components = audit_unique(events.map(event => event.component))
let qualities = events.map(event => event.quality_score)
let mut last = AuditSuccess
for event in events {
if audit_outcome_rank(event.outcome) > audit_outcome_rank(last) {
last = event.outcome
}
}
{
event_count: events.length(),
success_count: events
.filter(event => event.outcome == AuditSuccess)
.length(),
warning_count: events
.filter(event => {
event.outcome == AuditAcceptedWithWarning || event.kind == AuditWarning
})
.length(),
rejection_count: events
.filter(event => event.outcome == AuditRejected)
.length(),
error_count: events.filter(event => event.outcome == AuditError).length(),
component_count: components.length(),
mean_quality: mean_value(qualities),
last_outcome: last,
}
}
///|
pub fn audit_trails_summary(trails : Array[AuditTrail]) -> AuditSummary {
let events = []
for trail in trails {
for event in trail.events {
events.push(event)
}
}
audit_summary(events)
}
///|
pub fn audit_event_row(event : AuditEvent) -> Array[String] {
[
event.ordinal.to_string(),
event.run_id,
event.timestamp,
audit_kind_name(event.kind),
audit_outcome_name(event.outcome),
event.component,
event.message,
event.input_count.to_string(),
event.output_count.to_string(),
event.quality_score.to_string(),
event.checksum,
]
}
///|
pub fn audit_events_csv(events : Array[AuditEvent]) -> String {
let grid = [
[
"ordinal", "run_id", "timestamp", "kind", "outcome", "component", "message",
"input_count", "output_count", "quality_score", "checksum",
],
]
for event in events {
grid.push(audit_event_row(event))
}
to_csv(grid)
}
///|
pub fn audit_trail_csv(trail : AuditTrail) -> String {
audit_events_csv(trail.events)
}
///|
pub fn audit_summary_csv(summary : AuditSummary) -> String {
to_csv([
[
"event_count", "success_count", "warning_count", "rejection_count", "error_count",
"component_count", "mean_quality", "last_outcome",
],
[
summary.event_count.to_string(),
summary.success_count.to_string(),
summary.warning_count.to_string(),
summary.rejection_count.to_string(),
summary.error_count.to_string(),
summary.component_count.to_string(),
summary.mean_quality.to_string(),
audit_outcome_name(summary.last_outcome),
],
])
}
///|
pub fn audit_trail_feature_vector(trail : AuditTrail) -> Array[Double] {
let summary = audit_summary(trail.events)
[
trail.events.length().to_double(),
summary.success_count.to_double(),
summary.warning_count.to_double(),
summary.rejection_count.to_double(),
summary.error_count.to_double(),
summary.mean_quality,
trail.feature_count.to_double(),
if trail.is_reproducible() {
1.0
} else {
0.0
},
]
}
///|
pub fn audit_trail_status_message(trail : AuditTrail) -> String {
match trail.outcome {
AuditSuccess => "run completed successfully"
AuditAcceptedWithWarning => "run completed with warnings"
AuditRejected => "run was rejected by a quality or policy gate"
AuditError => "run encountered an error"
}
}
///|
pub fn audit_latest_event(trail : AuditTrail) -> AuditEvent? {
if trail.events.length() == 0 {
None
} else {
Some(trail.events[trail.events.length() - 1])
}
}
///|
pub fn audit_event_for_component(
trail : AuditTrail,
component : String,
) -> Array[AuditEvent] {
trail.events.filter(event => event.component == component)
}
///|
pub fn audit_quality_floor_events(
trail : AuditTrail,
floor : Double,
) -> Array[AuditEvent] {
trail.events.filter(event => event.quality_score < floor)
}
///|
pub fn audit_is_complete(trail : AuditTrail) -> Bool {
trail.events.any(event => event.kind == AuditCompletion) &&
trail.finished_at.length() > 0
}
///|
pub fn audit_has_input_event(trail : AuditTrail) -> Bool {
trail.events.any(event => event.kind == AuditInputReceived)
}
///|
pub fn audit_has_decision_event(trail : AuditTrail) -> Bool {
trail.events.any(event => event.kind == AuditDecision)
}
///|
pub fn audit_merge(left : AuditTrail, right : AuditTrail) -> AuditTrail {
let result = AuditTrail::new(
if left.run_id.length() > 0 {
left.run_id
} else {
right.run_id
},
if left.started_at.length() > 0 {
left.started_at
} else {
right.started_at
},
if left.input_checksum.length() > 0 {
left.input_checksum
} else {
right.input_checksum
},
)
for event in left.events {
result.events.push(event)
}
for event in right.events {
result.events.push(event)
}
result.events.sort_by((a, b) => a.ordinal - b.ordinal)
result.outcome = if audit_outcome_rank(left.outcome) >=
audit_outcome_rank(right.outcome) {
left.outcome
} else {
right.outcome
}
result.finished_at = if right.finished_at.length() > 0 {
right.finished_at
} else {
left.finished_at
}
result.output_checksum = if right.output_checksum.length() > 0 {
right.output_checksum
} else {
left.output_checksum
}
result.feature_count = left.feature_count.max(right.feature_count)
result
}