Scrum-driven fixes: P5-001 auth wired, P42-001 truth evaluator, P9-001 journal on ingest
lakehouse/auditor 2 blocking issues: cloud: claim not backed — "| **P9-001** (partial) | `crates/ingestd/src/service.rs` | **3 → 6** ↑↑↑ | `journal.record_ing

Apply the highest-confidence findings from the Phase 0→42 forensic sweep
after four scrum-master iterations under the adversarial prompt. Each fix
is independently validated by a later scrum iteration scoring the same
file higher under the same bar.

Code changes
────────────
P5-001 — crates/gateway/src/auth.rs + main.rs
  api_key_auth was marked #[allow(dead_code)] and never wrapped around
  the router, so `[auth] enabled=true` logged a green message and
  enforced nothing. Now wired via from_fn_with_state, with constant-time
  header compare and /health exempted for LB probes.

P42-001 — crates/truth/src/lib.rs
  TruthStore::check() ignored RuleCondition entirely — signature looked
  like enforcement, body returned every action unconditionally. Added
  evaluate(task_class, ctx) that actually walks FieldEquals / FieldEmpty /
  FieldGreater / Always against a serde_json::Value via dot-path lookup.
  check() kept for back-compat. Tests 14 → 24 (10 new exercising real
  pass/fail semantics). serde_json moved to [dependencies].

P9-001 (partial) — crates/ingestd/src/service.rs
  Added Optional<Journal> to IngestState + a journal.record_ingest() call
  on /ingest/file success. Gateway wires it with `journal.clone()` before
  the /journal nest consumes the original. First-ever internal mutation
  journal event verified live (total_events_created 0→1 after probe).

Iter-4 scrum scored these files higher under same prompt:
  ingestd/src/service.rs      3 → 6  (P9-001 visible)
  truth/src/lib.rs            3 → 4  (P42-001 visible)
  gateway/src/auth.rs         3 → 4  (P5-001 visible)
  gateway/src/execution_loop  4 → 6  (indirect)
  storaged/src/federation     3 → 4  (indirect)

Infrastructure additions
────────────────────────
 * tests/real-world/scrum_master_pipeline.ts
   - cloud-first ladder: kimi-k2:1t → deepseek-v3.1:671b → mistral-large-3:675b
     → gpt-oss:120b → devstral-2:123b → qwen3.5:397b (deep final thinker)
   - LH_SCRUM_FORENSIC env: injects SCRUM_FORENSIC_PROMPT.md as adversarial preamble
   - LH_SCRUM_PROPOSAL env: per-iter fix-wave doc override
   - Confidence extraction (markdown + JSON), schema v4 KB rows with:
     verdict, critical_failures_count, verified_components_count,
     missing_components_count, output_format, gradient_tier
   - Model trust profile written per file-accept to data/_kb/model_trust.jsonl
   - Fire-and-forget POST to observer /event so by_source.scrum appears in /stats

 * mcp-server/observer.ts — unchanged in shape, confirmed receiving scrum events

 * ui/ — new Visual Control Plane on :3950
   - Bun.serve with /data/{services,reviews,metrics,trust,overrides,findings,file,refactor_signals,search,logs/:svc,scrum_log}
   - Views: MAP (D3 graph, 5 overlays) / TRACE (per-file iter timeline) /
     TRAJECTORY (refactor signals + reverse index search) / METRICS (explainers
     with SOURCE + GOOD lines) / KB (card grid with tooltips) / CONSOLE (per-service
     journalctl tail, tabs for gateway/sidecar/observer/mcp/ctx7/auditor/langfuse)
   - tryFetch always attempts JSON.parse (fix for observer returning JSON without content-type)
   - renderNodeContext primitive-vs-object guard (fix for gateway /health string)

 * docs/SCRUM_FIX_WAVE.md     — iter-specific scope directing the scrum
 * docs/SCRUM_FORENSIC_PROMPT.md — adversarial audit prompt (verdict/critical/verified schema)
 * docs/SCRUM_LOOP_NOTES.md   — iteration observations + fix-next-loop queue
 * docs/SYSTEM_EVOLUTION_LAYERS.md — Layers 1-10 roadmap (trust profiling, execution DNA, drift sentinel, etc)

