Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
218962fcf6 | ||
| 157892a95a | |||
|
|
c1b2778b15 | ||
| 4fb817b18f | |||
| ad02feeeb6 | |||
| 283678a58b | |||
| 86371d2ce4 |
17 changed files with 806 additions and 55 deletions
10
AGENTS.md
10
AGENTS.md
|
|
@ -51,3 +51,13 @@ 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 <store>` admits the nibu proof packet under the key
|
||||
`packet:6e6962752d303031` (hex of `nibu-001`), not `nibu-proof-001`. Use that key
|
||||
with `weksa lower <store> <key>`.
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ pub enum Slot {
|
|||
Referent,
|
||||
Predication,
|
||||
Relation,
|
||||
Stance,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
|
|
@ -439,10 +440,28 @@ pub fn seed() -> (InventoryRevision, Vec<InventoryEntry>) {
|
|||
&pred,
|
||||
);
|
||||
let nuance = [Slot::Discourse, Slot::Predication];
|
||||
for g in ["hedge", "certainty", "warmth", "formality", "intensity"] {
|
||||
for g in ["hedge", "certainty"] {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub use inventory::{
|
|||
};
|
||||
pub use lower::{LowerError, lower};
|
||||
pub use packet::{
|
||||
EncoderId, EntityId, Grade, NodeId, Packet, PacketId, PacketKind, SCHEMA_VERSION,
|
||||
EncoderId, EntityId, Grade, NodeId, Packet, PacketId, PacketKind, SCHEMA_VERSION, Stance,
|
||||
};
|
||||
pub use score::{Embedder, ItemScore, OllamaEmbedder, Scorer, chrf};
|
||||
pub use split::{Split, SplitScheme};
|
||||
|
|
|
|||
|
|
@ -486,20 +486,22 @@ fn main() -> Result<()> {
|
|||
println!("admitted {key}");
|
||||
}
|
||||
Command::Lower { store, key } => {
|
||||
let inventory = InventoryStore::open(&cli.inventory)?.inventory()?;
|
||||
let packet = PacketStore::open(&store)?
|
||||
let inventory_store = InventoryStore::open(&cli.inventory)?;
|
||||
let inventory = inventory_store.inventory()?;
|
||||
let mut packets = PacketStore::open(&store)?;
|
||||
let packet = packets
|
||||
.get(&key)?
|
||||
.with_context(|| format!("no packet {key}"))?;
|
||||
let view = validate(&packet, &inventory).map_err(|refusals| {
|
||||
anyhow::anyhow!(
|
||||
"{}",
|
||||
refusals
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join("; ")
|
||||
// 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)?,
|
||||
)
|
||||
})?;
|
||||
.map_err(refused)?;
|
||||
println!("{}", lower(&view, &inventory)?);
|
||||
}
|
||||
Command::Score(args) => {
|
||||
|
|
|
|||
|
|
@ -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 = 3;
|
||||
pub const SCHEMA_VERSION: u32 = 4;
|
||||
|
||||
/// Id of a referent or predication within one packet: `n<k>`, `k` a canonical
|
||||
/// decimal 1..=9999.
|
||||
|
|
@ -208,6 +208,8 @@ pub struct Packet {
|
|||
pub extensions: Vec<Extension>,
|
||||
#[cultcache(key = 12)]
|
||||
pub trace: Trace,
|
||||
#[cultcache(key = 13)]
|
||||
pub stance: Stance,
|
||||
}
|
||||
|
||||
impl Packet {
|
||||
|
|
@ -262,6 +264,14 @@ pub struct Discourse {
|
|||
pub features: Vec<FeatureValue>,
|
||||
}
|
||||
|
||||
/// 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<FeatureValue>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct FeatureValue {
|
||||
/// Feature id.
|
||||
|
|
@ -417,5 +427,6 @@ pub fn proof_packet() -> Packet {
|
|||
notes: vec!["First proof packet for Nibu flavored-English lowering.".to_string()],
|
||||
rejected_readings: vec![],
|
||||
},
|
||||
stance: Stance::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::{Refusal, validate};
|
||||
use crate::validate::{LoweringInput, Refusal, admissible};
|
||||
|
||||
/// A broken store rule, named.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -74,6 +74,9 @@ 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 },
|
||||
}
|
||||
|
|
@ -666,9 +669,10 @@ impl PacketStore {
|
|||
self.cache.get::<Packet>(key)
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
/// 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.
|
||||
|
|
@ -678,9 +682,15 @@ impl PacketStore {
|
|||
inventory: &InventoryStore,
|
||||
corpus: &CorpusStore,
|
||||
entities: &EntityStore,
|
||||
) -> Result<(), StoreError> {
|
||||
validate(&packet, &inventory.inventory()?).map_err(StoreError::Invalid)?;
|
||||
) -> Result<LoweringInput, StoreError> {
|
||||
let view = admissible(&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 {
|
||||
|
|
@ -702,11 +712,11 @@ impl PacketStore {
|
|||
let key = packet.id.to_string();
|
||||
if let Some(existing) = self.get(&key)? {
|
||||
if existing == packet {
|
||||
return Ok(());
|
||||
return Ok(view);
|
||||
}
|
||||
return Err(StoreRefusal::PacketImmutable { key }.into());
|
||||
}
|
||||
self.cache.put(key, &packet)?;
|
||||
Ok(())
|
||||
Ok(view)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
//! 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
|
||||
//! 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
|
||||
//! or a grade is its type's parse, not a rule, so V9 is retired and not
|
||||
//! renumbered.
|
||||
//!
|
||||
|
|
@ -25,7 +28,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,
|
||||
PacketKind, Predication, Referent, Relation, SCHEMA_VERSION, Stance,
|
||||
};
|
||||
|
||||
/// A broken rule, named.
|
||||
|
|
@ -91,9 +94,42 @@ impl fmt::Display for Refusal {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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<LoweringInput, Vec<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<Refusal>> { weksa::validate(p, i) }
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use weksa::{Inventory, LoweringInput, Refusal, packet::Packet};
|
||||
/// fn lower(p: &Packet, i: &Inventory) -> Result<LoweringInput, Vec<Refusal>> { weksa::validate(p, i) }
|
||||
/// ```
|
||||
pub fn validate(packet: &Packet, inventory: &Inventory) -> Result<(), Vec<Refusal>> {
|
||||
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<LoweringInput, Vec<Refusal>> {
|
||||
let mut out = Vec::new();
|
||||
if packet.schema_version != SCHEMA_VERSION {
|
||||
out.push(Refusal::V1SchemaVersion {
|
||||
|
|
@ -221,6 +257,7 @@ impl<'a> Inventoried<'a> {
|
|||
predications,
|
||||
relations,
|
||||
extensions,
|
||||
stance,
|
||||
..
|
||||
} = p;
|
||||
let Context {
|
||||
|
|
@ -238,6 +275,7 @@ 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,
|
||||
|
|
@ -349,8 +387,9 @@ 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 `validate` is the only constructor, so a lowerer
|
||||
/// cannot be handed an unvalidated packet or the key to an answer text.
|
||||
/// 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.
|
||||
///
|
||||
/// 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
|
||||
|
|
@ -394,6 +433,10 @@ 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.
|
||||
|
|
@ -430,6 +473,7 @@ pub struct LoweringInput {
|
|||
relations: Vec<Relation>,
|
||||
constraints: Constraints,
|
||||
extensions: Vec<Extension>,
|
||||
stance: Stance,
|
||||
}
|
||||
|
||||
impl LoweringInput {
|
||||
|
|
@ -444,6 +488,7 @@ impl LoweringInput {
|
|||
relations: p.relations.clone(),
|
||||
constraints: p.constraints.clone(),
|
||||
extensions: p.extensions.clone(),
|
||||
stance: p.stance.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -474,4 +519,7 @@ impl LoweringInput {
|
|||
pub fn extensions(&self) -> &[Extension] {
|
||||
&self.extensions
|
||||
}
|
||||
pub fn stance(&self) -> &Stance {
|
||||
&self.stance
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,11 @@ 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:11";
|
||||
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";
|
||||
/// Introduced at rev 2.
|
||||
pub const CONCEPT_A: &str = "concept:1";
|
||||
pub const CONCEPT_B: &str = "concept:2";
|
||||
|
|
@ -213,6 +217,7 @@ pub fn world() -> World {
|
|||
Slot::Referent,
|
||||
Slot::Predication,
|
||||
Slot::Relation,
|
||||
Slot::Stance,
|
||||
],
|
||||
)
|
||||
},
|
||||
|
|
@ -310,5 +315,17 @@ 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)),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -395,7 +395,7 @@ fn packet_entities_resolve() {
|
|||
|
||||
#[test]
|
||||
fn packet_schema_v2() {
|
||||
assert_eq!(SCHEMA_VERSION, 3);
|
||||
assert_eq!(SCHEMA_VERSION, 4);
|
||||
let mut p = good();
|
||||
p.schema_version = 2;
|
||||
let Err(refused) = validate(&p, &inventory()) else {
|
||||
|
|
@ -420,4 +420,10 @@ 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"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,24 @@ fn committed() -> (Inventory, tempfile::TempDir) {
|
|||
(inv.inventory().unwrap(), tempfile::tempdir().unwrap())
|
||||
}
|
||||
|
||||
fn view(packet: &Packet, inv: &Inventory) -> LoweringInput {
|
||||
validate(packet, inv).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 lowered(packet: &Packet, inv: &Inventory) -> Result<String, LowerError> {
|
||||
|
|
@ -137,7 +153,7 @@ fn a_packet_carrying_what_the_lowerer_does_not_render_is_refused() {
|
|||
let mut p = proof_packet();
|
||||
edit(&mut p);
|
||||
assert_eq!(
|
||||
lower(&view(&p, &inv), &inv),
|
||||
lower(&admitted(&p, &store), &inv),
|
||||
Err(LowerError::Unrendered { what }),
|
||||
"{what}"
|
||||
);
|
||||
|
|
@ -187,7 +203,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!(
|
||||
lowered(&proof_packet(), &inv),
|
||||
lower(&admitted(&proof_packet(), &store), &inv),
|
||||
Err(LowerError::GlossMismatch {
|
||||
id: iv("concept:4"),
|
||||
row: "control"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ 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,
|
||||
|
|
@ -867,7 +868,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, 54), (2, 6), (3, 1), (4, 1)]);
|
||||
assert_eq!(revisions, vec![(1, 61), (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();
|
||||
|
|
@ -891,7 +892,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), 11);
|
||||
assert_eq!(count(Family::Feature), 18);
|
||||
assert_eq!(
|
||||
count(Family::Concept) + count(Family::Frame) + count(Family::Extension),
|
||||
0
|
||||
|
|
@ -909,6 +910,46 @@ 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())
|
||||
|
|
@ -945,7 +986,7 @@ fn gloss_is_bounded_text() {
|
|||
|
||||
#[test]
|
||||
fn packet_admission_goes_through_the_validator() {
|
||||
let w = world();
|
||||
let mut w = world();
|
||||
let mut store = PacketStore::open(&w.dir.path().join("p.cc")).unwrap();
|
||||
let mut bad = good();
|
||||
bad.schema_version = 5;
|
||||
|
|
@ -960,13 +1001,18 @@ fn packet_admission_goes_through_the_validator() {
|
|||
other => panic!("{other:?}"),
|
||||
}
|
||||
assert!(store.all().unwrap().is_empty());
|
||||
store
|
||||
// The door yields the lowering view, stance included.
|
||||
let view = store
|
||||
.admit(good(), &w.inventory, &w.corpus, &w.entities)
|
||||
.unwrap();
|
||||
// The same packet is idempotent; a different one under the same key is refused.
|
||||
store
|
||||
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
|
||||
.admit(good(), &w.inventory, &w.corpus, &w.entities)
|
||||
.unwrap();
|
||||
assert_eq!(replay, view);
|
||||
let mut other = good();
|
||||
other.provenance.confidence = grade(1);
|
||||
assert_eq!(
|
||||
|
|
@ -977,6 +1023,24 @@ 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]
|
||||
|
|
@ -1206,6 +1270,21 @@ 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 {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
mod common;
|
||||
use common::*;
|
||||
use weksa::packet::*;
|
||||
use weksa::{Family, Refusal, Slot, validate};
|
||||
use weksa::{Family, PacketStore, Refusal, Slot, validate};
|
||||
|
||||
fn refusals(p: &Packet) -> Vec<Refusal> {
|
||||
validate(p, &inventory()).err().unwrap_or_default()
|
||||
|
|
@ -334,6 +334,83 @@ 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();
|
||||
|
|
@ -358,7 +435,12 @@ 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()];
|
||||
let input = validate(&p, &inventory()).unwrap();
|
||||
// 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 seen = format!("{input:?}");
|
||||
for hidden in [PACKET_ID, ENCODER_ID, "item:nibu:1", "SECRET"] {
|
||||
assert!(!seen.contains(hidden), "{hidden} reached the input: {seen}");
|
||||
|
|
@ -373,6 +455,9 @@ 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]
|
||||
|
|
@ -435,6 +520,14 @@ 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),
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -127,6 +127,7 @@ 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
|
||||
|
|
@ -135,8 +136,10 @@ revision a packet was written against. A grade is an integer permille,
|
|||
|
||||
### Validation
|
||||
|
||||
A packet exists only if `validate` passes; every path that admits or lowers a
|
||||
packet calls it. A refusal names the rule.
|
||||
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.
|
||||
|
||||
- V1: `schema_version` is the version these types define.
|
||||
- V2: at least one of `referents`, `predications`, `relations` is present.
|
||||
|
|
@ -158,9 +161,11 @@ packet calls it. A refusal 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. `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
|
||||
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
|
||||
reads at the packet's revision, never one the caller supplies.
|
||||
|
||||
### `kind`
|
||||
|
|
@ -182,9 +187,22 @@ ids.
|
|||
### `discourse`
|
||||
|
||||
Why the packet is said: `speech_act` is a speech-act id, `addressee` a node id,
|
||||
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.
|
||||
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.
|
||||
|
||||
### `referents`
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@
|
|||
"format": "uint32",
|
||||
"minimum": 0
|
||||
},
|
||||
"stance": {
|
||||
"$ref": "#/$defs/Stance"
|
||||
},
|
||||
"trace": {
|
||||
"$ref": "#/$defs/Trace"
|
||||
}
|
||||
|
|
@ -72,7 +75,8 @@
|
|||
"relations",
|
||||
"constraints",
|
||||
"extensions",
|
||||
"trace"
|
||||
"trace",
|
||||
"stance"
|
||||
],
|
||||
"$defs": {
|
||||
"Constraints": {
|
||||
|
|
@ -446,6 +450,21 @@
|
|||
"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",
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@
|
|||
"Discourse",
|
||||
"Referent",
|
||||
"Predication",
|
||||
"Relation"
|
||||
"Relation",
|
||||
"Stance"
|
||||
]
|
||||
},
|
||||
"ValueKind": {
|
||||
|
|
|
|||
402
settings/rust-to-dust/imperial/language-design.md
Normal file
402
settings/rust-to-dust/imperial/language-design.md
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
# Rust to Dust Imperial Language: Design Spec
|
||||
|
||||
Status: **DRAFT r0.** First conlang project for Rust to Dust. Nothing here is
|
||||
canon until the operator rules on it.
|
||||
|
||||
The language has no name. Its working slug is `rtd-imperial`. A name is owed
|
||||
and should come from the ontology and phonology below, as
|
||||
[setting.md](../setting.md) asks, not from an English label. Nothing here
|
||||
assigns one.
|
||||
|
||||
## How To Read This
|
||||
|
||||
Every claim carries a tag:
|
||||
|
||||
- **[E]** evidence. The source material says it, and the file is cited.
|
||||
- **[I]** invention. This draft proposes it. The operator can overrule it, and
|
||||
it is the part most worth attacking.
|
||||
- **[E→I]** an extension. The source gives a seed and this draft develops it.
|
||||
|
||||
Sources read: everything under `settings/rust-to-dust/` (`README`, `setting`,
|
||||
`aliens`, `story`, `battle-royale`, `language-design`), the house method in
|
||||
`settings/eusocial-interbeing/language-design.md`, and Weksa's
|
||||
`docs/language-model.md`, `interlingua-standard.md`,
|
||||
`best-modern-approach.md`, `multilingual-utterance-lowering.md` and
|
||||
`schemas/interlingua-packet.md`. The stance design is read from the unmerged
|
||||
branch `weksa-lowering/speaker-state` (see Dependencies). The forge repos and
|
||||
Weksa `state/` and `docs/` hold no other Rust to Dust material: a forge search
|
||||
for the setting returned nothing outside Weksa. This draft therefore rests on
|
||||
about 440 lines of setting prose, and the invention share is large.
|
||||
|
||||
## 1. Speech Community
|
||||
|
||||
### Who speaks it [E]
|
||||
|
||||
The empire's aliens: reptilian, scale-decorating, aesthetics-as-moral-truth,
|
||||
honor as devotion to aesthetic perfection in a chosen field (`aliens.md`). The
|
||||
empire is galaxy-spanning, so the language is a **network language**. Its
|
||||
community is defined by the imperial cultural network (media, prestige,
|
||||
commerce), not by a homeland. Humans are not native speakers. They meet it
|
||||
through subtitles, memes and broadcasts, and their contaminated English is a
|
||||
separate register (`language-design.md`, Registers).
|
||||
|
||||
### Speech situations [E→I]
|
||||
|
||||
`language-design.md` lists registers. This draft turns each into a situation
|
||||
with a speaker, an addressee and a stake, because a register is a relationship
|
||||
and not a vocabulary list:
|
||||
|
||||
| Situation | Speaker → addressee | Stake | Source |
|
||||
|---|---|---|---|
|
||||
| Broadcast commentary | commentator → mass imperial audience, about a performer | rendering an aesthetic verdict in real time | E (battle-royale) |
|
||||
| High honor speech | warrior or master → rival, patron or witness | binding a vow, naming a performance | E (register list) |
|
||||
| Warrior discourse | practitioner ↔ practitioner of one field | form, discipline, challenge | E (register list) |
|
||||
| Commercial acquisition | buyer ↔ seller, or buyer → cultural object | access and integration phrased as uplift | E (setting: commerce as delivery rail) |
|
||||
| Panel-show media | host ↔ guest before an audience | turning the guest's stance into performance | E (story: the panel) |
|
||||
| Tourist admiration | tourist → anything | consuming a safe spectacle | E (aliens: tourists) |
|
||||
| Address to humans | any → human | uplift, fascination, appraisal | I |
|
||||
|
||||
The last row is added. The story's central irony is that the empire
|
||||
understands the father's refusal better than he understands the empire, so
|
||||
how imperial speakers address humans is a design surface, not an afterthought.
|
||||
|
||||
The first proving slice is **broadcast commentary** [E]: `battle-royale.md`
|
||||
names it as the runtime use, and it needs the fewest situations to be
|
||||
distinguishable by a reader.
|
||||
|
||||
### Status ladder [I]
|
||||
|
||||
The source gives status *signals* (scale decoration marks profession, class,
|
||||
rank, grief, celebrity, courtship, school, combat discipline, public
|
||||
obligation, commitments) and a *measure* (the more elaborate the decoration,
|
||||
the more visible the claim of virtue). It gives no ladder. The invention:
|
||||
status is two axes, not one.
|
||||
|
||||
- **Field.** The discipline one is devoted to (combat, speech, hosting, trade,
|
||||
politics, art). Honor is field-relative [E]. A master of one field has no
|
||||
rank in another.
|
||||
- **Standing in the field**, by *claim and demonstrated constraint*, not by
|
||||
force:
|
||||
1. **Unclaimed.** No decoration in a field. This is shameless, not neutral
|
||||
[E: unpainted = shamelessness].
|
||||
2. **Claimant.** Decorated, undemonstrated. Tourists live here: the claim is
|
||||
bought and the constraint never chosen [E: tourists buy a safe spectacle].
|
||||
3. **Practitioner.** Decorated, with constraint demonstrated in the field.
|
||||
4. **Master.** Constraint demonstrated before witnesses.
|
||||
5. **Exemplar.** Master whose form others cite.
|
||||
- **Bare-by-claim** is an exceptional state off the ladder, not a step on it.
|
||||
Going unpainted as an artistic claim that the performance alone is art is a
|
||||
provocation [E]. The grammar must be able to say it without saying
|
||||
"shameless".
|
||||
|
||||
The language's address and agreement system tracks **claim versus
|
||||
demonstrated constraint**, because that is what the source treats as the
|
||||
moral measure. A tourist's loud, expensive decoration is a *mismatch* the
|
||||
language can state, and the language carries that mismatch in the grammar
|
||||
itself.
|
||||
|
||||
Decoration is the primary status channel in the world. In speech it surfaces
|
||||
as address terms, honorific-like marking and metaphor (Section 4), not as a
|
||||
phoneme-level trick.
|
||||
|
||||
## 2. Phonology (provisional)
|
||||
|
||||
The source gives no phonetic profile. It poses the question
|
||||
(`language-design.md`, Open Questions) and says naming should follow from
|
||||
phonetics. House method (Zyphos) assigns Airawa hard dorsal and coronal
|
||||
edges and Sa'auei'a soft flow. This language must not collapse onto either
|
||||
[I].
|
||||
|
||||
### Design stance [I]
|
||||
|
||||
Three pressures from the source, turned into sound:
|
||||
|
||||
1. **Aesthetic devotion**: the language should sound *composed*. Prosody is
|
||||
deliberate, with controlled length, no clutter.
|
||||
2. **Chosen constraint is mastery**: constraint should be audible. Phonotactics
|
||||
are *tight*, so that a speaker visibly refrains from the permitted-in-vulgar
|
||||
alternatives.
|
||||
3. **Reptilian body** [E: reptilian]: the inventory leans on sustained
|
||||
fricatives, hisses and trills that a non-human vocal tract makes easy. The
|
||||
source does not specify a vocal tract. This is the weakest-evidenced
|
||||
choice and a human-pronounceable romanization is therefore required.
|
||||
|
||||
### Inventory (draft)
|
||||
|
||||
- **Stops** (voiceless only): `t`, `k`, `ʔ`. No voiced stops. This separates it
|
||||
from Airawa's `g d q`.
|
||||
- **Fricatives**: `s`, `ʃ`, `x`, `h`, with voiced `z`, `ʒ`. Fricatives
|
||||
contrast in **length**: short and held. Held fricatives are the language's
|
||||
main prosodic ornament and carry register weight (Section 3).
|
||||
- **Sonorants**: `n`, `m`, `l`, trilled `r`.
|
||||
- **Vowels**: `a`, `i`, `u`, `e`, each short or long. A long vowel is the
|
||||
other register-bearing length.
|
||||
- **Syllables**: `(C)V(C)`, coda `s n l ʔ` only. No clusters. The absence of
|
||||
clusters is the phonotactic form of "chosen constraint".
|
||||
|
||||
### Prosody [I]
|
||||
|
||||
- Stress and length are the speaker's expressive budget. In formal
|
||||
registers, *less* length variation reads as more command of the form. A
|
||||
commentator in broadcast register may spend length on a performer's
|
||||
moment, and an honor-speech speaker withholds it. Restraint is therefore
|
||||
audible without a phoneme change (Section 3).
|
||||
- Broadcast commentary has a lengthened, sibilant "announcer" delivery. Tourists
|
||||
over-lengthen. Master speech under-lengthens.
|
||||
|
||||
### Romanization and rendering [I]
|
||||
|
||||
An ASCII romanization is owed before any lowering output is scored: the
|
||||
speaker-state lowering renders text, and the renderer for the audience needs
|
||||
a stable orthography. `ʃ ʒ x ʔ` need ASCII digraphs. The choice follows
|
||||
the Weksa pronunciation-lowering docs and belongs to a later cut.
|
||||
|
||||
### Open
|
||||
|
||||
- The inventory is a sketch, drawn from three pressures. It should be rerun
|
||||
after at least a hundred roots exist, not defended.
|
||||
- The source calls for names to "emerge from ontology and phonetics". This
|
||||
draft supplies phonetics, and the name waits for the first root set.
|
||||
|
||||
## 3. Register, Status and Restraint: Stance Explicit
|
||||
|
||||
### Weksa's constraint [E]
|
||||
|
||||
On the unmerged `speaker-state` branch, `docs/interlingua-standard.md` adds
|
||||
a packet `stance`: **felt affect** (anger, fear, sadness, joy, disgust, surprise,
|
||||
contempt, affection, shame, as grades) and **`restraint`**, how much of the
|
||||
felt affect is held back (0 none, 1000 fully). It is felt, never displayed.
|
||||
How a feeling shows and how much leaks is cultural; each language's ontology
|
||||
maps felt affect and restraint to its own register. A held-back feeling is not
|
||||
a claim, so it is never a predication.
|
||||
|
||||
This language must therefore **mark stance explicitly in its own grammar**,
|
||||
and the marking is derived from the packet's `stance`, never from English
|
||||
register words in the source text.
|
||||
|
||||
### Why this language is the hard case for restraint [E→I]
|
||||
|
||||
For the empire, restraint is not suppression of a shameful interior. It is
|
||||
**the visible form of chosen constraint**, which is the moral proof of
|
||||
mastery [E]. So the map from `restraint` to surface is *inverted* relative to
|
||||
most human defaults:
|
||||
|
||||
- High restraint is the *display* of virtue. A fully restrained speaker with
|
||||
strong felt affect is exhibiting form.
|
||||
- Low restraint with strong affect is not honest. It is *vulgar* [E: "vulgar
|
||||
violence as failed form"; "collapse into mere function"].
|
||||
- Zero affect is not neutrality. It can read as an absence of devotion.
|
||||
|
||||
The deliberate consequence is that stance cannot be flattened to a politeness
|
||||
level. Two axes are needed, and the source's lens questions (elegant,
|
||||
disciplined, sincere, vulgar, memorable) ask for both.
|
||||
|
||||
### Obligatory categories [I]
|
||||
|
||||
Proposed grammatical commitments (the target-language selections that step 4
|
||||
of `language-model.md` demands). Each names the packet field that feeds it.
|
||||
|
||||
1. **Finish marking** on predicates: an obligatory grade-class drawn from
|
||||
`stance.restraint`, coarsened to a few classes. Working classes:
|
||||
*raw*, *held*, *finished*. The unmarked form does not exist: leaving it
|
||||
off is not neutrality but a gap that the community reads as vulgar.
|
||||
Feeds from: `stance.restraint`.
|
||||
2. **Leak channel**: how much felt affect shows *through* a high-restraint
|
||||
frame. Leak is not mapped to emotion words. It is mapped to **form
|
||||
choices**: clipped versus full inflection, length spent on a fricative,
|
||||
word choice within a field. This follows `speaker-state`'s own example
|
||||
(anger leaking through clipped syntax is stance). Feeds from: felt affect
|
||||
grades against `restraint`.
|
||||
3. **Standing agreement** on referents: field and standing-in-field of a
|
||||
named performer, with the **claim/demonstrated** mismatch statable. Feeds
|
||||
from: speaker and scene features (Section 6).
|
||||
4. **Witness and form-evaluation**. Broadcast commentary is evaluative by
|
||||
situation. Evaluation is expressed as a **separate discourse layer**
|
||||
(a particle or clause layer), not as adjectives in the content: the
|
||||
lens questions [E] (elegant, disciplined, sincere, vulgar, memorable,
|
||||
promising, mastery, collapse) are the layer's open slots. It is
|
||||
kept apart from content because a held-back evaluation is not a claim
|
||||
about the world, and the interlingua keeps content and attitude
|
||||
separate. Feeds from: `discourse` features and the situation.
|
||||
5. **Evidential**: commentary asserts what the camera sees and *infers* the
|
||||
performer's sincerity. The inference must be marked, because the source
|
||||
puts sincerity in the performer, who is not the speaker and not available to
|
||||
telemetry. Feeds from: the evidentiality feature the standard already
|
||||
names.
|
||||
|
||||
### Register is a function of three inputs [I]
|
||||
|
||||
Register is not a setting on the speaker. It is selected from the pair
|
||||
(speaker's standing and community, scene relation to addressee and subject)
|
||||
and the situation, with stance as the final modulator. This is what
|
||||
`persona-drives-ontology` implies on the speaker-state branch and what the
|
||||
`language-ontology` cut will implement as deterministic selection
|
||||
`select(profile, stance, speaker, scene)`. This spec supplies the **content
|
||||
of that selection table** for this language; it does not write the table.
|
||||
|
||||
Cases the table must cover first:
|
||||
|
||||
| Case | Expected pull |
|
||||
|---|---|
|
||||
| Commentator, high restraint, strong approval of a restrained low-means act | finished marking; evaluative layer foregrounded; length withheld |
|
||||
| Commentator, low restraint, strong approval | raw marking; the speaker is performing delight. Only permitted in tourist-adjacent registers |
|
||||
| Master addressing rival | held or finished; address by field and standing |
|
||||
| Tourist, any affect | claimant standing; over-lengthened; evaluation layer present but misapplied |
|
||||
| Host to a human guest on a panel | warmth and appraisal mixed: affection plus contempt at high restraint is the *normal* pairing. The guest's rejection is absorbed as form |
|
||||
|
||||
The last row is the story's turn written as a stance mapping, and is worth
|
||||
testing first: contempt and affection together, restrained, with the evaluation
|
||||
layer calling the rejection *sincere*.
|
||||
|
||||
## 4. Meaning Before Surface
|
||||
|
||||
The language is never produced by substituting English words with alien
|
||||
ones. The method is the Weksa path (`language-model.md`): packet, then
|
||||
ontology projection that can split, collapse or reweight concepts, then
|
||||
grammar commitments, morphology, phonology, surface.
|
||||
|
||||
### Concept splits and collapses [E→I]
|
||||
|
||||
The source names distinctions the language must make (`language-design.md`,
|
||||
Open Questions). Each is decomposed below into components so a lowerer can
|
||||
select a form from the packet's facts and not from a gloss.
|
||||
|
||||
| Distinction | Decomposition (hypothesis) | Needs from the packet |
|
||||
|---|---|---|
|
||||
| **victory** vs **mastery** vs **spectacle** vs **vulgar overmatch** | victory: outcome of a contest. mastery: outcome *plus* demonstrated chosen constraint. spectacle: attention drawn, independent of constraint. vulgar overmatch: means exceed the opposing means with no constraint chosen. | outcome; each party's means; whether the means were chosen |
|
||||
| **crude** vs **failed** | crude: sincere, unrefined, with form attempted. failed: form collapsed into mere function. | actor's means and discipline as facts; the stance of the performer is not available, so the ontology reads from conduct |
|
||||
| **resistance** admired and absorbable | resistance read as: threat, folk art, breakout talent (`battle-royale.md` commentary list). Choice depends on whether it centers a performer or a practice. | performer-centered vs distributed agency |
|
||||
| **public spectacle** vs **living practice** [E] | spectacle: meaning lives in a central performer. practice: meaning lives in relationships. The empire reads distributed cooperation as noise or misattributes it to a performer. | number and relation of agents; presence of a focal agent |
|
||||
| **unpainted**, split | shame / danger / contempt / rawness / refusal / purity (the source lists these senses). Different surface forms: the sense is selected by relation and stance, not by a gloss. | decoration state, scene relation, speaker stance |
|
||||
|
||||
### Scale decoration in grammar and metaphor [E→I]
|
||||
|
||||
Decoration is the language's central metaphor domain [E: used for rank,
|
||||
grief, celebrity and more]. Proposed uses, to be tested not asserted:
|
||||
|
||||
- **Address**: terms of address name a decoration claim ("the one marked for
|
||||
...") instead of a rank noun.
|
||||
- **Insult**: lack, mismatch or fraud of decoration (a claim not backed by
|
||||
constraint). The strongest insult is the claimant who is also vulgar.
|
||||
- **Praise**: finish, as in a surface completed.
|
||||
- **Metaphor**: grief and obligation are *worn*. A human speaking of loss
|
||||
reads, in this language, as a person visibly undecorated by it.
|
||||
|
||||
The source does not say whether the language has these metaphors. They are
|
||||
the draft's strongest invention and the easiest to overrule.
|
||||
|
||||
### Root families [I]
|
||||
|
||||
House method: faint family resemblance, never a nametag. No roots are
|
||||
proposed here. The first root set should come from the field concepts
|
||||
(combat, speech, hosting, trade, art) and the constraint/finish axis, and
|
||||
should be minted against corpus items, not authored in a vacuum.
|
||||
|
||||
### Contamination direction [E]
|
||||
|
||||
What leaks into human English first (`language-design.md`) is an open
|
||||
question. [I] Candidates are evaluation-layer particles (the empire's form
|
||||
verdicts become meme tags) and the claim/finish vocabulary, because the
|
||||
story's kids absorb *prestige codes* first. The subtitle renderer is a
|
||||
separate target profile and not this spec's job.
|
||||
|
||||
## 5. Evidence and Invention, Summarized
|
||||
|
||||
**Evidence-backed (E):**
|
||||
- reptilian, aesthetic-honor, decoration-as-status speakers; unpainted as
|
||||
profound shamelessness; unpainted-as-provocation for a rare few
|
||||
- mastery requires chosen constraint; overmatch and tourists are vulgar; the
|
||||
lens questions
|
||||
- the empire is a network; commerce carries, culture conquers; the registers
|
||||
listed
|
||||
- broadcast commentary and flavor-first subtitles as the runtime use
|
||||
- the packet has `stance` with felt affect and restraint; the ontology maps
|
||||
them (speaker-state branch)
|
||||
|
||||
**Invention (I):**
|
||||
- the two-axis status ladder and the claim/demonstrated-constraint mismatch
|
||||
- the whole phonological inventory and prosody
|
||||
- the five obligatory categories and the inverted restraint reading
|
||||
- the decoration-grammar and metaphor uses
|
||||
- the speech situations table, notably the address-to-humans row
|
||||
- the contempt-plus-affection panel pairing
|
||||
|
||||
**Not settled in the source and left open:**
|
||||
- the language's and speakers' names; the vocal tract; whether the empire is
|
||||
one language community or a family of dialects; whether non-reptilian
|
||||
imperial subjects speak it and how it marks them
|
||||
|
||||
## 6. What The Lowerer Must Provide
|
||||
|
||||
Interlingua rule [E]: the packet carries neutral meaning plus source-side
|
||||
communicative pressure, and target-language concept splits belong
|
||||
downstream. The earlier Rust to Dust sketch in `language-design.md` puts an
|
||||
`aesthetic_reading` block (form, discipline, spectacle, honor status) **in
|
||||
the packet**. This draft recommends moving it: those are the *imperial
|
||||
ontology's reading* of neutral facts, so they belong in the ontology
|
||||
projection, and the packet carries the facts the reading is derived from.
|
||||
[I] This is a change to existing content and the operator should rule on it.
|
||||
|
||||
Fields, in order of need. "Seed" means the standard's inventory already has
|
||||
a slot. "Gap" means the standard has no field yet; per the Promotion Rule,
|
||||
the gap is first an `rtd.*` extension and is promoted only after use in two
|
||||
real packets.
|
||||
|
||||
| Need | Why | Status |
|
||||
|---|---|---|
|
||||
| `stance` with felt affect and `restraint` | the whole of Section 3 | seed (unmerged: `speaker-state`) |
|
||||
| **speaker** axes: field, standing in field, school or lineage, decoration state, community | status and register selection | seed slot `Speaker` (unmerged); the *values* are the gap |
|
||||
| **scene**: relative status of speaker, subject and audience; audience composition (imperial, tourist, human via subtitles) | register and address | seed slot `Scene` (unmerged) |
|
||||
| `medium` and `setting` ids for broadcast commentary | situation selection | seed (`context`) |
|
||||
| `instrument` role on predications, **for each party** | overmatch is derived from both parties' means | seed role |
|
||||
| **volition of the constraint**: chosen / compelled / unknown, as a predication feature | the line between mastery and mere lacking | gap (`rtd.` extension first) |
|
||||
| **ordering of means** (power or means gap) | vulgar overmatch | not a packet field: ontology-side order over concept ids, so the packet carries concepts and the ontology compares them |
|
||||
| referent **decoration state** (decorated / unpainted / bare-by-claim / not applicable) | status, insult, the human-undecorated reading | gap (`rtd.` extension first) |
|
||||
| referent **community** or `member_of` relation | tourist versus warrior; human versus imperial | seed relation `member_of`; community ids come from the `language-ontology` cut |
|
||||
| **focal agent** presence | spectacle versus living practice | derivable: a predication with plural or no agent role; not a new field |
|
||||
| `evidentiality` | witnessed versus inferred sincerity | seed feature (to confirm its option set) |
|
||||
| `polarity`, tense, aspect | ordinary | seed |
|
||||
| `preserve` | which nodes the commentary must keep | seed (`constraints`) |
|
||||
|
||||
Fields the draft deliberately does **not** request: `aesthetic_reading`,
|
||||
`honor_status`, `spectacle_value` or any value judgment. The performer's
|
||||
sincerity is not telemetry, and the commentator infers it. These are
|
||||
outputs of the imperial ontology and must not be put into the packet by the
|
||||
client, or the client is deciding the language's concepts.
|
||||
|
||||
## 7. Dependencies, Risks and Next Cuts
|
||||
|
||||
**Dependencies.** Stance and the `Speaker` and `Scene` slots exist only on
|
||||
the `weksa-lowering/speaker-state` branch. The `language-ontology` cut
|
||||
(community and ontology-entry documents) is not landed. This spec states
|
||||
the content those cuts will hold, and does not substitute for them.
|
||||
|
||||
**Risks.**
|
||||
- The inventory and the grammar are heavily invented on thin evidence. Mitigation:
|
||||
tag everything (done) and test the five obligatory categories against the
|
||||
lowerer before building roots.
|
||||
- The claim-versus-constraint ladder risks making the language a *status-code
|
||||
detector*. The source's hold is that honor is devotion, not rank. Test:
|
||||
can the grammar say a bare-by-claim performance without calling it low?
|
||||
- Draft collides with `language-design.md`'s packet-side `aesthetic_reading`.
|
||||
Resolved by the recommendation in Section 6 if ruled.
|
||||
|
||||
**Next cuts, in order.**
|
||||
1. Operator rulings on the forks below.
|
||||
2. A first corpus slice: ten broadcast-commentary utterances, each decomposed
|
||||
to a packet (neutral facts plus stance), with the expected surface *category*
|
||||
(finish class, evaluation layer) and no words yet.
|
||||
3. The selection table for Section 3's first five cases.
|
||||
4. A root set from the field concepts, and a naming brief for the language.
|
||||
5. An orthography and a pronunciation lowering.
|
||||
|
||||
**Forks for the operator (recommendations given).**
|
||||
- Move `aesthetic_reading` out of the packet into the ontology. *Recommend:
|
||||
yes.*
|
||||
- Is a two-axis status ladder right, or should honor be flat within a field?
|
||||
*Recommend: two axes, because tourists need a place.*
|
||||
- Is inverted restraint (restraint as display of virtue) the intended reading?
|
||||
*Recommend: yes, it follows from "chosen constraint is mastery".*
|
||||
- Is the vocal tract reptilian-literal or a human-stylized invention?
|
||||
*Recommend: human-pronounceable stylization with length as ornament, so
|
||||
the first corpus does not wait on phonetics.*
|
||||
Loading…
Reference in a new issue