diff --git a/AGENTS.md b/AGENTS.md index 55d488a..ce540a6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,13 +51,3 @@ The live state architecture is ported from EpiphanyAgent in reduced form: Project truth does not belong in agent personality memory. If Weksa later grows role dossiers or heartbeat scheduling, those surfaces must remain separate from language-project truth. - -## Building And Verifying - -- `crates/weksa` depends on CultLib's `cultcache-rs` as a git dependency pinned by - `rev`, fetched from GitHub. With the default `CARGO_HOME` that fetch fails on a - Mind's host. Use the pre-fetched home: - `CARGO_HOME=/var/lib/mind-weksa-self/cargo-home cargo test --workspace`. -- `weksa packet proof ` admits the nibu proof packet under the key - `packet:6e6962752d303031` (hex of `nibu-001`), not `nibu-proof-001`. Use that key - with `weksa lower `. diff --git a/crates/weksa/src/inventory.rs b/crates/weksa/src/inventory.rs index 22538dd..b6fccf3 100644 --- a/crates/weksa/src/inventory.rs +++ b/crates/weksa/src/inventory.rs @@ -188,7 +188,6 @@ pub enum Slot { Referent, Predication, Relation, - Stance, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] @@ -440,28 +439,10 @@ pub fn seed() -> (InventoryRevision, Vec) { &pred, ); let nuance = [Slot::Discourse, Slot::Predication]; - for g in ["hedge", "certainty"] { + for g in ["hedge", "certainty", "warmth", "formality", "intensity"] { add(Family::Feature, g, Some(ValueKind::Grade), &nuance); } add(Family::Feature, "irony", Some(ValueKind::Flag), &nuance); - // Felt affect (permille of how strongly it is felt) and the restraint the speaker - // puts on it: slot Stance only. Affection covers fondness toward the - // addressee. Displayed affect is each language's ontology's business. - let stance = [Slot::Stance]; - for g in [ - "anger", - "fear", - "sadness", - "joy", - "disgust", - "surprise", - "contempt", - "affection", - "shame", - "restraint", - ] { - add(Family::Feature, g, Some(ValueKind::Grade), &stance); - } let changes = entries.iter().map(|e| Change::Add(e.id)).collect(); ( InventoryRevision { diff --git a/crates/weksa/src/lib.rs b/crates/weksa/src/lib.rs index 0bfdb8c..ca92940 100644 --- a/crates/weksa/src/lib.rs +++ b/crates/weksa/src/lib.rs @@ -24,7 +24,7 @@ pub use inventory::{ }; pub use lower::{LowerError, lower}; pub use packet::{ - EncoderId, EntityId, Grade, NodeId, Packet, PacketId, PacketKind, SCHEMA_VERSION, Stance, + EncoderId, EntityId, Grade, NodeId, Packet, PacketId, PacketKind, SCHEMA_VERSION, }; pub use score::{Embedder, ItemScore, OllamaEmbedder, Scorer, chrf}; pub use split::{Split, SplitScheme}; diff --git a/crates/weksa/src/main.rs b/crates/weksa/src/main.rs index f36ba27..a5830f6 100644 --- a/crates/weksa/src/main.rs +++ b/crates/weksa/src/main.rs @@ -486,22 +486,20 @@ fn main() -> Result<()> { println!("admitted {key}"); } Command::Lower { store, key } => { - let inventory_store = InventoryStore::open(&cli.inventory)?; - let inventory = inventory_store.inventory()?; - let mut packets = PacketStore::open(&store)?; - let packet = packets + let inventory = InventoryStore::open(&cli.inventory)?.inventory()?; + let packet = PacketStore::open(&store)? .get(&key)? .with_context(|| format!("no packet {key}"))?; - // Admission is the only source of a LoweringInput; re-admitting the - // stored packet is a no-op write that re-runs every door check. - let view = packets - .admit( - packet, - &inventory_store, - &CorpusStore::open(&cli.corpus)?, - &EntityStore::open(&cli.entities)?, + let view = validate(&packet, &inventory).map_err(|refusals| { + anyhow::anyhow!( + "{}", + refusals + .iter() + .map(ToString::to_string) + .collect::>() + .join("; ") ) - .map_err(refused)?; + })?; println!("{}", lower(&view, &inventory)?); } Command::Score(args) => { diff --git a/crates/weksa/src/packet.rs b/crates/weksa/src/packet.rs index cae001b..72132dd 100644 --- a/crates/weksa/src/packet.rs +++ b/crates/weksa/src/packet.rs @@ -16,7 +16,7 @@ pub use crate::inventory::InvId; use crate::inventory::OptionLabel; /// Bumped by a commit that changes these types; the published schema moves with it. -pub const SCHEMA_VERSION: u32 = 4; +pub const SCHEMA_VERSION: u32 = 3; /// Id of a referent or predication within one packet: `n`, `k` a canonical /// decimal 1..=9999. @@ -208,8 +208,6 @@ pub struct Packet { pub extensions: Vec, #[cultcache(key = 12)] pub trace: Trace, - #[cultcache(key = 13)] - pub stance: Stance, } impl Packet { @@ -264,14 +262,6 @@ pub struct Discourse { pub features: Vec, } -/// The speaker's felt affect and restraint, never displayed affect: how the -/// speaker shows a feeling is each language's ontology's business. A -/// held-back feeling is not a claim, so it is never a predication. -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -pub struct Stance { - pub features: Vec, -} - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct FeatureValue { /// Feature id. @@ -427,6 +417,5 @@ pub fn proof_packet() -> Packet { notes: vec!["First proof packet for Nibu flavored-English lowering.".to_string()], rejected_readings: vec![], }, - stance: Stance::default(), } } diff --git a/crates/weksa/src/store.rs b/crates/weksa/src/store.rs index 1caa6ac..138ff62 100644 --- a/crates/weksa/src/store.rs +++ b/crates/weksa/src/store.rs @@ -16,7 +16,7 @@ use crate::entity::{Entity, EntityOrigin}; use crate::form::{is_attestation, is_joining_only, is_language_tag, is_refused_format}; use crate::inventory::{Change, InvId, Inventory, InventoryEntry, InventoryRevision, revision_key}; use crate::packet::{EntityId, Packet}; -use crate::validate::{LoweringInput, Refusal, admissible}; +use crate::validate::{Refusal, validate}; /// A broken store rule, named. #[derive(Clone, Debug, PartialEq, Eq)] @@ -74,9 +74,6 @@ pub enum StoreRefusal { /// A corpus packet's referent cites an entity that is neither named on its /// item nor the item's speaker or a prior-turn speaker. EntityNotOnItem { entity: EntityId, item: String }, - /// A new packet names a corpus item already withdrawn. Packets admitted - /// before the withdrawal stay stored and readable. - WithdrawnCorpusItem { key: String }, /// Any other malformed document or change. Malformed { key: String, why: &'static str }, } @@ -669,10 +666,9 @@ impl PacketStore { self.cache.get::(key) } - /// The only door, and the only source of a [`LoweringInput`]: a packet - /// exists only if the validator's rules pass against the inventory the - /// store holds at the packet's revision, its `corpus_item` resolves to an - /// item that is not withdrawn, every referent's entity is in the entity store and is named on + /// The only door: a packet exists only if `validate` passes against the + /// inventory the store holds at the packet's revision, its `corpus_item` + /// resolves, every referent's entity is in the entity store and is named on /// that item or is its speaker or a prior-turn speaker (an id-set check). A /// key holds its packet for good. A packet that names no corpus item has no /// door here. @@ -682,15 +678,9 @@ impl PacketStore { inventory: &InventoryStore, corpus: &CorpusStore, entities: &EntityStore, - ) -> Result { - let view = admissible(&packet, &inventory.inventory()?).map_err(StoreError::Invalid)?; + ) -> Result<(), StoreError> { + validate(&packet, &inventory.inventory()?).map_err(StoreError::Invalid)?; let item = corpus.resolve(&packet.provenance.corpus_item)?; - if item.withdrawn { - return Err(StoreRefusal::WithdrawnCorpusItem { - key: item.id.clone(), - } - .into()); - } for entity in packet.referents.iter().filter_map(|r| r.entity.as_ref()) { if entities.get(entity)?.is_none() { return Err(StoreRefusal::UnknownEntity { @@ -712,11 +702,11 @@ impl PacketStore { let key = packet.id.to_string(); if let Some(existing) = self.get(&key)? { if existing == packet { - return Ok(view); + return Ok(()); } return Err(StoreRefusal::PacketImmutable { key }.into()); } self.cache.put(key, &packet)?; - Ok(view) + Ok(()) } } diff --git a/crates/weksa/src/validate.rs b/crates/weksa/src/validate.rs index 7fbf6aa..a88c18e 100644 --- a/crates/weksa/src/validate.rs +++ b/crates/weksa/src/validate.rs @@ -1,9 +1,6 @@ -//! The validator owns the inventory and form rules a packet must meet. Every -//! path that admits a packet applies them, but `validate` only judges: it -//! returns `Ok(())` or the refusals. [`LoweringInput`], the only thing a -//! lowerer sees, comes from `PacketStore::admit` alone, which builds it through -//! the crate-private `admissible`. The stance is checked like every feature -//! list, in slot `Stance`. The rules are V1..V8 and V10..V12: the shape of an id +//! The validator owns whether a packet exists. Every path that admits or lowers a +//! packet calls `validate`, and its Ok value, [`LoweringInput`], is the only +//! thing a lowerer sees. The rules are V1..V8 and V10..V12: the shape of an id //! or a grade is its type's parse, not a rule, so V9 is retired and not //! renumbered. //! @@ -28,7 +25,7 @@ use std::fmt; use crate::inventory::{Family, InvId, Inventory, InventoryEntry, Slot, ValueKind}; use crate::packet::{ Constraints, Context, Discourse, Extension, FeatValue, FeatureValue, NodeId, Packet, - PacketKind, Predication, Referent, Relation, SCHEMA_VERSION, Stance, + PacketKind, Predication, Referent, Relation, SCHEMA_VERSION, }; /// A broken rule, named. @@ -94,42 +91,9 @@ impl fmt::Display for Refusal { } } -/// Whether `packet` meets the rules under `inventory`. All broken rules are -/// returned. A pass carries no lowering view: that comes from -/// `PacketStore::admit` only. -/// -/// ``` -/// use weksa::{Inventory, Refusal, packet::Packet}; -/// fn judge(p: &Packet, i: &Inventory) -> Result<(), Vec> { weksa::validate(p, i) } -/// ``` -/// -/// ```compile_fail -/// use weksa::{Inventory, LoweringInput, Refusal, packet::Packet}; -/// fn lower(p: &Packet, i: &Inventory) -> Result> { weksa::validate(p, i) } -/// ``` -pub fn validate(packet: &Packet, inventory: &Inventory) -> Result<(), Vec> { - admissible(packet, inventory).map(|_| ()) -} - -/// The one implementation of the rules. Crate-private: `PacketStore::admit` is -/// the only caller that keeps the view. -/// -/// The first block is the same call through the public name, so the second -/// fails for the private path and nothing else. -/// -/// ``` -/// use weksa::{Inventory, packet::Packet}; -/// fn judge(p: &Packet, i: &Inventory) { let _ = weksa::validate::validate(p, i); } -/// ``` -/// -/// ```compile_fail -/// use weksa::{Inventory, packet::Packet}; -/// fn lower(p: &Packet, i: &Inventory) { let _ = weksa::validate::admissible(p, i); } -/// ``` -pub(crate) fn admissible( - packet: &Packet, - inventory: &Inventory, -) -> Result> { +/// Whether `packet` may exist under `inventory`. All broken rules are returned; +/// a packet that passes yields the lowering view, which only this function builds. +pub fn validate(packet: &Packet, inventory: &Inventory) -> Result> { let mut out = Vec::new(); if packet.schema_version != SCHEMA_VERSION { out.push(Refusal::V1SchemaVersion { @@ -257,7 +221,6 @@ impl<'a> Inventoried<'a> { predications, relations, extensions, - stance, .. } = p; let Context { @@ -275,7 +238,6 @@ impl<'a> Inventoried<'a> { self.slot(id, Family::SpeechAct, "speech act"); } self.features(features, Slot::Discourse); - self.features(&stance.features, Slot::Stance); for Referent { kind, concept, @@ -387,9 +349,8 @@ fn fits(kind: Option<&ValueKind>, value: &FeatValue) -> bool { /// What the lowerer sees: a validated packet's meaning, with no packet id, no /// provenance (so no corpus item key, encoder or source) and no trace. Its -/// fields are private and the constructor is crate-private, called only by -/// `PacketStore::admit`, so a lowerer cannot be handed an unadmitted packet or -/// the key to an answer text. +/// fields are private and `validate` is the only constructor, so a lowerer +/// cannot be handed an unvalidated packet or the key to an answer text. /// /// Each field has a doctest that reads it from outside the crate. A read /// compiles exactly when the field is `pub`, so the doctest (which must fail to @@ -433,10 +394,6 @@ fn fits(kind: Option<&ValueKind>, value: &FeatValue) -> bool { /// fn read(l: weksa::LoweringInput) { let _ = l.extensions; } /// ``` /// -/// ```compile_fail -/// fn read(l: weksa::LoweringInput) { let _ = l.stance; } -/// ``` -/// /// The constructor is private: a call from outside the crate compiles only if /// `LoweringInput::of` is `pub`. The first block is the same code without the /// call, so the second fails for that call and nothing else. @@ -473,7 +430,6 @@ pub struct LoweringInput { relations: Vec, constraints: Constraints, extensions: Vec, - stance: Stance, } impl LoweringInput { @@ -488,7 +444,6 @@ impl LoweringInput { relations: p.relations.clone(), constraints: p.constraints.clone(), extensions: p.extensions.clone(), - stance: p.stance.clone(), } } @@ -519,7 +474,4 @@ impl LoweringInput { pub fn extensions(&self) -> &[Extension] { &self.extensions } - pub fn stance(&self) -> &Stance { - &self.stance - } } diff --git a/crates/weksa/tests/common/mod.rs b/crates/weksa/tests/common/mod.rs index 07e95ee..13bad1f 100644 --- a/crates/weksa/tests/common/mod.rs +++ b/crates/weksa/tests/common/mod.rs @@ -19,11 +19,7 @@ pub const WARN: &str = "speech_act:5"; pub const POLARITY: &str = "feature:1"; pub const MODALITY: &str = "feature:4"; pub const HEDGE: &str = "feature:6"; -pub const IRONY: &str = "feature:8"; -/// Stance features, slot Stance only (Grade). -pub const ANGER: &str = "feature:9"; -pub const AFFECTION: &str = "feature:16"; -pub const RESTRAINT: &str = "feature:18"; +pub const IRONY: &str = "feature:11"; /// Introduced at rev 2. pub const CONCEPT_A: &str = "concept:1"; pub const CONCEPT_B: &str = "concept:2"; @@ -217,7 +213,6 @@ pub fn world() -> World { Slot::Referent, Slot::Predication, Slot::Relation, - Slot::Stance, ], ) }, @@ -315,17 +310,5 @@ pub fn good() -> Packet { notes: vec!["a note".into()], rejected_readings: vec![], }, - stance: Stance { - features: vec![ - FeatureValue { - feature: iv(ANGER), - value: FeatValue::Grade(grade(300)), - }, - FeatureValue { - feature: iv(RESTRAINT), - value: FeatValue::Grade(grade(700)), - }, - ], - }, } } diff --git a/crates/weksa/tests/entities.rs b/crates/weksa/tests/entities.rs index 46aeb71..4bf0569 100644 --- a/crates/weksa/tests/entities.rs +++ b/crates/weksa/tests/entities.rs @@ -395,7 +395,7 @@ fn packet_entities_resolve() { #[test] fn packet_schema_v2() { - assert_eq!(SCHEMA_VERSION, 4); + assert_eq!(SCHEMA_VERSION, 3); let mut p = good(); p.schema_version = 2; let Err(refused) = validate(&p, &inventory()) else { @@ -420,10 +420,4 @@ fn packet_schema_v2() { serde_json::from_str(&published["weksa.corpus_item.schema.json"]).unwrap(); assert!(item["properties"].get("names").is_some()); assert!(published.contains_key("weksa.entity.schema.json")); - // v4: the packet carries the speaker's stance, a bare feature list. - assert!(packet["properties"].get("stance").is_some(), "{packet}"); - assert_eq!( - packet["$defs"]["Stance"]["properties"]["features"]["type"], - "array" - ); } diff --git a/crates/weksa/tests/lower.rs b/crates/weksa/tests/lower.rs index f50f096..346513a 100644 --- a/crates/weksa/tests/lower.rs +++ b/crates/weksa/tests/lower.rs @@ -22,24 +22,8 @@ fn committed() -> (Inventory, tempfile::TempDir) { (inv.inventory().unwrap(), tempfile::tempdir().unwrap()) } -/// A `LoweringInput` has one source, `PacketStore::admit`, so every packet -/// under test goes through a throwaway store backed by the committed stores. -fn view(packet: &Packet, _inv: &Inventory) -> LoweringInput { - let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); - let inventory = InventoryStore::open(&root.join("data/interlingua/inventory.cc")).unwrap(); - admitted(packet, &inventory) -} - -/// The same door against an inventory store the test built. -fn admitted(packet: &Packet, inventory: &InventoryStore) -> LoweringInput { - let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); - let corpus = CorpusStore::open(&root.join("data/corpus/corpus.cc")).unwrap(); - let entities = EntityStore::open(&root.join("data/entity/entity.cc")).unwrap(); - let dir = tempfile::tempdir().unwrap(); - PacketStore::open(&dir.path().join("p.cc")) - .unwrap() - .admit(packet.clone(), inventory, &corpus, &entities) - .unwrap() +fn view(packet: &Packet, inv: &Inventory) -> LoweringInput { + validate(packet, inv).unwrap() } fn lowered(packet: &Packet, inv: &Inventory) -> Result { @@ -153,7 +137,7 @@ fn a_packet_carrying_what_the_lowerer_does_not_render_is_refused() { let mut p = proof_packet(); edit(&mut p); assert_eq!( - lower(&admitted(&p, &store), &inv), + lower(&view(&p, &inv), &inv), Err(LowerError::Unrendered { what }), "{what}" ); @@ -203,7 +187,7 @@ fn a_row_cannot_speak_for_an_id_whose_gloss_differs() { store.admit_revision(rev2, entries).unwrap(); let inv = store.inventory().unwrap(); assert_eq!( - lower(&admitted(&proof_packet(), &store), &inv), + lowered(&proof_packet(), &inv), Err(LowerError::GlossMismatch { id: iv("concept:4"), row: "control" diff --git a/crates/weksa/tests/stores.rs b/crates/weksa/tests/stores.rs index 07cbe0c..e22b498 100644 --- a/crates/weksa/tests/stores.rs +++ b/crates/weksa/tests/stores.rs @@ -3,7 +3,6 @@ use common::*; use tempfile::TempDir; use weksa::corpus::{CorpusItem, ModelFamily, Source, Turn}; use weksa::inventory::{Change, seed}; -use weksa::packet::PacketId; use weksa::{ AddOutcome, CorpusStore, EntityStore, Family, InventoryEntry, InventoryRevision, InventoryStore, PacketStore, Refusal, Slot, SplitScheme, StoreError, StoreRefusal, ValueKind, @@ -868,7 +867,7 @@ fn retiring_keeps_the_entry_for_earlier_revisions() { fn revision_one_survives_revision_two() { let inv = inventory(); let revisions: Vec<(u32, usize)> = inv.revisions().map(|r| (r.n, r.changes.len())).collect(); - assert_eq!(revisions, vec![(1, 61), (2, 6), (3, 1), (4, 1)]); + assert_eq!(revisions, vec![(1, 54), (2, 6), (3, 1), (4, 1)]); // A packet of revision 1 is judged by the inventory of revision 1: the // revision is known (no V10), and it names nothing that came later. let mut p = good(); @@ -892,7 +891,7 @@ fn the_seed_is_the_standards_enumerations_and_the_nuance_features() { assert_eq!(count(Family::Relation), 16); assert_eq!(count(Family::Role), 8); assert_eq!(count(Family::SpeechAct), 8); - assert_eq!(count(Family::Feature), 18); + assert_eq!(count(Family::Feature), 11); assert_eq!( count(Family::Concept) + count(Family::Frame) + count(Family::Extension), 0 @@ -910,46 +909,6 @@ fn the_seed_is_the_standards_enumerations_and_the_nuance_features() { inv.entry(&iv(IRONY)).unwrap().value_kind, Some(ValueKind::Flag) ); - // Felt affect and restraint: Grade features in slot Stance alone. - for (id, name) in [ - (ANGER, "anger"), - (AFFECTION, "affection"), - (RESTRAINT, "restraint"), - ] { - let e = inv.entry(&iv(id)).unwrap(); - assert_eq!(e.gloss, name); - assert_eq!(e.value_kind, Some(ValueKind::Grade)); - assert_eq!(e.applies_to, vec![Slot::Stance]); - } - let stance_names: Vec<&str> = inv - .entries() - .filter(|e| e.applies_to == [Slot::Stance]) - .map(|e| e.gloss.as_str()) - .collect(); - assert_eq!( - stance_names, - [ - "anger", - "fear", - "sadness", - "joy", - "disgust", - "surprise", - "contempt", - "affection", - "shame", - "restraint" - ] - ); - // Hedge, certainty and irony stay on content; displayed formality, warmth - // and intensity are each language's ontology's, not the seed's. - for e in inv.entries() { - assert!( - !["warmth", "formality", "intensity"].contains(&e.gloss.as_str()), - "{}", - e.gloss - ); - } assert!( inv.entries() .all(|e| e.well_formed() && e.introduced_rev == 1 && e.retired_rev.is_none()) @@ -986,7 +945,7 @@ fn gloss_is_bounded_text() { #[test] fn packet_admission_goes_through_the_validator() { - let mut w = world(); + let w = world(); let mut store = PacketStore::open(&w.dir.path().join("p.cc")).unwrap(); let mut bad = good(); bad.schema_version = 5; @@ -1001,18 +960,13 @@ fn packet_admission_goes_through_the_validator() { other => panic!("{other:?}"), } assert!(store.all().unwrap().is_empty()); - // The door yields the lowering view, stance included. - let view = store + store .admit(good(), &w.inventory, &w.corpus, &w.entities) .unwrap(); - assert_eq!(view.stance(), &good().stance); - assert!(!view.stance().features.is_empty()); - // The same packet is idempotent and yields the same view; a different one - // under the same key is refused. - let replay = store + // The same packet is idempotent; a different one under the same key is refused. + store .admit(good(), &w.inventory, &w.corpus, &w.entities) .unwrap(); - assert_eq!(replay, view); let mut other = good(); other.provenance.confidence = grade(1); assert_eq!( @@ -1023,24 +977,6 @@ fn packet_admission_goes_through_the_validator() { ); assert_eq!(store.all().unwrap().len(), 1); assert_eq!(store.get(PACKET_ID).unwrap(), Some(good())); - // A withdrawn item admits no packet, a replay included; the packet admitted - // before the withdrawal stays stored and readable. - w.corpus.withdraw("item:nibu:1").unwrap(); - let mut fresh = good(); - fresh.id = PacketId::parse("packet:00000000000000aa").unwrap(); - let withdrawn = StoreRefusal::WithdrawnCorpusItem { - key: "item:nibu:1".into(), - }; - assert_eq!( - refusal(store.admit(fresh, &w.inventory, &w.corpus, &w.entities)), - withdrawn - ); - assert_eq!( - refusal(store.admit(good(), &w.inventory, &w.corpus, &w.entities)), - withdrawn - ); - assert_eq!(store.all().unwrap().len(), 1); - assert_eq!(store.get(PACKET_ID).unwrap(), Some(good())); } #[test] @@ -1270,21 +1206,6 @@ fn seed_reseeded() { .all() .unwrap(); assert_eq!(items.len(), 3); - // The committed inventory is the seed, reseeded (stance features in, the - // displayed-affect features out), then the nibu-proof revision. - let committed = InventoryStore::open(&root.join("data/interlingua/inventory.cc")) - .unwrap() - .inventory() - .unwrap(); - let (rev, mut entries) = seed(); - let (proof, proof_entries) = weksa::inventory::proof_revision(); - assert!(committed.has_revision(rev.n) && committed.has_revision(proof.n)); - entries.extend(proof_entries); - let mut have: Vec<_> = committed.entries().cloned().collect(); - let mut want = entries; - have.sort_by_key(|e| e.id); - want.sort_by_key(|e| e.id); - assert_eq!(have, want); let d = dir(); let mut replay = CorpusStore::open(&d.path().join("c.cc")).unwrap(); for i in items { diff --git a/crates/weksa/tests/validate.rs b/crates/weksa/tests/validate.rs index 3453376..cdf7644 100644 --- a/crates/weksa/tests/validate.rs +++ b/crates/weksa/tests/validate.rs @@ -1,7 +1,7 @@ mod common; use common::*; use weksa::packet::*; -use weksa::{Family, PacketStore, Refusal, Slot, validate}; +use weksa::{Family, Refusal, Slot, validate}; fn refusals(p: &Packet) -> Vec { validate(p, &inventory()).err().unwrap_or_default() @@ -334,83 +334,6 @@ fn v8_feature_applies_to() { assert_eq!(refusals(&pred), vec![]); } -#[test] -fn stance_features_apply_to_the_stance_only() { - // Affect and restraint sit in slot Stance and nowhere else. - assert_eq!(refusals(&good()), vec![]); - for id in [ANGER, RESTRAINT] { - let mut p = good(); - p.discourse - .features - .push(feat(id, FeatValue::Grade(grade(1)))); - assert_eq!( - refusals(&p), - vec![Refusal::V8NotApplicable { - feature: iv(id), - slot: Slot::Discourse - }] - ); - } - let mut referent = good(); - referent.referents[0] - .features - .push(feat(AFFECTION, FeatValue::Grade(grade(1)))); - assert_eq!( - refusals(&referent), - vec![Refusal::V8NotApplicable { - feature: iv(AFFECTION), - slot: Slot::Referent - }] - ); - let mut predication = good(); - predication.predications[0] - .features - .push(feat(ANGER, FeatValue::Grade(grade(1)))); - assert_eq!( - refusals(&predication), - vec![Refusal::V8NotApplicable { - feature: iv(ANGER), - slot: Slot::Predication - }] - ); - // Hedge is a stance on content, not a felt state: not in the stance. - let mut p = good(); - p.stance - .features - .push(feat(HEDGE, FeatValue::Grade(grade(1)))); - assert_eq!( - refusals(&p), - vec![Refusal::V8NotApplicable { - feature: iv(HEDGE), - slot: Slot::Stance - }] - ); - // A stance feature takes its inventory's value kind (V7) and must exist (V5). - let mut flag = good(); - flag.stance.features = vec![feat(ANGER, FeatValue::Flag(true))]; - assert_eq!( - refusals(&flag), - vec![Refusal::V7ValueKind { what: ANGER.into() }] - ); - let mut missing = good(); - missing.stance.features = vec![feat("feature:99", FeatValue::Grade(grade(1)))]; - assert_eq!(refusals(&missing), vec![unresolved("feature:99")]); - // A default stance is valid: the packet records no felt state. - let mut none = good(); - none.stance = Stance::default(); - assert_eq!(refusals(&none), vec![]); -} - -#[test] -fn displayed_formality_warmth_and_intensity_are_not_in_the_inventory() { - // Felt affect is the packet's; how it is displayed is each language's - // ontology's, so no feature carries it. - let inv = inventory(); - for gone in ["warmth", "formality", "intensity"] { - assert!(inv.entries().all(|e| e.gloss != gone), "{gone}"); - } -} - #[test] fn v10_rev_exists() { let mut p = good(); @@ -435,12 +358,7 @@ fn lowering_input_carries_no_trace_id_or_provenance() { let mut p = good(); p.trace.notes = vec!["SECRET-NOTE".into()]; p.trace.rejected_readings = vec!["SECRET-READING".into()]; - // Only the packet store's door yields the view. - let w = world(); - let mut store = PacketStore::open(&w.dir.path().join("p.cc")).unwrap(); - let input = store - .admit(p.clone(), &w.inventory, &w.corpus, &w.entities) - .unwrap(); + let input = validate(&p, &inventory()).unwrap(); let seen = format!("{input:?}"); for hidden in [PACKET_ID, ENCODER_ID, "item:nibu:1", "SECRET"] { assert!(!seen.contains(hidden), "{hidden} reached the input: {seen}"); @@ -455,9 +373,6 @@ fn lowering_input_carries_no_trace_id_or_provenance() { assert_eq!(input.context(), &p.context); assert_eq!(input.discourse(), &p.discourse); assert_eq!(input.constraints(), &p.constraints); - // The speaker's stance is meaning, not provenance: it reaches the lowerer. - assert_eq!(input.stance(), &p.stance); - assert!(!p.stance.features.is_empty()); } #[test] @@ -520,14 +435,6 @@ fn v11_duplicate_feature_each_slot() { refusals(&relation), vec![v11(Some(Slot::Relation), FEAT_ALL)] ); - let mut stance = good(); - stance.stance.features = vec![grade_feature(1), grade_feature(2)]; - assert_eq!(refusals(&stance), vec![v11(Some(Slot::Stance), FEAT_ALL)]); - let mut felt = good(); - felt.stance - .features - .push(feat(ANGER, FeatValue::Grade(grade(5)))); - assert_eq!(refusals(&felt), vec![v11(Some(Slot::Stance), ANGER)]); let mut extension = good(); extension.extensions.push(Extension { key: iv(EXT), diff --git a/data/interlingua/inventory.cc b/data/interlingua/inventory.cc index becfc92..77e437b 100644 Binary files a/data/interlingua/inventory.cc and b/data/interlingua/inventory.cc differ diff --git a/docs/interlingua-standard.md b/docs/interlingua-standard.md index 8b36983..a59a963 100644 --- a/docs/interlingua-standard.md +++ b/docs/interlingua-standard.md @@ -127,7 +127,6 @@ Packet constraints { preserve, deterministic, max_latency_ms } extensions [ { key: InvId (family extension), value: Grade | Option | Flag } ] trace { notes, rejected_readings } - stance { features } ``` `schema_version` and `inventory_rev` name the exact schema and inventory @@ -136,10 +135,8 @@ revision a packet was written against. A grade is an integer permille, ### Validation -A packet exists only if the validator's rules pass and the packet store admits -it. `validate` judges and returns `Ok(())` or the refusals; the packet store's -`admit` is the one door and the only source of the lowering view. A refusal -names the rule. +A packet exists only if `validate` passes; every path that admits or lowers a +packet calls it. A refusal names the rule. - V1: `schema_version` is the version these types define. - V2: at least one of `referents`, `predications`, `relations` is present. @@ -161,11 +158,9 @@ names the rule. (a calibration constant, capacity and not meaning). V9 is retired and not renumbered: the form of a key or a grade is its type, and -an extension key's family is V6. `PacketStore::admit` returns the lowering view of a -packet it admits; the view carries the meaning and the stance, no packet id, no -provenance and no trace, and is built nowhere else. The store also refuses a -packet that names a withdrawn corpus item (packets admitted before the -withdrawal stay stored). A packet is admitted against the inventory the packet store +an extension key's family is V6. `validate` returns the lowering view of a +packet that passes; it carries no packet id, no provenance and no trace, and is +built nowhere else. A packet is admitted against the inventory the packet store reads at the packet's revision, never one the caller supplies. ### `kind` @@ -187,22 +182,9 @@ ids. ### `discourse` Why the packet is said: `speech_act` is a speech-act id, `addressee` a node id, -and `features` carry source-side nuance on content (hedge, certainty, irony and -whatever the inventory grows). This is source-side communicative pressure, not -target-language register. - -### `stance` - -The speaker's felt affect and how much of it the speaker holds back. `features` -is a feature list in slot `Stance`, checked like every other (V5 to V8, V11). -The seed carries felt affect as grades of how strongly it is felt (anger, fear, sadness, -joy, disgust, surprise, contempt, affection, shame) and `restraint`, a grade for -how much of the felt affect is suppressed (0 none, 1000 fully). The stance is -what is felt, never what is displayed: how a feeling shows, and how much of a -restrained one leaks, are cultural, and each language's ontology maps felt -affect and restraint to its own register. A held-back feeling is not a claim -about the world, so it is never a predication. An empty stance is valid. The -stance reaches the lowerer in the lowering view. +and `features` carry source-side nuance (hedge, certainty, warmth, formality, +intensity, irony and whatever the inventory grows). This is source-side +communicative pressure, not target-language register. ### `referents` diff --git a/schemas/cultnet/weksa.interlingua_packet.schema.json b/schemas/cultnet/weksa.interlingua_packet.schema.json index d839203..24d7507 100644 --- a/schemas/cultnet/weksa.interlingua_packet.schema.json +++ b/schemas/cultnet/weksa.interlingua_packet.schema.json @@ -55,9 +55,6 @@ "format": "uint32", "minimum": 0 }, - "stance": { - "$ref": "#/$defs/Stance" - }, "trace": { "$ref": "#/$defs/Trace" } @@ -75,8 +72,7 @@ "relations", "constraints", "extensions", - "trace", - "stance" + "trace" ], "$defs": { "Constraints": { @@ -450,21 +446,6 @@ "filler" ] }, - "Stance": { - "description": "The speaker's felt affect and restraint, never displayed affect: how the\nspeaker shows a feeling is each language's ontology's business. A\nheld-back feeling is not a claim, so it is never a predication.", - "type": "object", - "properties": { - "features": { - "type": "array", - "items": { - "$ref": "#/$defs/FeatureValue" - } - } - }, - "required": [ - "features" - ] - }, "Trace": { "description": "Authoring and debug notes. Stripped before lowering.", "type": "object", diff --git a/schemas/cultnet/weksa.inventory_entry.schema.json b/schemas/cultnet/weksa.inventory_entry.schema.json index ffefd95..6187f02 100644 --- a/schemas/cultnet/weksa.inventory_entry.schema.json +++ b/schemas/cultnet/weksa.inventory_entry.schema.json @@ -74,8 +74,7 @@ "Discourse", "Referent", "Predication", - "Relation", - "Stance" + "Relation" ] }, "ValueKind": {