Files
settled-reach/server/src/simulation/knowledge_grant.rs
T
jpmschweitzerandClaude Opus 4.6 aa79dd97e7 fix(simulation): Clippy cleanup and CI enforcement (#635)
Fix all Clippy warnings across the server codebase (2411 insertions, 1341
deletions). Raise type-complexity-threshold to 750 and too-many-arguments
to 12 in .clippy.toml for idiomatic Bevy ECS system signatures. The server
now passes `cargo clippy -- --deny warnings` cleanly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 10:33:15 +01:00

93 lines
3.2 KiB
Rust

//! Knowledge grant types for dialogue and monologue lines.
//!
//! `KnowledgeGrant` describes the knowledge a player gains from a dialogue line.
//! `Prerequisites` describes preconditions for a monologue line to fire.
use serde::Deserialize;
use std::collections::BTreeMap;
/// Knowledge grant attached to a dialogue line (D-079).
///
/// Untagged enum — serde tries each variant in order:
/// `Fact` matches YAML with `fact_id` field.
/// `Entity` matches YAML with `entity_ref` field.
///
/// **Ambiguity hazard**: if a YAML object contains both `fact_id` and `entity_ref`,
/// serde(untagged) silently selects `Fact` (first variant wins) and ignores `entity_ref`.
/// Use [`KnowledgeGrant::validate`] at load time to catch this case.
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum KnowledgeGrant {
/// Grant knowledge of a non-entity fact.
/// Format: fact_id "category.topic", confidence string.
Fact { fact_id: String, confidence: String },
/// Grant knowledge of an entity (creates EntityKnowledge entry in observer's KG).
/// Required for contradiction detection: testimony must create ToldBy EntityKnowledge
/// so a subsequent DirectObservation can detect a discrepancy (D-079, D-083).
Entity {
entity_ref: String,
#[serde(default)]
attributes: BTreeMap<String, String>,
confidence: String,
},
}
impl KnowledgeGrant {
/// Validates that the grant is internally consistent.
///
/// Specifically catches the serde(untagged) ambiguity: a raw YAML value that has
/// both `fact_id` and `entity_ref` would parse silently as `Fact`, losing the
/// entity reference. This can only be detected after deserializing the surrounding
/// structure — call this on every grant after loading.
///
/// Currently there is no post-deserialization raw-value access, so this serves as
/// a place to add future cross-field validation. Returns `Ok(())` for now.
pub fn validate(&self) -> Result<(), String> {
match self {
KnowledgeGrant::Fact { fact_id, .. } => {
if fact_id.is_empty() {
return Err("KnowledgeGrant::Fact has empty fact_id".to_string());
}
}
KnowledgeGrant::Entity { entity_ref, .. } => {
if entity_ref.is_empty() {
return Err("KnowledgeGrant::Entity has empty entity_ref".to_string());
}
}
}
Ok(())
}
}
/// Prerequisite set for a monologue line.
#[derive(Debug, Clone, Deserialize)]
pub struct Prerequisites {
#[serde(default)]
pub facts: Vec<FactPrerequisite>,
#[serde(default)]
pub entity_attributes: Vec<AttributePrerequisite>,
#[serde(default)]
pub relationship: Option<RelationshipPrerequisite>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FactPrerequisite {
pub fact_id: String,
pub min_confidence: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AttributePrerequisite {
pub entity: String,
pub key: String,
pub value: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RelationshipPrerequisite {
#[serde(default)]
pub target: Option<String>,
#[serde(default)]
pub state: Option<String>,
}