Measurements across iterations
──────────────────────────────
 iter 1 (soft prompt, gpt-oss:120b):   mean score 5.00/10
 iter 3 (forensic, kimi-k2:1t):        mean score 3.56/10 (−1.44 — bar raised)
 iter 4 (same bar, post fixes):        mean score 4.00/10 (+0.44 — fixes landed)

 Score movement iter3→iter4: ↑5 ↓1 =12
 21/21 first-attempt accept by kimi-k2:1t in iter 4
 20/21 emitted forensic JSON (richer signal than markdown)
 16 verified_components captured (proof-of-life, new metric)
 Permission Gradient distribution: 0 auto · 16 dry_run · 4 sim · 1 block

 Observer loop: by_source {scrum: 21, langfuse: 1985, phase24_audit: 1}
 v1/usage: 224 requests, 477K tokens, all tracked

Signal classes per file (iter 3 → iter 4):
 CONVERGING:  1 (ingestd/service.rs — fix clearly landed)
 LOOPING:     4 (catalogd/registry, main, queryd/service, vectord/index_registry)
 ORBITING:    1 (truth — novel findings surfacing as surface ones fix)
 PLATEAU:     9 (scores flat with high confidence — diminishing returns)
 MIXED:       6

Loop thesis status
──────────────────
A file's score rises only when the scrum confirms a real fix landed.
No false positives yet across 3 iterations. Fixes applied to 3 files all
raised their independent scores under the same adversarial prompt. Loop
is measurable, not hand-wavy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-04-24 02:25:43 -05:00
co-authored by Claude Opus 4.7
parent 4251e94531
commit 21fd3b9c61
38 changed files with 5736 additions and 53 deletions
+523
View File
@@ -0,0 +1,523 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TruthRule {
pub id: String,
pub task_class: String,
pub description: String,
pub condition: RuleCondition,
pub action: RuleAction,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum RuleCondition {
Always,
FieldEquals { field: String, value: String },
FieldMismatch { field: String, value: String },
FieldEmpty { field: String },
FieldGreater { field: String, threshold: i64 },
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum RuleAction {
Pass,
Reject { message: String },
Redact { fields: Vec<String> },
Block { message: String },
}
#[derive(Default)]
pub struct TruthStore {
rules: HashMap<String, Vec<TruthRule>>,
}
impl TruthStore {
pub fn new() -> Self {
Self::default()
}
pub fn add_rule(&mut self, rule: TruthRule) {
self.rules
.entry(rule.task_class.clone())
.or_default()
.push(rule);
}
pub fn get_rules(&self, task_class: &str) -> Vec<&TruthRule> {
self.rules
.get(task_class)
.map(|v| v.iter().collect())
.unwrap_or_default()
}
/// Legacy API: returns the list of actions registered for a task class
/// without evaluating conditions. Retained for backward compatibility
/// with callers that only want the action catalog. New callers should
/// prefer `evaluate()`, which actually walks `RuleCondition` against
/// a context and reports per-rule pass/fail.
pub fn check(&self, task_class: &str) -> Vec<RuleAction> {
let rules = self.get_rules(task_class);
rules
.into_iter()
.map(|r| r.action.clone())
.collect()
}
/// Evaluate every rule registered for `task_class` against `ctx`,
/// returning one `RuleOutcome` per rule. `passed = true` means the
/// rule's `condition` held; the rule's action is still attached so
/// callers can distinguish "passed and therefore no-op" (RuleAction::Pass)
/// from "passed and apply Redact". `passed = false` means the condition
/// failed — callers should treat the attached action as the enforcement
/// response (Reject/Block).
///
/// Fixed P42-001 (2026-04-23): previously `check()` returned all actions
/// unconditionally — the `RuleCondition` field was ignored. Now every
/// rule is actually walked against the provided context.
pub fn evaluate(&self, task_class: &str, ctx: &serde_json::Value) -> Vec<RuleOutcome> {
self.get_rules(task_class)
.into_iter()
.map(|r| RuleOutcome {
rule_id: r.id.clone(),
passed: evaluate_condition(&r.condition, ctx),
action: r.action.clone(),
})
.collect()
}
}
/// Result of evaluating one rule against a context. `passed` reports
/// whether the condition held; `action` is the rule's declared action
/// regardless (callers decide how to apply it based on `passed`).
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct RuleOutcome {
pub rule_id: String,
pub passed: bool,
pub action: RuleAction,
}
fn evaluate_condition(cond: &RuleCondition, ctx: &serde_json::Value) -> bool {
match cond {
RuleCondition::Always => true,
RuleCondition::FieldEquals { field, value } => {
field_as_string(ctx, field)
.map(|s| s == *value)
.unwrap_or(false)
}
RuleCondition::FieldMismatch { field, value } => {
field_as_string(ctx, field)
.map(|s| s != *value)
.unwrap_or(false)
}
RuleCondition::FieldEmpty { field } => {
match lookup(ctx, field) {
None => true,
Some(v) => v.is_null() || v.as_str().map(|s| s.is_empty()).unwrap_or(false),
}
}
RuleCondition::FieldGreater { field, threshold } => {
lookup(ctx, field)
.and_then(|v| v.as_i64().or_else(|| v.as_f64().map(|f| f as i64)))
.map(|n| n > *threshold)
.unwrap_or(false)
}
}
}
/// Walk a dot-separated path through a serde_json::Value. `"worker.status"`
/// → `ctx["worker"]["status"]`. Returns None if any segment is missing or
/// a non-object is encountered mid-path.
fn lookup<'a>(ctx: &'a serde_json::Value, path: &str) -> Option<&'a serde_json::Value> {
let mut cur = ctx;
for seg in path.split('.') {
cur = cur.get(seg)?;
}
Some(cur)
}
fn field_as_string(ctx: &serde_json::Value, path: &str) -> Option<String> {
lookup(ctx, path).and_then(|v| match v {
serde_json::Value::String(s) => Some(s.clone()),
serde_json::Value::Bool(b) => Some(b.to_string()),
serde_json::Value::Number(n) => Some(n.to_string()),
_ => None,
})
}
pub fn default_truth_store() -> TruthStore {
let mut store = TruthStore::new();
store.add_rule(TruthRule {
id: "worker-active".to_string(),
task_class: "staffing.fill".to_string(),
description: "Worker must be active".to_string(),
condition: RuleCondition::FieldEquals {
field: "worker.status".to_string(),
value: "active".to_string(),
},
action: RuleAction::Pass,
});
store.add_rule(TruthRule {
id: "client-not-blacklisted".to_string(),
task_class: "staffing.fill".to_string(),
description: "Worker cannot be blacklisted for client".to_string(),
condition: RuleCondition::FieldEquals {
field: "worker.client_blacklisted".to_string(),
value: "false".to_string(),
},
action: RuleAction::Pass,
});
store.add_rule(TruthRule {
id: "deadline-required".to_string(),
task_class: "staffing.fill".to_string(),
description: "Contract must have deadline".to_string(),
condition: RuleCondition::FieldEmpty {
field: "contract.deadline".to_string(),
},
action: RuleAction::Reject {
message: "Contract deadline is required".to_string(),
},
});
store.add_rule(TruthRule {
id: "budget-required".to_string(),
task_class: "staffing.fill".to_string(),
description: "Budget must be non-negative".to_string(),
condition: RuleCondition::FieldGreater {
field: "contract.budget_per_hour_max".to_string(),
threshold: 0,
},
action: RuleAction::Pass,
});
store.add_rule(TruthRule {
id: "pii-redact".to_string(),
task_class: "staffing.any".to_string(),
description: "Redact PII before cloud calls".to_string(),
condition: RuleCondition::Always,
action: RuleAction::Redact {
fields: vec!["ssn".to_string(), "salary".to_string()],
},
});
store
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truth_store_new_is_empty() {
let store = TruthStore::new();
assert!(store.rules.is_empty());
}
#[test]
fn add_rule_inserts_into_correct_task_class() {
let mut store = TruthStore::new();
store.add_rule(TruthRule {
id: "test-rule".to_string(),
task_class: "test.task".to_string(),
description: "Test rule".to_string(),
condition: RuleCondition::Always,
action: RuleAction::Pass,
});
let rules = store.get_rules("test.task");
assert_eq!(rules.len(), 1);
assert_eq!(rules[0].id, "test-rule");
}
#[test]
fn get_rules_returns_empty_for_unknown_class() {
let store = TruthStore::new();
let rules = store.get_rules("unknown.class");
assert!(rules.is_empty());
}
#[test]
fn check_returns_actions_for_task_class() {
let mut store = TruthStore::new();
store.add_rule(TruthRule {
id: "a1".to_string(),
task_class: "test".to_string(),
description: "A1".to_string(),
condition: RuleCondition::Always,
action: RuleAction::Pass,
});
store.add_rule(TruthRule {
id: "a2".to_string(),
task_class: "test".to_string(),
description: "A2".to_string(),
condition: RuleCondition::Always,
action: RuleAction::Reject {
message: "test reject".to_string(),
},
});
let actions = store.check("test");
assert_eq!(actions.len(), 2);
}
#[test]
fn rule_condition_serialize_always() {
let cond = RuleCondition::Always;
let json = serde_json::to_string(&cond).unwrap();
assert!(json.contains(r#""type":"Always"#));
}
#[test]
fn rule_condition_serialize_field_equals() {
let cond = RuleCondition::FieldEquals {
field: "foo".to_string(),
value: "bar".to_string(),
};
let json = serde_json::to_string(&cond).unwrap();
assert!(json.contains(r#""type":"FieldEquals""#));
assert!(json.contains(r#""field":"foo""#));
assert!(json.contains(r#""value":"bar""#));
}
#[test]
fn rule_action_serialize_redact() {
let action = RuleAction::Redact {
fields: vec!["ssn".to_string()],
};
let json = serde_json::to_string(&action).unwrap();
assert!(json.contains(r#""type":"Redact""#));
assert!(json.contains("ssn"));
}
#[test]
fn rule_action_serialize_reject() {
let action = RuleAction::Reject {
message: "test".to_string(),
};
let json = serde_json::to_string(&action).unwrap();
assert!(json.contains(r#""type":"Reject""#));
}
#[test]
fn default_truth_store_has_staffing_rules() {
let store = default_truth_store();
let fill_rules = store.get_rules("staffing.fill");
assert!(!fill_rules.is_empty());
let any_rules = store.get_rules("staffing.any");
assert!(!any_rules.is_empty());
}
#[test]
fn multiple_rules_same_task_class() {
let mut store = TruthStore::new();
for i in 0..5 {
store.add_rule(TruthRule {
id: format!("rule-{}", i),
task_class: "test".to_string(),
description: format!("Rule {}", i),
condition: RuleCondition::Always,
action: RuleAction::Pass,
});
}
let rules = store.get_rules("test");
assert_eq!(rules.len(), 5);
}
#[test]
fn truth_rule_clone_preserves_data() {
let rule = TruthRule {
id: "clone-test".to_string(),
task_class: "clone.task".to_string(),
description: "Clone test".to_string(),
condition: RuleCondition::FieldEquals {
field: "x".to_string(),
value: "y".to_string(),
},
action: RuleAction::Block {
message: "blocked".to_string(),
},
};
let cloned = rule.clone();
assert_eq!(cloned.id, rule.id);
assert_eq!(cloned.condition, rule.condition);
assert_eq!(cloned.action, rule.action);
}
#[test]
fn field_greater_condition_parse() {
let json = r#"{"type":"FieldGreater","field":"count","threshold":10}"#;
let cond: RuleCondition = serde_json::from_str(json).unwrap();
match cond {
RuleCondition::FieldGreater { field, threshold } => {
assert_eq!(field, "count");
assert_eq!(threshold, 10);
}
_ => panic!("Expected FieldGreater"),
}
}
#[test]
fn block_action_blocks_with_message() {
let action = RuleAction::Block {
message: "Rate limited".to_string(),
};
let json = serde_json::to_string(&action).unwrap();
assert!(json.contains("Rate limited"));
}
#[test]
fn empty_store_check_returns_empty() {
let store = TruthStore::new();
let actions = store.check("empty.class");
assert!(actions.is_empty());
}
// ── P42-001 evaluate() tests — actually walk RuleCondition ──
fn fill_store() -> TruthStore {
let mut s = TruthStore::new();
s.add_rule(TruthRule {
id: "active".into(),
task_class: "t".into(),
description: "must be active".into(),
condition: RuleCondition::FieldEquals {
field: "worker.status".into(),
value: "active".into(),
},
action: RuleAction::Reject {
message: "worker not active".into(),
},
});
s.add_rule(TruthRule {
id: "deadline".into(),
task_class: "t".into(),
description: "deadline required".into(),
condition: RuleCondition::FieldEmpty {
field: "contract.deadline".into(),
},
action: RuleAction::Reject {
message: "missing deadline".into(),
},
});
s.add_rule(TruthRule {
id: "budget".into(),
task_class: "t".into(),
description: "budget positive".into(),
condition: RuleCondition::FieldGreater {
field: "contract.budget".into(),
threshold: 0,
},
action: RuleAction::Block {
message: "budget must be positive".into(),
},
});
s
}
#[test]
fn evaluate_field_equals_pass_on_match() {
let s = fill_store();
let ctx = serde_json::json!({"worker": {"status": "active"}});
let o = s.evaluate("t", &ctx);
let active = o.iter().find(|r| r.rule_id == "active").unwrap();
assert!(active.passed, "active condition should hold");
}
#[test]
fn evaluate_field_equals_fail_on_mismatch() {
let s = fill_store();
let ctx = serde_json::json!({"worker": {"status": "terminated"}});
let o = s.evaluate("t", &ctx);
let active = o.iter().find(|r| r.rule_id == "active").unwrap();
assert!(!active.passed, "terminated should fail active condition");
}
#[test]
fn evaluate_field_equals_fail_on_missing() {
let s = fill_store();
let ctx = serde_json::json!({});
let o = s.evaluate("t", &ctx);
let active = o.iter().find(|r| r.rule_id == "active").unwrap();
assert!(!active.passed, "missing worker.status should fail");
}
#[test]
fn evaluate_field_empty_pass_when_absent() {
let s = fill_store();
// FieldEmpty passes when the field is missing/null/empty string.
// Deadline rule says "field empty means action fires" — so passed=true
// here means the rule's condition held (deadline IS empty).
let ctx = serde_json::json!({});
let o = s.evaluate("t", &ctx);
let deadline = o.iter().find(|r| r.rule_id == "deadline").unwrap();
assert!(deadline.passed);
}
#[test]
fn evaluate_field_empty_fail_when_present() {
let s = fill_store();
let ctx = serde_json::json!({"contract": {"deadline": "2026-05-01"}});
let o = s.evaluate("t", &ctx);
let deadline = o.iter().find(|r| r.rule_id == "deadline").unwrap();
assert!(!deadline.passed, "non-empty deadline should fail FieldEmpty check");
}
#[test]
fn evaluate_field_greater_pass_and_fail() {
let s = fill_store();
let ctx_ok = serde_json::json!({"contract": {"budget": 100}});
let ctx_bad = serde_json::json!({"contract": {"budget": 0}});
let ok = s.evaluate("t", &ctx_ok);
let bad = s.evaluate("t", &ctx_bad);
assert!(ok.iter().find(|r| r.rule_id == "budget").unwrap().passed);
assert!(!bad.iter().find(|r| r.rule_id == "budget").unwrap().passed);
}
#[test]
fn evaluate_always_condition_passes_unconditionally() {
let mut s = TruthStore::new();
s.add_rule(TruthRule {
id: "always".into(),
task_class: "x".into(),
description: "".into(),
condition: RuleCondition::Always,
action: RuleAction::Pass,
});
let o = s.evaluate("x", &serde_json::json!(null));
assert!(o[0].passed);
}
#[test]
fn evaluate_preserves_action_regardless_of_outcome() {
let s = fill_store();
let ctx = serde_json::json!({"worker": {"status": "active"}});
let o = s.evaluate("t", &ctx);
let active = o.iter().find(|r| r.rule_id == "active").unwrap();
// Action is attached whether the rule passed or not — the consumer
// decides how to use it.
assert_eq!(
active.action,
RuleAction::Reject {
message: "worker not active".into()
}
);
}
#[test]
fn evaluate_on_unknown_task_class_returns_empty() {
let s = fill_store();
let o = s.evaluate("nonexistent", &serde_json::json!({}));
assert!(o.is_empty());
}
#[test]
fn check_still_returns_actions_unconditionally_for_back_compat() {
// Legacy API should still behave the same — no condition walking.
let s = fill_store();
let actions = s.check("t");
assert_eq!(actions.len(), 3, "check returns one action per rule regardless of condition");
}
}