///|
priv struct AuditCursor {
resource : String
token : Int
revision : Int
at : Int
}
///|
fn find_cursor(cursors : Array[AuditCursor], resource : String) -> Int {
for index, cursor in cursors {
if cursor.resource == resource {
return index
}
}
-1
}
///|
/// Checks monotonic token, revision, and logical-time invariants.
pub fn audit_events(events : Array[LeaseEvent]) -> AuditReport {
let cursors : Array[AuditCursor] = []
let findings : Array[AuditFinding] = []
for index, event in events {
if event.token <= 0 {
findings.push({
code: "invalid_token",
resource: event.resource,
event_index: index,
message: "fencing token must be positive",
})
}
if event.revision <= 0 {
findings.push({
code: "invalid_revision",
resource: event.resource,
event_index: index,
message: "revision must be positive",
})
}
let cursor_index = find_cursor(cursors, event.resource)
if cursor_index < 0 {
cursors.push({
resource: event.resource,
token: event.token,
revision: event.revision,
at: event.at,
})
} else {
let cursor = cursors[cursor_index]
if event.token < cursor.token {
findings.push({
code: "token_regression",
resource: event.resource,
event_index: index,
message: "fencing token moved backwards",
})
}
if event.revision <= cursor.revision {
findings.push({
code: "revision_regression",
resource: event.resource,
event_index: index,
message: "revision did not increase",
})
}
if event.at < cursor.at {
findings.push({
code: "time_regression",
resource: event.resource,
event_index: index,
message: "logical event time moved backwards",
})
}
cursors[cursor_index] = {
resource: event.resource,
token: if event.token > cursor.token {
event.token
} else {
cursor.token
},
revision: if event.revision > cursor.revision {
event.revision
} else {
cursor.revision
},
at: if event.at > cursor.at {
event.at
} else {
cursor.at
},
}
}
}
{ valid: findings.length() == 0, checked_events: events.length(), findings }
}