From 5622e825b2b3fb74c7a585485291a319fbc948d4 Mon Sep 17 00:00:00 2001 From: Weksa Date: Sat, 14 Nov 2026 04:07:48 +0000 Subject: [PATCH 1/3] V18: validate refuses a request_intervention predication the addressee consent does not name Co-Authored-By: Claude Sonnet 5.5 --- crates/weksa/src/validate.rs | 32 +++++++++++++++++++++++++++++++- crates/weksa/tests/display.rs | 1 + docs/interlingua-standard.md | 1 + 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/crates/weksa/src/validate.rs b/crates/weksa/src/validate.rs index 49c670f..71c92d5 100644 --- a/crates/weksa/src/validate.rs +++ b/crates/weksa/src/validate.rs @@ -4,7 +4,7 @@ //! lowerer sees, comes from `PacketStore::admit` alone, which builds it from //! the admitted packet and its corpus item through the crate-private //! `LoweringInput::of`. The stance is checked like every feature -//! list, in slot `Stance`. The rules are V1..V8, V10..V12 and V14..V17: the +//! list, in slot `Stance`. The rules are V1..V8, V10..V12 and V14..V18: the //! shape of an id or a grade is its type's parse, not a rule, so V9 is retired //! and not renumbered; V13 is retired too. [`LoweringInput::with_scene`] reads the //! scene of the view's own item group from the speaker-state store, and @@ -120,6 +120,12 @@ pub enum Refusal { V17AddresseeNotAReferent { id: NodeId, }, + /// V18: a predication whose frame is the inventory's `request_intervention`, + /// with no `addressee_consent` naming it. It says what the addressee did, so + /// only the packet's own record may license it. + V18RequestWithoutConsent { + predication: NodeId, + }, } impl Refusal { @@ -144,6 +150,7 @@ impl Refusal { Refusal::V15ConsentSource { .. } => "V15", Refusal::V16SpeakerNotInScene => "V16", Refusal::V17AddresseeNotAReferent { .. } => "V17", + Refusal::V18RequestWithoutConsent { .. } => "V18", } } } @@ -173,6 +180,9 @@ impl fmt::Display for Refusal { "the scene does not cover the speaker of every live item of its group" } Refusal::V17AddresseeNotAReferent { .. } => "the addressee names no referent", + Refusal::V18RequestWithoutConsent { .. } => { + "a request frame needs the addressee's consent naming it" + } }; write!(f, "{}: {what}", self.rule()) } @@ -221,6 +231,7 @@ pub fn validate(packet: &Packet, inventory: &Inventory) -> Result<(), Vec, out: &mut Vec) { + for p in &packet.predications { + if inventory + .resolve(&p.frame, packet.inventory_rev) + .is_some_and(|e| e.gloss == REQUEST_FRAME_GLOSS) + && packet.discourse.addressee_consent.as_ref() != Some(&p.id) + { + out.push(Refusal::V18RequestWithoutConsent { + predication: p.id.clone(), + }); + } + } +} + fn node_lists(packet: &Packet) -> [(&Vec, &'static str); 3] { [ (&packet.context.participants, "participant"), diff --git a/crates/weksa/tests/display.rs b/crates/weksa/tests/display.rs index e82b5fb..de4369c 100644 --- a/crates/weksa/tests/display.rs +++ b/crates/weksa/tests/display.rs @@ -104,6 +104,7 @@ fn refusal_display_names_the_rule_and_no_identifier() { }, Refusal::V16SpeakerNotInScene, Refusal::V17AddresseeNotAReferent { id: node() }, + Refusal::V18RequestWithoutConsent { predication: node() }, ]; assert_eq!( Refusal::V16SpeakerNotInScene.to_string(), diff --git a/docs/interlingua-standard.md b/docs/interlingua-standard.md index cfed562..80e6faa 100644 --- a/docs/interlingua-standard.md +++ b/docs/interlingua-standard.md @@ -164,6 +164,7 @@ names the rule. `GameTelemetry`; a decomposition of text or imported corpus text cannot mint it. - V17: an `addressee` names a referent, not a predication. +- V18: a predication whose frame is the inventory's `request_intervention` is named by `addressee_consent`. 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 From fb96348d0244b4b0c3463ee6e7ed9bb7cb79f072 Mon Sep 17 00:00:00 2001 From: Weksa Date: Sat, 14 Nov 2026 04:07:48 +0000 Subject: [PATCH 2/3] Pin V18: validate cases and the encoder-shaped request through admit Co-Authored-By: Claude Sonnet 5.5 --- crates/weksa/tests/context.rs | 35 +++++++++++++++++++++++++ crates/weksa/tests/validate.rs | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/crates/weksa/tests/context.rs b/crates/weksa/tests/context.rs index d2550de..a8993fc 100644 --- a/crates/weksa/tests/context.rs +++ b/crates/weksa/tests/context.rs @@ -593,6 +593,41 @@ mod lowering { assert!(Prompt::lower(&read, &item, &away(), "en-US", &inv, &held, &empty).is_ok()); } + /// The packet the encoder emits for a request: frame:3, no consent, source + /// ModelDecomposition. Admission refuses it, so no lowering view exists. + #[test] + fn an_encoder_request_never_reaches_a_lowerer() { + let cast = InventoryStore::open(&data("interlingua/inventory.cc")).unwrap(); + let entities = EntityStore::open(&data("entity/entity.cc")).unwrap(); + let corpus = CorpusStore::open(&data("corpus/corpus.cc")).unwrap(); + let dir = tempfile::tempdir().unwrap(); + let mut store = PacketStore::open(&dir.path().join("p.cc")).unwrap(); + let mut packet = proof_packet(); + packet.provenance.source = weksa::packet::ProvenanceSource::ModelDecomposition; + packet.predications[0].frame = weksa::InvId::parse("frame:3").unwrap(); + match store.admit(packet.clone(), &cast, &corpus, &entities) { + Err(weksa::StoreError::Invalid(r)) => assert_eq!( + r, + vec![weksa::Refusal::V18RequestWithoutConsent { + predication: weksa::packet::NodeId::parse("n4").unwrap() + }] + ), + other => panic!("expected V18, got {:?}", other.map(|_| ())), + } + // Not blanket: telemetry that records the consent admits and lowers. + packet.provenance.source = weksa::packet::ProvenanceSource::GameTelemetry; + packet.discourse.addressee_consent = Some(weksa::packet::NodeId::parse("n4").unwrap()); + drop(store); + let view = admitted(packet, &cast, "corpus/corpus.cc", &entities); + let item = CorpusItemId::parse("item:nibu:1").unwrap(); + let held = Held(vec![nibu(&[("en-US", &["Nibu"])])]); + let empty = weksa::Ontology::empty(); + assert!( + Prompt::lower(&view, &item, &away(), "en-US", &cast.inventory().unwrap(), &held, &empty) + .is_ok() + ); + } + fn dao(index: usize) -> (LoweringInput, Inventory, EntityStore, CorpusItemId) { let inv = InventoryStore::open(&data("daodejing/inventory.cc")).unwrap(); let entities = EntityStore::open(&data("daodejing/entity.cc")).unwrap(); diff --git a/crates/weksa/tests/validate.rs b/crates/weksa/tests/validate.rs index b46cd85..057e92f 100644 --- a/crates/weksa/tests/validate.rs +++ b/crates/weksa/tests/validate.rs @@ -740,6 +740,53 @@ fn a_source_that_only_read_wording_cannot_set_consent() { } } +// V18: a request frame is licensed by the packet's own consent record. + +/// The committed inventory, where `frame:3` is `request_intervention`. +fn cast_refusals(p: &Packet) -> Vec { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/interlingua/inventory.cc"); + let inv = weksa::InventoryStore::open(&path).unwrap().inventory().unwrap(); + validate(p, &inv).err().unwrap_or_default() +} + +fn requesting() -> Packet { + let mut p = proof_packet(); + p.predications[0].frame = iv("frame:3"); + p.discourse.addressee_consent = Some(nd("n4")); + p +} + +#[test] +fn a_request_frame_needs_consent_naming_its_predication() { + assert_eq!(cast_refusals(&requesting()), vec![]); + let mut p = requesting(); + p.discourse.addressee_consent = None; + assert_eq!( + cast_refusals(&p), + vec![Refusal::V18RequestWithoutConsent { predication: nd("n4") }] + ); + p.discourse.addressee_consent = Some(nd("n5")); + assert_eq!( + cast_refusals(&p), + vec![Refusal::V18RequestWithoutConsent { predication: nd("n4") }] + ); + let mut p = requesting(); + p.provenance.source = ProvenanceSource::GameTelemetry; + assert_eq!(cast_refusals(&p), vec![]); +} + +#[test] +fn model_source_consent_is_v15_alone() { + let mut p = requesting(); + p.provenance.source = ProvenanceSource::ModelDecomposition; + assert_eq!( + cast_refusals(&p), + vec![Refusal::V15ConsentSource { + source: ProvenanceSource::ModelDecomposition + }] + ); +} + // V17: the addressee is someone spoken to, a referent. #[test] From 879a917a22bfee2c77c6002b247f9b61876a4e8e Mon Sep 17 00:00:00 2001 From: Weksa Date: Sat, 14 Nov 2026 04:20:57 +0000 Subject: [PATCH 3/3] Docs: V18 replaces the open consent gap; name the residual gaps Co-Authored-By: Claude Sonnet 5.5 --- docs/interlingua-standard.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/interlingua-standard.md b/docs/interlingua-standard.md index 80e6faa..a24b02c 100644 --- a/docs/interlingua-standard.md +++ b/docs/interlingua-standard.md @@ -198,14 +198,16 @@ whatever the inventory grows). This is source-side communicative pressure, not target-language register. `addressee_consent` is no feature: it names the predication the addressee asked for or agreed to, a fact about what the addressee did. `V14` and `V15` check the field: it may be set only by authoring or game -telemetry, and it must name a predication of the packet. Open gap: `frame:3` -(`request_intervention`) is in the committed inventories (`data/interlingua`, -`data/original`, `data/daodejing`) and seeded at `inventory.rs:518`. No -committed packet uses it, but the encoder sees it, and `V14` and `V15` check only -the consent field, so a model-encoded `frame:3` predication with a null -`addressee_consent` reaches `Prompt::lower` and nothing refuses it. The retired -gate covered only the deleted `lower()`. Follow-up -`table-retirement-consent-gap` owns the gap. +telemetry, and it must name a predication of the packet. `V18` refuses a predication whose frame resolves, at the packet's revision, to +the gloss `request_intervention` (`frame:3` in the committed inventories) +unless `addressee_consent` names that predication. The encoder can never set +consent (`V15`), so an encoder-produced request frame is always refused. +Residual gaps: `V18` keys on that one gloss, so another frame that asserts the +addressee's act (for example a `frame:2` intervention on the original +inventory) or a renamed gloss is not covered; consent is one field, so two +request predications in one packet cannot validate; and the check that the +asker is the addressee was not kept. Follow-up +`weksa-lowering:follow_up:request-frame-consent-residual` owns them. ### `stance`