language-ontology-scene r2: a run reads entries and scenes at its own head, default bed is seed+proof+scene rev 3 (no Dao), politeness keyword guard replaced by model readings, nine seed entries reworded

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Weksa 2026-11-12 17:11:57 +00:00
commit 139fa94b29
12 changed files with 146 additions and 77 deletions

View file

@ -100,7 +100,9 @@ pub struct OntologyEntry {
/// The entry's number within its language, from 1.
#[cultcache(key = 1)]
pub n: u32,
/// The inventory revision the conditions read against.
/// The revision, of the inventory the entry was admitted through, at which the
/// store read its conditions: provenance only; a run reads the conditions'
/// features at its own head.
#[cultcache(key = 2)]
pub inventory_rev: u32,
#[cultcache(key = 3)]

View file

@ -43,7 +43,6 @@ use crate::corpus::{CorpusItem, CorpusItemId, ModelFamily, Source};
use crate::encode::{EditDraft, LoweringDraft, PacketDraft, encoder_id};
use crate::form::{is_attestation, is_language_tag, is_slug};
use crate::model::{ModelPort, PortError};
use crate::ontology::OntologyEntry;
use crate::packet::{Packet, Stance};
use crate::score::{CHRF_VERSION, chrf, Embedder, ItemScore, ScoreInput, Scorer, SplitMean, aggregate};
use crate::split::{Split, SplitScheme, fnv1a64, fnv1a64_of, hex16};
@ -807,46 +806,34 @@ impl RunStores {
}
/// Every ontology entry of a panel language reads against the run's head
/// inventory at its own revision: the check the store applied at admission,
/// applied again to this inventory.
/// inventory: every condition's feature is live at the head in the
/// condition's slot and fits its test. An entry's `inventory_rev` names a
/// revision of the inventory it was admitted through, and beds number
/// revisions independently, so a run never reads it.
fn check_ontology(&self) -> Result<(), RunError> {
let inventory = self.inventory.inventory().map_err(|_| RunError::Store)?;
let entries = self.ontology.all().map_err(|_| RunError::Store)?;
let head = inventory.latest().map(|r| r.n);
entries
.iter()
.filter(|e| self.record.panel.contains(&e.language))
.try_for_each(|e| {
// At its own revision, and again at the head: an entry on a
// feature the head retired could never select.
let at_head = OntologyEntry {
inventory_rev: head.ok_or(RunError::Ontology)?,
..e.clone()
};
check_ontology_entry(e, &inventory)
.and_then(|()| check_ontology_entry(&at_head, &inventory))
.map_err(|_| RunError::Ontology)
let head = inventory.latest().map(|r| r.n).ok_or(RunError::Ontology)?;
check_ontology_entry(e, &inventory, head).map_err(|_| RunError::Ontology)
})
}
/// Every scene's relation features read against the run's inventory at the
/// scene's own revision, and again at the head: the checks `put_scene`
/// applied, applied again to this inventory.
/// Every scene's relation features read against the run's head inventory
/// in slot `Scene`, as [`check_ontology`](Self::check_ontology) reads
/// entries: the scene's `inventory_rev` is admission provenance a run never reads.
fn check_scenes(&self) -> Result<(), RunError> {
let inventory = self.inventory.inventory().map_err(|_| RunError::Store)?;
let scenes = self.speaker_state.all_scenes().map_err(|_| RunError::Store)?;
for scene in &scenes {
let head = inventory.latest().map(|r| r.n).ok_or(RunError::SpeakerState)?;
for r in &scene.relations {
for rev in [scene.inventory_rev, head] {
if !feature_refusals(&inventory, rev, &r.features, Slot::Scene).is_empty() {
return Err(RunError::SpeakerState);
}
}
}
if scene.relations.is_empty()
&& !(inventory.has_revision(scene.inventory_rev) && inventory.has_revision(head))
{
if scenes.is_empty() {
return Ok(());
}
let head = inventory.latest().map(|r| r.n).ok_or(RunError::SpeakerState)?;
for r in scenes.iter().flat_map(|s| &s.relations) {
if !feature_refusals(&inventory, head, &r.features, Slot::Scene).is_empty() {
return Err(RunError::SpeakerState);
}
}

View file

@ -71,6 +71,8 @@ pub struct Scene {
/// The revision, from 1.
#[cultcache(key = 1)]
pub n: u32,
/// The revision, of the inventory the scene was admitted through, at which
/// `put_scene` read its features: provenance only; a run never reads it.
#[cultcache(key = 2)]
pub inventory_rev: u32,
#[cultcache(key = 3)]

View file

@ -1273,7 +1273,7 @@ impl OntologyStore {
return Err(malformed(&key, "a language holds at most 64 entries"));
}
counted.insert(e.language.as_str(), e.n);
check_ontology_entry(e, &inv)?;
check_ontology_entry(e, &inv, e.inventory_rev)?;
}
let mut staged = Vec::new();
for e in &batch {
@ -1286,13 +1286,14 @@ impl OntologyStore {
/// The rules of one ontology entry against an inventory: the annotator, the
/// bounded and normalized text of the choice and examples, the condition count
/// and each condition's feature (slot `Stance` or `Discourse`, a grade feature
/// for `AtLeast` and `AtMost`, an option or flag value for `Is`) read at the
/// entry's inventory revision. The store applies it on admission and a run
/// applies it again against its own head inventory.
/// and each condition's feature (slot `Stance`, `Discourse` or `Scene`, a grade
/// feature for `AtLeast` and `AtMost`, an option or flag value for `Is`) read at
/// `rev`. The store passes the entry's own `inventory_rev` on admission; a run
/// passes its inventory's head, because beds number revisions independently.
pub(crate) fn check_ontology_entry(
e: &OntologyEntry,
inventory: &Inventory,
rev: u32,
) -> Result<(), StoreError> {
let key = e.key();
check_annotator(&key, &e.annotator)?;
@ -1331,13 +1332,13 @@ pub(crate) fn check_ontology_entry(
}
out.extend(feature_refusals(
inventory,
e.inventory_rev,
rev,
&[c.as_value()],
c.slot,
));
}
if e.when.is_empty() && !inventory.has_revision(e.inventory_rev) {
out.extend(feature_refusals(inventory, e.inventory_rev, &[], Slot::Stance));
if e.when.is_empty() && !inventory.has_revision(rev) {
out.extend(feature_refusals(inventory, rev, &[], Slot::Stance));
}
if !out.is_empty() {
return Err(StoreError::Invalid(out));

View file

@ -5047,3 +5047,70 @@ fn create_refuses_a_scene_its_inventory_cannot_read() {
assert!(!root.join("run.cc").exists());
assert!(!root.join("inventory.cc").exists());
}
#[test]
fn a_run_reads_entries_and_scenes_at_its_own_head() {
use weksa::{Condition, Layer, OntologyEntry, OntologyStore, Slot, Test};
// An entry and a scene admitted at rev 4 through a Dao-shaped inventory open
// a run whose own inventory is seed, proof and the scene revision as rev 3:
// beds number revisions independently, so a run reads features at its head.
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/daodejing");
let dir = tempfile::tempdir().unwrap();
for (from, to) in [("corpus.cc", "c.cc"), ("entity.cc", "e.cc")] {
std::fs::copy(src.join(from), dir.path().join(to)).unwrap();
}
let rich = dir.path().join("rich.cc");
common::dao_rev3_at(&rich);
let state = common::speaker_state_in(dir.path());
put_dao_scene(&state, &rich, &dir.path().join("c.cc"), &dir.path().join("e.cc"));
let rich_store = InventoryStore::open(&rich).unwrap();
let status = rich_store.inventory().unwrap().entries().find(|e| e.gloss == "relative_status").unwrap().id;
let ontology = common::ontology_in(dir.path());
OntologyStore::open(&ontology)
.unwrap()
.admit(
vec![OntologyEntry {
language: "en-US".into(),
n: 1,
inventory_rev: 4,
layer: Layer::Discourse,
when: vec![Condition {
slot: Slot::Scene,
feature: status,
test: Test::Is(weksa::packet::FeatValue::Option(common::lab("above"))),
}],
choice: "ENABOVE".into(),
examples: vec![],
annotator: weksa::Annotator::Operator,
}],
&rich_store,
)
.unwrap();
let head = dir.path().join("i.cc");
let mut own = InventoryStore::open(&head).unwrap();
for (rev, entries) in [
weksa::inventory::seed(),
weksa::inventory::proof_revision(),
weksa::inventory::speaker_scene_revision(3),
] {
own.admit_revision(rev, entries).unwrap();
}
drop(own);
let run = RunStores::create(
&dir.path().join("run"),
"x",
RunPaths {
corpus: &dir.path().join("c.cc"),
inventory: &head,
entities: &dir.path().join("e.cc"),
ontology: &ontology,
speaker_state: &state,
},
"fake-model",
"bag",
weksa::run::LowererReach::ToolLessRelay,
ModelFamily::Unrecorded,
&["en-US".to_string()],
);
assert!(run.is_ok(), "{:?}", run.err());
}

View file

@ -314,11 +314,12 @@ fn committed_ontology_is_model_drafted_and_reads_every_inventory() {
"{lang} has no Stance-conditioned entry"
);
}
// Every committed inventory bed, the default one included, carries the
// revisions the entries bind to: a run opens over each with its panel.
// The default bed (data/interlingua) is read with the original corpus,
// entities and scenes, as `loop open` with its defaults and `--corpus
// data/original` does.
// A run opens over every committed inventory bed with its panel. The default
// bed (data/interlingua) numbers the scene revision 3 and holds no Dao
// revision; the Dao beds number it 4. Entries and scenes admitted at 4 open
// over both because a run reads features at its own head. The default bed is
// read with the original corpus, entities and scenes, as `loop open` with its
// defaults and `--corpus data/original` does.
for (corpus, bed, panel) in [
("original", "original", &PANEL[..]),
("daodejing", "daodejing", &PANEL[..1]),
@ -349,7 +350,34 @@ fn committed_ontology_is_model_drafted_and_reads_every_inventory() {
ModelFamily::Unrecorded,
&panel.iter().map(|l| l.to_string()).collect::<Vec<_>>(),
)
.unwrap_or_else(|e| panic!("{name}: {e:?}"));
.unwrap_or_else(|e| panic!("{name}: {e:?}"));
}
// Revision numbers are local to a bed, so agreement is proven on the beds:
// every feature an entry condition or a committed scene relation names means
// the same in all three (gloss, value kind, slots, retirement).
let mut named: std::collections::BTreeSet<weksa::InvId> = entries
.iter()
.flat_map(|e| e.when.iter().map(|c| c.feature))
.collect();
let scenes = weksa::SpeakerStateStore::open(&data.join("original/speaker-state.cc")).unwrap();
for s in scenes.all_scenes().unwrap() {
named.extend(s.relations.iter().flat_map(|r| r.features.iter().map(|f| f.feature)));
}
let beds: Vec<_> = ["interlingua", "original", "daodejing"]
.iter()
.map(|b| {
let copy = tmp.path().join(format!("bed-{b}.cc"));
std::fs::copy(data.join(b).join("inventory.cc"), &copy).unwrap();
InventoryStore::open(&copy).unwrap().inventory().unwrap()
})
.collect();
for id in named {
let shape = |inv: &weksa::Inventory| {
let e = inv.entry(&id).unwrap_or_else(|| panic!("{id} is missing from a bed"));
(e.gloss.clone(), e.value_kind.clone(), e.applies_to.clone(), e.retired_rev)
};
assert_eq!(shape(&beds[0]), shape(&beds[1]), "{id}");
assert_eq!(shape(&beds[1]), shape(&beds[2]), "{id}");
}
}
@ -1032,12 +1060,13 @@ fn committed_scenes_cover_original_groups() {
}
/// The committed ontology's Scene-conditioned entries, over every scene relation
/// (relative_status x in_group x intimacy in steps of 50): an entry that names
/// a register or politeness level has a Scene condition, and two Scene-conditioned
/// entries of one language and layer are never selected by the same relation, so
/// the ontology never gives opposing instructions for one relation.
/// (relative_status x in_group x intimacy in steps of 50): two entries of one
/// language and layer are never selected by the same relation, so the ontology
/// never gives two instructions for one relation in one layer. Whether an entry's
/// text keeps a politeness level on the scene is a model reading owed per change,
/// not a test.
#[test]
fn scene_entries_never_co_select_in_a_layer_and_levels_hang_on_the_scene() {
fn scene_entries_never_co_select_in_a_layer() {
use weksa::inventory::Slot;
use weksa::ontology::{Condition, Test};
use weksa::packet::FeatValue;
@ -1050,27 +1079,6 @@ fn scene_entries_never_co_select_in_a_layer_and_levels_hang_on_the_scene() {
let id = |gloss: &str| inv.entries().find(|e| e.gloss == gloss).unwrap().id;
let (status, intimacy, in_group) = (id("relative_status"), id("intimacy"), id("in_group"));
let entries = OntologyStore::open(&onto).unwrap().all().unwrap();
// Terms that name a register or politeness level.
let levels = [
"teineigo", "sonkeigo", "kenjogo", "desu/masu", "honorific", "register",
"politeness", "polite", "formal", "informal", "casual",
];
for e in &entries {
let lower = e.choice.to_lowercase();
if levels.iter().any(|t| lower.contains(t)) {
// The seed entries that say they set no level carry "no politeness" or
// follow the rest of the line; every other naming is Scene-conditioned.
let disclaimed = lower.contains("sets no politeness")
|| lower.contains("politeness follows the rest")
|| lower.contains("politeness follows")
|| lower.contains("own register allows");
assert!(
disclaimed || e.when.iter().any(|c: &Condition| c.slot == Slot::Scene),
"{} names a level without a Scene condition",
e.key()
);
}
}
let holds = |c: &Condition, rel: (&str, u16, bool)| match (c.feature, &c.test) {
(f, Test::Is(FeatValue::Option(l))) if f == status => l.as_str() == rel.0,
(f, Test::AtLeast(g)) if f == intimacy => rel.1 >= g.get(),

View file

@ -1584,8 +1584,9 @@ fn seed_reseeded() {
.unwrap();
assert_eq!(items.len(), 3);
// The committed inventory is the seed, reseeded (stance features in, the
// displayed-affect features out), the nibu-proof revision, the Dao revision
// and the speaker-scene revision: the same four as the other beds.
// displayed-affect features out), the nibu-proof revision and the
// speaker-scene revision as rev 3: never the Dao revision (ruling
// `dao-round-seed-inventory`).
let committed = InventoryStore::open(&root.join("data/interlingua/inventory.cc"))
.unwrap()
.inventory()
@ -1594,8 +1595,8 @@ fn seed_reseeded() {
let (proof, proof_entries) = weksa::inventory::proof_revision();
assert!(committed.has_revision(rev.n) && committed.has_revision(proof.n));
entries.extend(proof_entries);
entries.extend(weksa::daodejing::revision().1);
entries.extend(weksa::inventory::speaker_scene_revision(4).1);
entries.extend(weksa::inventory::speaker_scene_revision(3).1);
assert_eq!(committed.latest().unwrap().n, 3);
let mut have: Vec<_> = committed.entries().cloned().collect();
let mut want = entries;
have.sort_by_key(|e| e.id);

Binary file not shown.

Binary file not shown.

View file

@ -34,10 +34,10 @@ means no store holds it and nothing writes it.
| Inventory entry | Key `<family>:<n>`, `n` canonical decimal >= 1 (families: concept, frame, role, relation, speech_act, feature, referent_kind, extension); type `weksa.inventory_entry`, with a bounded gloss, value kind (option labels bounded), the slots a feature may sit in (`Discourse`, `Referent`, `Predication`, `Relation`; `Stance` from cut `speaker-state`, `Speaker` and `Scene` from cut `speaker-scene`), `introduced_rev` and optional `retired_rev`. Speaker axes, scene relations and affects are features of those slots, so the loop grows them like any closed-class family and convergence covers them | Added or retired by an inventory revision; never edited in place (a changed meaning is a retire plus an add); a retired id still resolves for packets of earlier revisions | The loop's accepted rounds, whose minting call the context builder assembles from Train items only; Self on merge to main. Admission checks form, not text |
| Inventory revision | Key `rev:<n>`, type `weksa.inventory_revision`, with parent, change list and `frozen` | Appended per accepted round; exactly one revision is ever marked frozen, by the convergence test | The loop's accept rule appends; the convergence test freezes; Self records the freeze as a ruling |
| Target profile | Locale or language-project id (`en-US`, `ja-JP`, a conlang's slug). Until entries grow in rounds there is no profile revision document: a run names the profile it lowered with by the ontology store's path and digest in its `RunRecord` (cut `language-ontology`); `profile:<language>:<n>` revision documents arrive with follow-up `language-ontology-growth` | The ontology store is revised by commit; a run pins it and refuses drift; it likewise pins the speaker-state store (`RunRecord` keys 17 and 18, `Drift(SpeakerState)`, cut `language-ontology-scene`) | Profile authors and accepted rounds; never the lowerer's defaults (`persona-drives-register`) |
| Language ontology entry (cut `language-ontology`) | Key `onto:<language>:<n>`, `n` from 1 per language; type `weksa.ontology_entry` in `data/ontology/ontology.cc`: the language, the inventory revision its conditions are read at, a layer (lexicon, grammar, discourse, phonology, orthography), at most four conditions (a feature id of slot `Stance`, `Discourse` or `Scene` with `AtLeast`/`AtMost` a grade or `Is` an option or flag; a `Scene` condition reads the relation of the item's speaker to its addressee that `with_scene` selected; slot `Speaker` and community ids wait on follow-up `language-ontology-communities`), a choice of 1 to 200 bytes, at most three examples of 1 to 120 bytes, and an `Annotator` | Admitted in all-or-nothing batches through `OntologyStore::admit`, immutable, never edited; retire arrives with `language-ontology-growth`. The run pins the store like the entity store (path and digest, `Drift(Ontology)`), never copies it, because no loop step writes it yet. Cut `language-ontology-seed` converts by drafting, not by parse: a fresh model drafts each language's seed from that language's YAML and the inventory's feature lines with no corpus text, then deletes `data/target-language-ontology/` and `.weksa/cultural-ontology/` in the same cut | Seed: a labelled model (`Annotator::Model`), no operator gate, never Test evidence (`ja-ontology-owner-answer`); attestation (operator, native reader) appends later. Accepted rounds later, through a mint call from Train items only. Selection is `ontology::select(ontology, language, view)`, called only inside `Prompt::lower`, so no caller picks entries; the prompt prints each selected entry's id, layer, choice and examples. Its text is minted text for the leak diagnostic, which today has no runtime caller (follow-up `leak-diagnostic-in-round`) |
| Language ontology entry (cut `language-ontology`) | Key `onto:<language>:<n>`, `n` from 1 per language; type `weksa.ontology_entry` in `data/ontology/ontology.cc`: the language, the revision of the inventory it was admitted through (provenance: beds number revisions independently and a run reads each condition's feature at its own head), a layer (lexicon, grammar, discourse, phonology, orthography), at most four conditions (a feature id of slot `Stance`, `Discourse` or `Scene` with `AtLeast`/`AtMost` a grade or `Is` an option or flag; a `Scene` condition reads the relation of the item's speaker to its addressee that `with_scene` selected; slot `Speaker` and community ids wait on follow-up `language-ontology-communities`), a choice of 1 to 200 bytes, at most three examples of 1 to 120 bytes, and an `Annotator`. Whether an entry's text keeps politeness on the scene is a model reader's judgment, owed per change (a mint owes it under `language-ontology-growth`), not a test | Admitted in all-or-nothing batches through `OntologyStore::admit`, immutable, never edited; retire arrives with `language-ontology-growth`. The run pins the store like the entity store (path and digest, `Drift(Ontology)`), never copies it, because no loop step writes it yet. Cut `language-ontology-seed` converts by drafting, not by parse: a fresh model drafts each language's seed from that language's YAML and the inventory's feature lines with no corpus text, then deletes `data/target-language-ontology/` and `.weksa/cultural-ontology/` in the same cut | Seed: a labelled model (`Annotator::Model`), no operator gate, never Test evidence (`ja-ontology-owner-answer`); attestation (operator, native reader) appends later. Accepted rounds later, through a mint call from Train items only. Selection is `ontology::select(ontology, language, view)`, called only inside `Prompt::lower`, so no caller picks entries; the prompt prints each selected entry's id, layer, choice and examples. Its text is minted text for the leak diagnostic, which today has no runtime caller (follow-up `leak-diagnostic-in-round`) |
| Community (cut `language-ontology`, follow-up `language-ontology-communities`) | Key `community:<slug>`; type `weksa.community`, converted from `data/cultural-ontology/cultures/` and `referents/` (YAML deleted in the same cut) | Revised by commit; a speaker names the communities it belongs to by id | The cultural ontology's author (owner-attested evidence only; Discord-only content stays deleted, `discord-only-content-deleted`) |
| Speaker (cut `speaker-scene`) | Key `speaker:<slug>@<n>`; type `weksa.speaker`: stable axes as feature values of slot `Speaker` (origin, socioeconomic standing, age cohort, education and whatever the loop adds), at an inventory revision, and an `Annotator` | A new revision appends, never edits; a corpus item and every prior turn name a speaker slug, and a round records the speaker revisions it read | The annotator it names: `PersonaSnapshot` (a projection of a `gamecult.persona_state.v0` document, naming its source document and `updatedAt`), `Operator`, `WorkMetadata` (a public-domain work's own cast list and narration), or `Model { family, generator }` (the 2b `ModelFamily`) |
| Scene (cut `speaker-scene`) | Key `scene:<group>@<n>`, the item's split group; type `weksa.scene`: participants (speaker slugs) and, per ordered pair, relation features of slot `Scene` (relative status, intimacy, in-group, audience and whatever the loop adds), at an inventory revision, and an `Annotator` | As Speaker. Keyed by the group, so a scene never straddles the split. `data/original/speaker-state.cc` holds Model-annotated keeper scenes, one per Train or Validation group of both languages (`weksa scene put|dump`); a Test group has none and reads `NoScene` | As Speaker; a model-annotated scene is never Test evidence |
| Scene (cut `speaker-scene`) | Key `scene:<group>@<n>`, the item's split group; type `weksa.scene`: participants (speaker slugs) and, per ordered pair, relation features of slot `Scene` (relative status, intimacy, in-group, audience and whatever the loop adds), at an inventory revision (provenance; a run reads the features at its own head), and an `Annotator` | As Speaker. Keyed by the group, so a scene never straddles the split. `data/original/speaker-state.cc` holds Model-annotated keeper scenes, one per Train or Validation group of both languages (`weksa scene put|dump`); a Test group has none and reads `NoScene` | As Speaker; a model-annotated scene is never Test evidence |
| Corpus item | Key `item:<source-slug>:<n>`, `n` canonical decimal >= 1 (no sign, no leading zero, from cut 2b); type `weksa.corpus_item` with text, language, speaker, split group, prior turns, source. Source `ModelWritten` carries a `ModelFamily` enum, the generator string as the fixture wrote it, and a prompt digest (cut 2b). The item carries no speaker state of its own: its speaker slug and group name the Speaker and Scene documents | Admitted once; the whole item is immutable (re-adding an identical item is a no-op, any difference is refused; a correction is a new item that `supersedes` the old); text already held under another key is refused unless the new item supersedes that key (cut 2b); withdrawn, never deleted, so old scores stay explainable | Importers and `weksa corpus add`, under `corpus-cast-plus-public-domain`; the source enum has no variant for Discord or any training-forbidden source (`corpus-clean`) |
| Split assignment | Derived: `split(scheme, item.group)` | Fixed for a scheme; a new scheme is a new constant, never a reassignment. A model-written item whose group derives test is in no split; a superseding item keeps its predecessor's group | The hash function. Nobody assigns by hand, so there is no writer to forbid |
| Loop round | Key `run:<label>:round:<n>`; type `weksa.loop_round` with spec version, profile revisions, model ids and prompt digests, sampled train items, Validation means and refusal counts (from cut `ja-panel-loop`: one set per language of the run's panel, no pooled mean), the digest of the inventory file it was scored under and, for a cycle's candidate round, its cycle `k` | Scored and recorded whole; immutable. A candidate round is put in one batch with its Decision. Lives in the run store on Yggdrasil | `run::round` and `run::cycle` (both through `score_round`) |
@ -424,7 +424,7 @@ One line per cut; specs exist for the cuts about to run (2d and 4, siblings on `
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` 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`, in sequential cuts on `d9ebbcf`. 5a `language-ontology` (spec r1): `weksa.ontology_entry`, `OntologyStore`, `ontology::select` over the view's `Stance` and `Discourse` features, `Prompt::lower` printing the selected entries of the item's language, `RunRecord` pinning the ontology store (keys 15 and 16), `weksa ontology admit|dump`; `ontology.rs` joins `PROMPT_SOURCE_DIGEST`. 5b `language-ontology-seed` (spec r1, after 5a): Model-annotated en-US and ja-JP seed entries drafted by an isolated model, `data/target-language-ontology/`, `.weksa/cultural-ontology/` and `schemas/target-language-cultural-ontology.md` deleted. Lost until 5c': keigo, hierarchy and uchi-soto hang on the scene relation from 5c', so ja-JP politeness is still the lowerer's guess. 5c' `language-ontology-scene` (spec r1, after 5b): the run pins the `SpeakerStateStore` (keys 17 and 18, `Drift(SpeakerState)`), `RunStores::assembled` is the one door that reads the item's scene into a loop view, `select` reads slot `Scene`, `Prompt::lower` refuses a view whose scene was never read, `weksa scene put|dump` and `weksa inventory speaker-scene` (inventory rev 4), Model-annotated keeper scenes and Scene-conditioned entries for `data/original`. 5c `language-ontology-view-assembly` (follow-up, now only the Speaker read): `select` widens to slot `Speaker`; waits on `speaker-scene-seeds`. 5d `language-ontology-communities` (follow-up): `weksa.community` from `data/cultural-ontology/` (YAML deleted, owns `edda-referent-yaml-parse`), community ids on `weksa.speaker`, community conditions; waits on 5c and question `persona-speech-community-home`.
5. `language-ontology`, in sequential cuts on `d9ebbcf`. 5a `language-ontology` (spec r1): `weksa.ontology_entry`, `OntologyStore`, `ontology::select` over the view's `Stance` and `Discourse` features, `Prompt::lower` printing the selected entries of the item's language, `RunRecord` pinning the ontology store (keys 15 and 16), `weksa ontology admit|dump`; `ontology.rs` joins `PROMPT_SOURCE_DIGEST`. 5b `language-ontology-seed` (spec r1, after 5a): Model-annotated en-US and ja-JP seed entries drafted by an isolated model, `data/target-language-ontology/`, `.weksa/cultural-ontology/` and `schemas/target-language-cultural-ontology.md` deleted. Lost until 5c': keigo, hierarchy and uchi-soto hang on the scene relation from 5c', so ja-JP politeness is still the lowerer's guess. 5c' `language-ontology-scene` (spec r1, after 5b): the run pins the `SpeakerStateStore` (keys 17 and 18, `Drift(SpeakerState)`), `RunStores::assembled` is the one door that reads the item's scene into a loop view, `select` reads slot `Scene`, `Prompt::lower` refuses a view whose scene was never read, `weksa scene put|dump` and `weksa inventory speaker-scene` (rev 4 in data/original and data/daodejing, rev 3 in data/interlingua, which holds no Dao revision), Model-annotated keeper scenes and Scene-conditioned entries for `data/original`. 5c `language-ontology-view-assembly` (follow-up, now only the Speaker read): `select` widens to slot `Speaker`; waits on `speaker-scene-seeds`. 5d `language-ontology-communities` (follow-up): `weksa.community` from `data/cultural-ontology/` (YAML deleted, owns `edda-referent-yaml-parse`), community ids on `weksa.speaker`, community conditions; waits on 5c and question `persona-speech-community-home`.
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`.
7a. `loop-context-builder`: `context.rs` (`TrainItems`, `EncoderOffer`, `Prompt`: corpus text reaches a model call only through `Prompt::mint` over live Train items or `Prompt::encode` over one offered item; instructions are the consts `MINT_INSTRUCTIONS` and `ENCODE_INSTRUCTIONS` in `context.rs`; `Checkpoint` has no public constructor, offers each item once, and its offers borrow it (the gate is typed `TestReadings` and Test offers on the round path; operator tools such as `weksa corpus dump` and `weksa score` still read corpus text of any split); prior turns print the held Train text), `SplitScheme::live`, `CorpusItem::cited_entities` (one owner of the id set `PacketStore::admit` and the offer share), and checkpoint-only Test readings (`Checkpoint`, `TestReadings`; `Aggregate` and `leak_diagnostic` carry no Test). Library only; since 7b `TrainItems::from_corpus` and `EncoderOffer::for_round` are crate-private and `RunStores` is their one public producer. Merges follow-ups `loop-context-builder-2` and `scorer-test-readings-type`. (spec r1)
7b. `golf-loop-round` (spec r1; branch `weksa-lowering/golf-loop-round`): the smallest loop that scores one round. `run.rs`: `RunStores` pins the corpus and entity stores of a run by canonical path and FNV-1a 64 digest at `create`, reopens only from `<root>/run.cc` and refuses drift (corpus, entities, `PROMPT_SOURCE_DIGEST` of `context.rs`, a second pin on `lower.rs` retired in 7d, model, embedder); `round` encodes every live Train and Validation item through a `ModelPort` (`model.rs`; the one adapter `AnswerFiles` writes `<digest>.prompt` and reads `<digest>.answer`, so the model is the Claude session), reads each answer into a `PacketDraft` (`encode.rs`, the one place a model's JSON is read), admits through `PacketStore::admit` into `<root>/round-<n>/packets.cc`, lowers, scores once, and keeps a `RoundRecord` (`weksa.loop_round`, no text, no Test; Train rows, Validation only as means and refusal counts). `weksa loop open|round`. Not here: the spec-edit step, accept rule and composite, noise floor, Score and lowering documents, a subprocess adapter, the checkpoint door, a model lowerer (follow-ups `golf-loop-spec-edit`, `golf-loop-accept-rule`, `golf-loop-score-documents`, `golf-loop-subprocess-adapter`, `golf-loop-checkpoint-door-bound`, `golf-loop-model-lowerer`). `packet-store-inventory-binding` is met for loop packets only (`RunStores` admits with the run's inventory); `validate`, `packet proof|dao` and `lower` still take caller stores. Waits on 7a, `checkpoint-gate`, 4b.

View file

@ -17,7 +17,7 @@
}
},
"inventory_rev": {
"description": "The inventory revision the conditions read against.",
"description": "The revision, of the inventory the entry was admitted through, at which the\nstore read its conditions: provenance only; a run reads the conditions'\nfeatures at its own head.",
"type": "integer",
"format": "uint32",
"minimum": 0

View file

@ -12,6 +12,7 @@
"type": "string"
},
"inventory_rev": {
"description": "The revision, of the inventory the scene was admitted through, at which\n`put_scene` read its features: provenance only; a run never reads it.",
"type": "integer",
"format": "uint32",
"minimum": 0