subjects: 2nd scrum fix wave (token min, chain_tip, tampering, rebuild collision warn)
Second cross-lineage scrum on Steps 5+6 returned 13 distinct findings, 0 convergent.
Three BLOCK-class claims verified as false positives (cache IS written, per-subject
Mutex IS in place, spawn IS safe under writer's lock). Five real fixes shipped:
1. audit_endpoint: legal token min length 16->32 (HMAC-SHA256 best practice, kimi)
2. subject_audit: new chain_tip() returns last hash from full log; audit_endpoint
now reports chain_root from full chain instead of windowed slice (opus)
3. registry: rebuild loader now warns on sanitize collision (symmetric with
put_subject's collision guard - opus)
4. audit_endpoint: tampering detection - if manifest expects non-empty chain_root
but log returns 0 rows, flag chain_verified=false with explicit message (opus)
5. execution_loop::audit_result_state: tightened heuristic - error/denied/not_found
only classify when no rows/data/results sibling (opus INFO)
Tests: 17 catalogd subject + 6 gateway audit_result_state, all green.
New: audit_result_state_does_not_classify_error_when_data_sibling_present,
audit_result_state_status_is_authoritative_even_with_data.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1070,15 +1070,29 @@ async fn append_outcomes_row_at(
|
||||
/// row-level concern, not a result-state concern).
|
||||
pub(crate) fn audit_result_state(v: &serde_json::Value) -> String {
|
||||
if let Some(obj) = v.as_object() {
|
||||
if obj.get("error").map(is_truthy).unwrap_or(false) {
|
||||
return "error".into();
|
||||
}
|
||||
if obj.get("denied").map(is_truthy).unwrap_or(false) {
|
||||
return "denied".into();
|
||||
}
|
||||
if obj.get("not_found").map(is_truthy).unwrap_or(false) {
|
||||
return "not_found".into();
|
||||
// Tighter than before (2026-05-03 opus scrum INFO at
|
||||
// execution_loop:1085): only flag error/denied/not_found when
|
||||
// the payload doesn't ALSO have a rows/data/results sibling.
|
||||
// A successful tool whose response includes an "error" field
|
||||
// describing one row's problem (out of many) shouldn't taint
|
||||
// the whole call as "error" — that's a row-level concern, not
|
||||
// a result-state concern.
|
||||
let has_data_sibling = obj.contains_key("rows")
|
||||
|| obj.contains_key("data")
|
||||
|| obj.contains_key("results");
|
||||
if !has_data_sibling {
|
||||
if obj.get("error").map(is_truthy).unwrap_or(false) {
|
||||
return "error".into();
|
||||
}
|
||||
if obj.get("denied").map(is_truthy).unwrap_or(false) {
|
||||
return "denied".into();
|
||||
}
|
||||
if obj.get("not_found").map(is_truthy).unwrap_or(false) {
|
||||
return "not_found".into();
|
||||
}
|
||||
}
|
||||
// status is always authoritative — if a tool explicitly says
|
||||
// status=denied that's not a row-level concern.
|
||||
if let Some(s) = obj.get("status").and_then(|v| v.as_str()) {
|
||||
match s {
|
||||
"denied" | "not_found" | "error" => return s.into(),
|
||||
@@ -2130,4 +2144,23 @@ mod tests {
|
||||
let v = serde_json::json!({"error": null, "denied": false, "rows": [{"candidate_id":"X"}]});
|
||||
assert_eq!(audit_result_state(&v), "success");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audit_result_state_does_not_classify_error_when_data_sibling_present() {
|
||||
// 2026-05-03 opus scrum INFO at execution_loop:1085 regression:
|
||||
// a tool that returns rows BUT also has an "error" field describing
|
||||
// one bad row shouldn't taint the whole call as "error".
|
||||
let v = serde_json::json!({
|
||||
"rows": [{"candidate_id": "OK"}, {"candidate_id": "BAD"}],
|
||||
"error": "1 row had a malformed certification"
|
||||
});
|
||||
assert_eq!(audit_result_state(&v), "success");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audit_result_state_status_is_authoritative_even_with_data() {
|
||||
// Explicit status="denied" wins over data-sibling rule.
|
||||
let v = serde_json::json!({"status": "denied", "rows": []});
|
||||
assert_eq!(audit_result_state(&v), "denied");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user