ja-inventory: rev 5 concepts and frames for the original bed (model-drafted, Validation-informed), weksa inventory admit, onto:ja-JP:15 (#54)
This commit is contained in:
parent
002d3186c3
commit
7babf8aadc
9 changed files with 130 additions and 162 deletions
|
|
@ -25,7 +25,9 @@ measured.
|
|||
published schemas in `schemas/cultnet/`.
|
||||
- `data/corpus/corpus.cc`, `data/entity/entity.cc` and `data/interlingua/inventory.cc`: the English
|
||||
corpus, its entities and the inventory, read with `weksa corpus dump`,
|
||||
`weksa entity dump` and `weksa inventory dump`.
|
||||
`weksa entity dump` and `weksa inventory dump`. `data/original/inventory.cc`
|
||||
carries its own Validation-informed lexicon revision (rev 5) for lowering
|
||||
measurements; scored runs use `data/interlingua`.
|
||||
- [Lowering target](./docs/targets/interlingua-lowering.md) and
|
||||
[its map](./docs/targets/interlingua-lowering-map.md): the design, scoring
|
||||
and cut sequence.
|
||||
|
|
|
|||
|
|
@ -223,19 +223,22 @@ impl EditDraft {
|
|||
}
|
||||
|
||||
/// The revision after `base` this edit asks for, and the entries it adds.
|
||||
/// `None` when the edit makes no change or more than [`MAX_EDIT_CHANGES`].
|
||||
/// `None` when the edit makes no change or more than `max_changes`. The
|
||||
/// loop's cycle passes [`MAX_EDIT_CHANGES`]; the CLI's `inventory admit`
|
||||
/// passes its own larger cap for a draft reviewed whole in its cut.
|
||||
/// Every id is the store's: an added entry takes the next number of its
|
||||
/// family after the highest in `inventory`, retired entries included, in
|
||||
/// draft order. Nothing else is judged here: the gloss, the entry's shape,
|
||||
/// whether a retire target is live and a repeated id are
|
||||
/// `InventoryStore::admit_revision`'s to refuse.
|
||||
pub(crate) fn into_revision(
|
||||
pub fn into_revision(
|
||||
self,
|
||||
inventory: &Inventory,
|
||||
base: u32,
|
||||
max_changes: usize,
|
||||
) -> Option<(InventoryRevision, Vec<InventoryEntry>)> {
|
||||
let count = self.add.len() + self.retire.len();
|
||||
if count == 0 || count > MAX_EDIT_CHANGES {
|
||||
if count == 0 || count > max_changes {
|
||||
return None;
|
||||
}
|
||||
let n = base.checked_add(1)?;
|
||||
|
|
@ -583,7 +586,7 @@ mod tests {
|
|||
InvId::parse("frame:2").unwrap(),
|
||||
],
|
||||
};
|
||||
let (rev, entries) = draft.clone().into_revision(&inv, 6).unwrap();
|
||||
let (rev, entries) = draft.clone().into_revision(&inv, 6, MAX_EDIT_CHANGES).unwrap();
|
||||
let ids: Vec<String> = entries.iter().map(|e| e.id.to_string()).collect();
|
||||
// After the highest of the family, retired ones included, in draft order.
|
||||
assert_eq!(ids, ["concept:4", "role:1", "concept:5", "frame:3"]);
|
||||
|
|
@ -626,20 +629,25 @@ mod tests {
|
|||
.collect(),
|
||||
retire: vec![],
|
||||
};
|
||||
assert!(adds(0).into_revision(&inv, 1).is_none());
|
||||
assert!(adds(1).into_revision(&inv, 1).is_some());
|
||||
assert_eq!(adds(8).into_revision(&inv, 1).unwrap().1.len(), 8);
|
||||
assert!(adds(9).into_revision(&inv, 1).is_none());
|
||||
assert!(adds(0).into_revision(&inv, 1, MAX_EDIT_CHANGES).is_none());
|
||||
assert!(adds(1).into_revision(&inv, 1, MAX_EDIT_CHANGES).is_some());
|
||||
assert_eq!(adds(8).into_revision(&inv, 1, MAX_EDIT_CHANGES).unwrap().1.len(), 8);
|
||||
assert!(adds(9).into_revision(&inv, 1, MAX_EDIT_CHANGES).is_none());
|
||||
// Adds and retires count together.
|
||||
let mixed = |a: usize, r: usize| EditDraft {
|
||||
add: (0..a).map(|_| add(Family::Concept, "g")).collect(),
|
||||
retire: vec![InvId::parse("concept:1").unwrap(); r],
|
||||
};
|
||||
assert!(mixed(4, 4).into_revision(&inv, 1).is_some());
|
||||
assert!(mixed(4, 5).into_revision(&inv, 1).is_none());
|
||||
assert!(mixed(0, 9).into_revision(&inv, 1).is_none());
|
||||
assert!(mixed(4, 4).into_revision(&inv, 1, MAX_EDIT_CHANGES).is_some());
|
||||
assert!(mixed(4, 5).into_revision(&inv, 1, MAX_EDIT_CHANGES).is_none());
|
||||
assert!(mixed(0, 9).into_revision(&inv, 1, MAX_EDIT_CHANGES).is_none());
|
||||
// A repeated retire is the door's to refuse, not this fn's.
|
||||
assert!(mixed(0, 2).into_revision(&inv, 1).is_some());
|
||||
assert!(mixed(0, 2).into_revision(&inv, 1, MAX_EDIT_CHANGES).is_some());
|
||||
// The cap is the caller's.
|
||||
let (_, entries) = adds(200).into_revision(&inv, 1, 256).unwrap();
|
||||
assert_eq!(entries.first().unwrap().id.to_string(), "concept:2");
|
||||
assert_eq!(entries.last().unwrap().id.to_string(), "concept:201");
|
||||
assert!(adds(257).into_revision(&inv, 1, 256).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use clap::error::{ContextKind, ContextValue, ErrorKind};
|
|||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
use weksa::corpus::{CorpusItem, ModelFamily, NameSpan, Source, Turn};
|
||||
use weksa::encode::OntologyDraft;
|
||||
use weksa::encode::{EditDraft, OntologyDraft};
|
||||
use weksa::entity::{Entity, EntityOrigin, LanguageForms, Pronunciation};
|
||||
use weksa::inventory::{proof_revision, seed, speaker_scene_revision};
|
||||
use weksa::packet::{FeatValue, FeatureValue, Grade};
|
||||
|
|
@ -200,8 +200,15 @@ enum InventoryCommand {
|
|||
/// Admit the speaker-state revision (Scene and Speaker features) as the
|
||||
/// inventory's latest plus one.
|
||||
SpeakerScene,
|
||||
/// Admit a model's inventory draft (a JSON file, {add, retire} as the
|
||||
/// loop's editor writes it) as the inventory's latest plus one.
|
||||
Admit { answer: PathBuf },
|
||||
}
|
||||
|
||||
/// The most changes `inventory admit` takes in one draft. A hand-admitted draft
|
||||
/// is reviewed whole in its cut, so the cap is not the loop editor's.
|
||||
const MAX_ADMIT_CHANGES: usize = 256;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum SceneCommand {
|
||||
/// Append a scene revision for one corpus group: its one live speaker's
|
||||
|
|
@ -785,6 +792,22 @@ fn main() -> Result<()> {
|
|||
store.admit_revision(rev, entries).map_err(refused)?;
|
||||
println!("admitted rev:{n} with {count} entries");
|
||||
}
|
||||
Command::Inventory(InventoryCommand::Admit { answer }) => {
|
||||
let text = std::fs::read_to_string(&answer).context("the draft would not read")?;
|
||||
let draft = EditDraft::parse(&text)?;
|
||||
let mut store = InventoryStore::open(&cli.inventory)?;
|
||||
let inventory = store.inventory()?;
|
||||
let latest = inventory
|
||||
.latest()
|
||||
.map(|r| r.n)
|
||||
.context("the inventory holds no revision")?;
|
||||
let (rev, entries) = draft
|
||||
.into_revision(&inventory, latest, MAX_ADMIT_CHANGES)
|
||||
.context("the draft makes no change or more than the cap")?;
|
||||
let (n, count) = (rev.n, entries.len());
|
||||
store.admit_revision(rev, entries).map_err(refused)?;
|
||||
println!("admitted rev:{n} with {count} entries");
|
||||
}
|
||||
Command::Scene(SceneCommand::Put {
|
||||
group,
|
||||
to,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||
|
||||
use crate::context::{EditFeedback, EncoderOffer, Prompt, TrainItems};
|
||||
use crate::corpus::{CorpusItem, CorpusItemId, ModelFamily, Source};
|
||||
use crate::encode::{EditDraft, LoweringDraft, PacketDraft, encoder_id};
|
||||
use crate::encode::{EditDraft, LoweringDraft, MAX_EDIT_CHANGES, PacketDraft, encoder_id};
|
||||
use crate::form::{is_attestation, is_language_tag, is_slug};
|
||||
use crate::model::{ModelPort, PortError};
|
||||
use crate::packet::{Packet, Stance};
|
||||
|
|
@ -2257,7 +2257,7 @@ pub fn cycle<M: ModelPort, E: Embedder>(
|
|||
let Some(draft) = answer.as_deref().and_then(|a| EditDraft::parse(a).ok()) else {
|
||||
return reject(run, Why::EditParse);
|
||||
};
|
||||
let Some((revision, entries)) = draft.into_revision(&inventory, base_rev) else {
|
||||
let Some((revision, entries)) = draft.into_revision(&inventory, base_rev, MAX_EDIT_CHANGES) else {
|
||||
return reject(run, Why::EditAdmit);
|
||||
};
|
||||
let candidate_rev = revision.n;
|
||||
|
|
|
|||
|
|
@ -1875,3 +1875,31 @@ fn scene_put_and_dump() {
|
|||
assert!(!o.status.success());
|
||||
assert!(!out(&o).contains("scene-1"), "{}", out(&o));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inventory_admit_numbers_a_draft_and_refuses_an_empty_one() {
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
assert!(weksa(d.path(), &["inventory", "seed"]).status.success());
|
||||
let path = |name: &str, text: &str| {
|
||||
let p = d.path().join(name);
|
||||
std::fs::write(&p, text).unwrap();
|
||||
p.to_str().unwrap().to_owned()
|
||||
};
|
||||
let before = out(&weksa(d.path(), &["inventory", "dump"]));
|
||||
|
||||
for (name, text) in [("empty.json", "{}"), ("junk.json", "not json at all")] {
|
||||
let refused = weksa(d.path(), &["inventory", "admit", &path(name, text)]);
|
||||
assert!(!refused.status.success(), "{}", out(&refused));
|
||||
assert_eq!(before, out(&weksa(d.path(), &["inventory", "dump"])));
|
||||
}
|
||||
|
||||
let draft = path(
|
||||
"draft.json",
|
||||
r#"{"add":[{"family":"Concept","gloss":"bath_house"},{"family":"Frame","gloss":"fly_a_kite"}]}"#,
|
||||
);
|
||||
let admitted = weksa(d.path(), &["inventory", "admit", &draft]);
|
||||
assert!(admitted.status.success(), "{}", out(&admitted));
|
||||
assert!(out(&admitted).contains("admitted rev:2 with 2 entries"));
|
||||
let dump = out(&weksa(d.path(), &["inventory", "dump"]));
|
||||
assert!(dump.contains("bath_house") && dump.contains("fly_a_kite"), "{dump}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,23 +240,49 @@ fn the_first_six_original_items_are_unchanged() {
|
|||
}
|
||||
}
|
||||
|
||||
/// Revision 5 of `data/original/inventory.cc` is the original bed's own
|
||||
/// lexicon: concepts and frames drafted by claude-sonnet-5-5 (Weksa Hands,
|
||||
/// model-drafted original-bed lexicon from Train and Validation passages) so an
|
||||
/// encoder can name baths, kites and persimmons. It is Validation-informed on
|
||||
/// purpose: numbers on this bed measure the lowering half (register, scene,
|
||||
/// ontology), never inventory generalization or convergence; scored runs use
|
||||
/// `data/interlingua`. The Dao De Jing inventory is its revision prefix.
|
||||
const ORIGINAL_REV5_ADDS: usize = 233;
|
||||
|
||||
#[test]
|
||||
fn the_original_inventory_is_the_dao_inventory() {
|
||||
let dao = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/daodejing/inventory.cc");
|
||||
let own = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/original/inventory.cc");
|
||||
assert_eq!(std::fs::read(dao).unwrap(), std::fs::read(&own).unwrap());
|
||||
fn the_dao_inventory_is_a_prefix_of_the_original_inventory() {
|
||||
use weksa::inventory::{Change, Family};
|
||||
let data = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let copy = tmp.path().join("inventory.cc");
|
||||
std::fs::copy(&own, ©).unwrap();
|
||||
let inventory = InventoryStore::open(©).unwrap().inventory().unwrap();
|
||||
// Revision 4 is the speaker-state vocabulary: the same six features the
|
||||
// generator admits, ids and all.
|
||||
assert_eq!(inventory.latest().unwrap().n, 4);
|
||||
let (rev, entries) = weksa::inventory::speaker_scene_revision(4);
|
||||
assert_eq!(inventory.latest().unwrap().changes, rev.changes);
|
||||
for e in &entries {
|
||||
assert_eq!(inventory.entry(&e.id), Some(e), "{}", e.gloss);
|
||||
let open = |name: &str| {
|
||||
let copy = tmp.path().join(format!("{name}.cc"));
|
||||
std::fs::copy(data.join(name).join("inventory.cc"), ©).unwrap();
|
||||
InventoryStore::open(©).unwrap().inventory().unwrap()
|
||||
};
|
||||
let (dao, own) = (open("daodejing"), open("original"));
|
||||
assert_eq!(dao.latest().unwrap().n, 4);
|
||||
let latest = own.latest().unwrap();
|
||||
assert_eq!((latest.n, latest.parent), (5, Some(4)));
|
||||
for n in 1..=4 {
|
||||
let revision = |inv: &weksa::Inventory| inv.revisions().find(|r| r.n == n).cloned();
|
||||
assert_eq!(revision(&dao), revision(&own), "rev {n}");
|
||||
}
|
||||
let mut glosses = std::collections::BTreeSet::new();
|
||||
for e in dao.entries() {
|
||||
assert_eq!(own.entry(&e.id), Some(e), "{}", e.gloss);
|
||||
glosses.insert(e.gloss.clone());
|
||||
}
|
||||
assert_eq!(latest.changes.len(), ORIGINAL_REV5_ADDS);
|
||||
for change in &latest.changes {
|
||||
let Change::Add(id) = change else {
|
||||
panic!("rev 5 retires {change:?}");
|
||||
};
|
||||
let e = own.entry(id).unwrap();
|
||||
assert!(matches!(id.family(), Family::Concept | Family::Frame), "{id}");
|
||||
assert_eq!(e.introduced_rev, 5, "{id}");
|
||||
assert!(glosses.insert(e.gloss.clone()), "{} repeats a gloss", e.gloss);
|
||||
}
|
||||
assert_eq!(own.entries().count(), dao.entries().count() + ORIGINAL_REV5_ADDS);
|
||||
}
|
||||
|
||||
/// The committed seed ontology: every entry model-drafted, exactly the two
|
||||
|
|
@ -889,135 +915,6 @@ fn the_eight_ja_test_sentences_are_pinned_verbatim() {
|
|||
}
|
||||
}
|
||||
|
||||
/// The concepts the pinned inventory holds, as each language names them in the
|
||||
/// passages: English by word stem, Japanese by a multi-character surface. This
|
||||
/// is a tripwire, not the encoder: it fails a passage that names no live
|
||||
/// concept, and `FALSE_FRIENDS` strips the idioms that hold a concept's
|
||||
/// character without its meaning (天気, 心配, 腹が立つ ...) before matching.
|
||||
const LEXICON: [(&str, &[&str], &[&str]); 21] = [
|
||||
("endanger", &["endanger"], &["危う", "危ない"]),
|
||||
("prevent", &["prevent"], &["防い", "防げ", "妨げ"]),
|
||||
("control", &["control"], &["操ろ", "操る", "操れ", "支配"]),
|
||||
(
|
||||
"request",
|
||||
&["request"],
|
||||
&["いただけませんか", "もらえますか", "くれませんか", "お願い"],
|
||||
),
|
||||
("sage", &["sage"], &["賢者"]),
|
||||
("mind", &["mind"], &["心が", "心は", "心を", "心の", "心も"]),
|
||||
(
|
||||
"belly",
|
||||
&["belly", "bellies"],
|
||||
&["お腹", "空腹", "満腹", "腹の虫"],
|
||||
),
|
||||
("will", &["will"], &["意志"]),
|
||||
("bone", &["bone"], &["骨が", "骨を", "骨は", "骨の"]),
|
||||
("empty", &["empt"], &["空っぽ", "空にし", "空の器"]),
|
||||
("fill", &["fill"], &["満た", "いっぱい"]),
|
||||
("weaken", &["weaken"], &["弱ら", "弱め", "弱っ"]),
|
||||
("strengthen", &["strengthen"], &["強くな", "強め", "強くし"]),
|
||||
(
|
||||
"heaven",
|
||||
&["heaven"],
|
||||
&["天が", "天を", "天は", "天上", "天の"],
|
||||
),
|
||||
("earth", &["earth"], &["大地"]),
|
||||
("water", &["water"], &["水が", "水を", "水は", "水面"]),
|
||||
("compare", &["compare"], &["比べ"]),
|
||||
("resemble", &["resemble"], &["似て", "似た", "似る"]),
|
||||
("continues_long", &[], &["長く続", "久しく続"]),
|
||||
("true_words", &[], &["誠の言葉", "真実の言葉"]),
|
||||
("seem_paradoxical", &[], &["逆さま"]),
|
||||
];
|
||||
|
||||
/// Compounds and idioms that contain a surface above without naming its concept.
|
||||
const FALSE_FRIENDS: [&str; 8] = [
|
||||
"天気",
|
||||
"心配",
|
||||
"腹が立",
|
||||
"腹を立",
|
||||
"水曜",
|
||||
"骨が折れ",
|
||||
"天の川",
|
||||
"天井",
|
||||
];
|
||||
|
||||
/// The live concepts a passage names, by `LEXICON`, in `lang_col` (0 en-US, 1 ja-JP).
|
||||
fn concepts_in(text: &str, col: usize) -> Vec<&'static str> {
|
||||
let mut text = text.to_lowercase();
|
||||
for f in FALSE_FRIENDS {
|
||||
text = text.replace(f, "");
|
||||
}
|
||||
let words: Vec<&str> = text.split(|c: char| !c.is_alphabetic()).collect();
|
||||
LEXICON
|
||||
.iter()
|
||||
.filter(|(_, en, ja)| {
|
||||
if col == 0 {
|
||||
en.iter().any(|s| words.iter().any(|w| w.starts_with(s)))
|
||||
} else {
|
||||
ja.iter().any(|s| text.contains(s))
|
||||
}
|
||||
})
|
||||
.map(|(c, _, _)| *c)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_concept_lexicon_does_not_match_a_word_that_merely_holds_a_character() {
|
||||
for ja in [
|
||||
"今日の天気はどうだろう。",
|
||||
"試験が心配でならない。",
|
||||
"腹が立って仕方がない。",
|
||||
"水曜に会いましょう。",
|
||||
"骨が折れる仕事だ。",
|
||||
] {
|
||||
assert!(concepts_in(ja, 1).is_empty(), "{ja}");
|
||||
}
|
||||
assert!(concepts_in("I attempt it, with a reminder.", 0).is_empty());
|
||||
assert_eq!(concepts_in("水が澄んでいる。", 1), ["water"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_original_passage_rests_on_a_live_inventory_concept() {
|
||||
use weksa::inventory::Family;
|
||||
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../data/original");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
std::fs::copy(dir.join("inventory.cc"), tmp.path().join("inventory.cc")).unwrap();
|
||||
let inventory = InventoryStore::open(&tmp.path().join("inventory.cc"))
|
||||
.unwrap()
|
||||
.inventory()
|
||||
.unwrap();
|
||||
let rev = inventory.latest().unwrap().n;
|
||||
let live: Vec<&str> = inventory
|
||||
.entries()
|
||||
.filter(|e| e.family() == Family::Concept && e.live_at(rev))
|
||||
.map(|e| e.gloss.as_str())
|
||||
.collect();
|
||||
for (concept, _, _) in LEXICON {
|
||||
assert!(live.contains(&concept), "{concept} is not a live concept");
|
||||
}
|
||||
let (corpus, _) = stores();
|
||||
for (col, lang) in PANEL.iter().enumerate() {
|
||||
let mut hit = std::collections::BTreeSet::new();
|
||||
let mut passages = 0;
|
||||
for item in corpus.all().unwrap() {
|
||||
if item.language != *lang || !matches!(item.source, Source::ModelWritten { .. }) {
|
||||
continue;
|
||||
}
|
||||
passages += 1;
|
||||
let found = concepts_in(&item.text, col);
|
||||
assert!(!found.is_empty(), "{} rests on no live concept", item.id);
|
||||
hit.extend(found);
|
||||
}
|
||||
eprintln!(
|
||||
"{lang}: {passages} passages on a live concept, {} distinct concepts",
|
||||
hit.len()
|
||||
);
|
||||
assert_eq!(passages, 28);
|
||||
assert!(hit.len() >= 12, "{lang}: {} distinct concepts", hit.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// The committed scenes: one per live Train or Validation group of
|
||||
/// `data/original`, none for any other group, every one model-annotated and
|
||||
/// never Test evidence (a Test group gets no scene and reads `NoScene`).
|
||||
|
|
@ -1105,6 +1002,16 @@ fn scene_entries_never_co_select_in_a_layer() {
|
|||
);
|
||||
}
|
||||
}
|
||||
// An above, out-group speaker is told by exactly one ja-JP
|
||||
// Scene-conditioned grammar entry, at every intimacy.
|
||||
if (st, ig) == ("above", false) {
|
||||
let told = entries
|
||||
.iter()
|
||||
.filter(|e| e.language == "ja-JP" && e.layer.name() == "grammar")
|
||||
.filter(|e| e.when.iter().any(|c| c.slot == Slot::Scene) && selects(e, rel))
|
||||
.count();
|
||||
assert_eq!(told, 1, "{rel:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -31,7 +31,7 @@ means no store holds it and nothing writes it.
|
|||
|---|---|---|---|
|
||||
| Packet | Key `packet:<16 hex>`, an opaque digest of item, inventory revision and encoder (the item key lives in `provenance.corpus_item`, which the lowerer never sees); CultCache type `weksa.interlingua_packet`, carrying `schema_version` and `inventory_rev`. Every id inside is an opaque form type (`n<k>` nodes, `encoder:<16 hex>`, canonical `<family>:<n>`). From cut `speaker-state` (schema v4) it also carries the utterance **stance**: the speaker's felt affect and restraint, as inventory features of slot `Stance` (see Speaker state below) | Minted by an encoder for one corpus item under one spec version; immutable; a new spec version means a new packet, never an edit. Admitted only through `PacketStore::admit`, which validates against the inventory store at the packet's revision | The encoder writes; the form types refuse malformed ids at construction and load, the validator refuses broken references and over-capacity packets: a feature at most once per slot, at most 64 nodes (`typed-packet`); nothing else writes packets |
|
||||
| Schema version | `weksa.interlingua_packet.v<N>` constant in `crates/weksa`; published JSON Schema at `schemas/cultnet/` | Bumped by a commit that changes the Rust types; the published schema is regenerated in the same commit and a test fails on drift | A cut (Hands, under a spec); never the loop |
|
||||
| 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 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; `weksa inventory admit` for one model-drafted `EditDraft` outside a run (cap 256, reviewed whole in its cut; same numbering and admission check as the loop); Self on merge to main. `data/original` rev 5 (233 concepts and frames) was drafted by claude-sonnet-5-5 (Weksa Hands, model-drafted original-bed lexicon from Train and Validation passages); Validation-informed: numbers on this bed measure lowering, never generalization or convergence. 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 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`) |
|
||||
|
|
@ -320,8 +320,8 @@ line two", "その 制御" against the
|
|||
features reach a loop's lowering view. `score::leak_diagnostic` and
|
||||
`TestReadings::at_checkpoint` take minted text as `&[&str]` and have no
|
||||
caller outside tests: no round reads glosses or any minted text against
|
||||
Validation. `data/original/inventory.cc` and `data/daodejing/inventory.cc` are
|
||||
byte-identical; with `data/interlingua/inventory.cc` they share revision 1,
|
||||
Validation. `data/daodejing/inventory.cc` is a revision prefix of
|
||||
`data/original/inventory.cc` (revisions 1 to 4 equal, a test proves it; rev 5 adds the bed's own lexicon); with `data/interlingua/inventory.cc` they share revision 1,
|
||||
where features 6 to 8 (hedge, certainty grades, irony flag) sit in
|
||||
`Discourse` and `Predication` and features 9 to 18 (anger to restraint,
|
||||
grades) in `Stance`. `PROMPT_SOURCE_DIGEST` covers `context.rs`, `split.rs`,
|
||||
|
|
|
|||
Loading…
Reference in a new issue