Pin the read-side coverage filters; V16 says the scene covers every live speaker of the group

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Weksa 2026-11-10 08:54:28 +00:00
commit 7a803f5cb5
4 changed files with 37 additions and 3 deletions

View file

@ -112,7 +112,8 @@ pub enum Refusal {
V15ConsentSource {
source: ProvenanceSource,
},
/// V16: `with_scene` found the item's speaker absent from its group's scene.
/// V16: `with_scene` found its group's scene not covering the item's speaker
/// or the speaker of some live item of the group.
V16SpeakerNotInScene,
/// V17: an addressee that names a predication. An act is not someone
/// spoken to, so the addressee names a referent.
@ -168,7 +169,9 @@ impl fmt::Display for Refusal {
Refusal::V14ConsentWithoutAddressee => "consent given without an addressee",
Refusal::V14ConsentNotAPredication { .. } => "consent names no predication",
Refusal::V15ConsentSource { .. } => "consent set by a source that only read wording",
Refusal::V16SpeakerNotInScene => "the speaker is not a participant of the scene",
Refusal::V16SpeakerNotInScene => {
"the scene does not cover the speaker of every live item of its group"
}
Refusal::V17AddresseeNotAReferent { .. } => "the addressee names no referent",
};
write!(f, "{}: {what}", self.rule())

View file

@ -125,6 +125,10 @@ fn refusal_display_names_the_rule_and_no_identifier() {
Refusal::V16SpeakerNotInScene,
Refusal::V17AddresseeNotAReferent { id: node() },
];
assert_eq!(
Refusal::V16SpeakerNotInScene.to_string(),
"V16: the scene does not cover the speaker of every live item of its group"
);
for r in &all {
assert!(r.to_string().starts_with(r.rule()), "{r:?}");
}

View file

@ -381,3 +381,30 @@ fn read_refuses_a_scene_that_stopped_covering_a_live_speaker_of_its_group() {
SceneRead::NoAddressee { rev: 2 }
);
}
#[test]
fn only_live_speakers_of_the_views_group_and_the_views_own_speaker_must_be_covered() {
let mut w = world();
put(&mut w, scene("g", 1, &["nibu"], vec![]));
let ok = SceneRead::NoAddressee { rev: 1 };
// A withdrawn speaker the scene lacks does not count.
let mut gone = item_in("item:brokkr:1", "Hello.", "g", cast());
gone.speaker = eid("brokkr");
w.corpus.add(gone, &w.entities).unwrap();
w.corpus.withdraw("item:brokkr:1").unwrap();
assert_eq!(read(&w, view_of(&w, 3, None)), ok);
// Nor does a live speaker the scene lacks in another group.
let mut other = item_in("item:brokkr:2", "Hello you.", "h", cast());
other.speaker = eid("brokkr");
w.corpus.add(other, &w.entities).unwrap();
assert_eq!(read(&w, view_of(&w, 3, None)), ok);
// The view's own speaker must be listed even when its item was withdrawn
// after the view was built: scene g@2 lists Brokkr alone.
let view = view_of(&w, 3, None);
w.corpus.withdraw("item:nibu:1").unwrap();
put(&mut w, scene("g", 2, &["brokkr"], vec![]));
assert_eq!(
rules(view.with_scene(&inv(&w), &w.state, &w.corpus)),
["V16"]
);
}

View file

@ -400,7 +400,7 @@ One line per cut; specs exist for the cuts about to run (2d and 4, siblings on `
2d. `entity-door-lows`: invisible format characters refused in entity forms (ZWJ, ZWNJ, CGJ and variation selectors kept); the form length bound per question `form-word-bound` (recommended: bytes only); attested origins (`Cast { persona, fixture }`, `Live { persona }`) and `CastAuthored.author`; empty provenance strings refused at the store; S6 under NFKC plus case fold and no-alphanumeric texts refused; `next_key` refuses at `u32::MAX`; CLI flags that do not apply refuse and no error echoes a value. Owns follow-ups `entity-door-lows` and `s6-unicode-normal-form`. (spec r1)
3b. `lowerer-first-cut` (follow-up `golf-loop-lowerer-cut`): the section above. `lower`, `weksa lower`, `weksa score`, `weksa packet proof`, inventory rev 2 for `nibu-proof-001`.
4. `speaker-state`: packet `stance` (felt affect and restraint, slot `Stance`, packet schema v4); formality, warmth and intensity out of the seed and the standard; only `PacketStore::admit` yields a `LoweringInput`, and it refuses packets for withdrawn items (follow-up `lowering-input-from-packet-store`). (spec r1)
4a. `speaker-relation-input`: slot `Scene` and its first seed features (`relative_status`, `intimacy`, `in_group`; inventory revision 3, now `speaker_scene_revision`); `LoweringInput::with_scene` carries the speaker's relation to the addressee as typed `Scene` feature values. Since 4b it takes the `SpeakerStateStore`, reads the scene of the view's own item group for the item's speaker, and selects the relation by the addressee referent's entity (V16 when the item's speaker is not a participant; V17 refuses an addressee that names a predication; V13 is retired). The English lowerer does not render it. (PR for follow-up `chat-s3352-3352`)
4a. `speaker-relation-input`: slot `Scene` and its first seed features (`relative_status`, `intimacy`, `in_group`; inventory revision 3, now `speaker_scene_revision`); `LoweringInput::with_scene` carries the speaker's relation to the addressee as typed `Scene` feature values. Since 4b it takes the `SpeakerStateStore` and the `CorpusStore`, reads the scene of the view's own item group for the item's speaker, and selects the relation by the addressee referent's entity (V16 when the scene does not list the item's speaker or the speaker of any live item of the group, the rule `put_scene` applies too; V17 refuses an addressee that names a predication; V13 is retired). The English lowerer does not render it. (PR for follow-up `chat-s3352-3352`)
4b. `speaker-scene` (spec r1; `speaker-scene-seeds` is its cut 4c: CLI, inventory revision 3, cast speakers and scenes; `Party::Unnamed` covers an addressee with no entity; Speaker and Scene share one `SpeakerStateStore`): slots `Speaker` and `Scene` with their seed features; `weksa.speaker` (`speaker:<entity-slug>@<n>`) and `weksa.scene` (`scene:<group>@<n>`, participants and ordered-pair relations) documents with `Annotator` `Operator` and `Model { family, generator }`, validated by the packet's shared feature check, append-only per key; their stores, `weksa speaker` and `weksa scene` commands, published schemas; Model-annotated cast speakers and scenes for the committed items. Waits on 2d and 4; `pd-speaker-annotation` blocks only the public-domain annotator. Part 1 landed (branch `weksa-lowering/speaker-scene`): `speaker.rs`, `SpeakerStateStore`, slots `Speaker` and `Scene` exclusive to their own feature lists, `speaker_scene_revision()` (Speaker features 22-24), `with_scene` on the store, schemas. The CLI, the committed inventory revision 3 and the seeds stay with 4c.
5. `language-ontology`: `weksa.community` from `data/cultural-ontology/` (owns follow-up `edda-referent-yaml-parse`), `weksa.ontology_entry` for en-US and ja-JP from `data/target-language-ontology/` (YAML deleted), speaker communities, deterministic `select`, the lowering view (packet without trace, speaker, scene, selected entries). Waits on 4.
6. `pd-importer`: public-domain play and fiction dialogue into the corpus with speaker, scene group, edition provenance, and Speaker and Scene annotations by the ruled annotator. Waits on 4, follow-up `gutenberg-terms` and question `pd-speaker-annotation`.