# NeuroGlyph — Грамматика GlyphGraph v0.1

**Статус:** Фаза 1, итерация 1.0 · **Дата:** 2026-08-11
**Основа:** схема `schemas/message-v0.3.json` + канон v15 (`CANON-V15.md`) + набросок BNF от Qwen (P0 Review)
**Согласовано:** единый `discourse` контейнер — в v0.4 (D-01); ирреалис через REALIS, пресуппозиция отложена (D-02/D-03); **P1 Review Qwen** (2026-08-11): В1-В5 приняты — детали в `DECISIONS-QWEN-P1.md`

---

## 0. Принципы

1. **Глиф-граф — rooted DAG.** Один корень: либо единый `root`, либо `discourse` с корнями юнитов. Не оба (D13).
2. **Роли могут дублироваться** (два ArgM-LOC) — аргументы это массив, не объект (D1).
3. **Кореференция** через `node_id`/`ref` — узел может быть описан один раз и переиспользован (D3).
4. **Макро обязаны иметь decomposition** — обратная совместимость (D11).
5. **Канонический порядок обхода** — один граф = одна сериализация (необходимо для хэшей и обучения).

---

## 1. BNF — грамматика

```bnf
(* ===== Сообщение верхнего уровня ===== *)
Message       ::= "NG/" Version Intent Mode? Content Envelope? Provenance? Fallback?
Version       ::= "0.3"
Intent        ::= "act=" IntentName
IntentName    ::= "ASSERT" | "QUERY" | "REQUEST" | "COMMAND" | "EMOTE"
                | "NEGATE" | "OFFER" | "REFUSE" | "CONFIRM" | "ALERT"
                | "INITIATE" | "PROCEED" | "REPAIR" | "NIL"
Mode          ::= "mode=" ModeName
ModeName      ::= "GENERIC" | "INVITE" | "START" | "PROCEED" | "ENCOURAGE"
                | "ACCEPT" | "CHALLENGE" | "SOFTEN"
                | "DECLARATIVE" | "INTERROGATIVE" | "IMPERATIVE" | "EXPRESSIVE"
                | "NIL"

(* ===== Контент: root XOR discourse (D13) ===== *)
Content       ::= "root=" RootGlyph | "discourse=" Discourse

(* ===== Discourse (многоклаузное) ===== *)
Discourse     ::= "{" Units ("," Relations)? "}"
Units         ::= "units:" "[" Unit ("," Unit)* "]"
Unit          ::= "{" UnitId? "act=" IntentName "root=" RootGlyph "}"
UnitId        ::= "id:" Identifier
Relations     ::= "relations:" "[" Relation ("," Relation)* "]"
Relation      ::= "{" "rel:" RelType "from:" Ref "to:" Ref "}"
RelType       ::= "BECAUSE" | "IF" | "BEFORE" | "AFTER" | "WHILE"
                | "CONTRAST" | "CONCESSION" | "PURPOSE" | "SEQUENCE" | "ELABORATION"

(* ===== Глифы ===== *)
RootGlyph     ::= GlyphInstance
GlyphInstance ::= GlyphId (":" FeatureMask)? ("[" Arguments "]")? ("#" NodeId)? ("~" Decomposition)?
GlyphId       ::= HexNumber            (* 0x00-0x1F структуры, 0x20-0x60 примы, 0x80+ макро *)
FeatureMask   ::= HexNumber            (* uint32, 0 по умолчанию *)

(* ===== Аргументы ===== *)
Arguments     ::= Argument ("," Argument)*
Argument      ::= Role "=" (GlyphInstance | Ref | Literal)
Role          ::= "Arg0" | "Arg1" | "Arg2" | "Arg3" | "Arg4" | "Arg5"
                | "ArgM-LOC" | "ArgM-TMP" | "ArgM-MNR" | "ArgM-CAU" | "ArgM-PRP"
                | "ArgM-ADV" | "ArgM-COM" | "ArgM-BEN" | "ArgM-DIR" | "ArgM-GOL"
                | "ArgM-SRC" | "ArgM-TOP" | "ArgM-NEG" | "ArgM-DIS" | "ArgM-MOD"
                | "ArgM-EXT" | "ArgM-LVB"

(* ===== Кореференция ===== *)
Ref           ::= "#" Identifier      (* ссылка на node_id *)

(* ===== Decomposition (обязателен для макро 0x80+) ===== *)
Decomposition ::= "[" DecompGlyph ("," DecompGlyph)* "]"
DecompGlyph   ::= GlyphId

(* ===== Литералы ===== *)
Literal       ::= LitNumber | LitString | LitBoolean | LitTime | LitUri
LitNumber     ::= "-"? Digit+ ("." Digit+)?
LitString     ::= "\"" Char* "\""
LitBoolean    ::= "true" | "false"
LitTime       ::= Digit{4} "-" Digit{2} "-" Digit{2} ("T" Digit{2} ":" Digit{2} (":" Digit{2})?)?
LitUri        ::= "uri:" URL

(* ===== Обёртки ===== *)
Envelope      ::= "envelope=" "{" Tone? Register? Urgency? Politeness? "}"
Tone          ::= "tone:" ("neutral"|"warm"|"cold"|"playful"|"ironic"|"sarcastic"
                | "serious"|"urgent"|"calm"|"excited"|"sad"|"angry"|"encouraging"
                | "reluctant"|"aggressive"|"gentle"|"NIL")
Register      ::= "register:" ("formal"|"neutral"|"informal"|"intimate"|"slang")
Urgency       ::= "urgency:" ("low"|"normal"|"high"|"critical")
Politeness    ::= "politeness:" ("blunt"|"neutral"|"polite"|"deferential")
Provenance    ::= "provenance=" "{" SourceHash? Span? Annotator? Confidence? Glosses? "}"
SourceHash    ::= "source_hash:" HexString
Span          ::= "span:" String
Annotator     ::= "annotator:" ("human"|"ai"|"hybrid")
Confidence    ::= "confidence:" Float
Glosses       ::= "glosses:" "[" Gloss ("," Gloss)* "]"
Gloss         ::= "{" "tag:" String "explanation:" String "}"

(* ===== Лексемы ===== *)
Identifier    ::= Letter (Letter | Digit | "_")*
HexNumber     ::= "0x" HexDigit+
HexString     ::= HexDigit+
HexDigit      ::= "0"-"9" | "a"-"f" | "A"-"F"
Digit         ::= "0"-"9"
Letter        ::= "a"-"z" | "A"-"Z"
```

---

## 2. Канонический порядок обхода (детерминированная сериализация)

### Canonical Form (правило для энкодера и хэшей)

> **Аргументы сохраняют порядок записи** — семантически значимо (Arg0=Agent, Arg1=Patient;
> `[Arg0=I, Arg1=YOU]` ≠ `[Arg0=YOU, Arg1=I]`). **Relations дискурса сортируются**
> лексикографически по `(rel, from, to)` — порядок связей не несёт смысла.
> Один и тот же граф → ровно одна каноническая строка.

**Зачем это:** один и тот же граф всегда даёт одну строку. Это критично для:
- стабильных хэшей сообщений (кэши, идемпотентность);
- обучения (инвариантность к порядку записи);
- кодирования (write-path может выбрать любой порядок, канон нормализует).

**Порядок:**

```
1. Корень:             root=<glyph>
2. Для каждого узла:
   a. glyph_id          (по hex, возрастание не важно — граф, не список)
   b. feature_mask      (если != 0)
   c. аргументы         (НЕ сортировать! роли в порядке записи, дубли сохраняются)
   d. node_id           (# если задан)
   e. decomposition     (~ если макро)
3. Для discourse:
   a. units             (в порядке записи, unit_id обязателен если >1 юнита)
   b. relations         (сортировка по (rel, from, to) для детерминизма)
```

**Ключевое:** аргументы НЕ сортируются (роли могут дублироваться, порядок значим),
а relations дискурса сортируются. Почему так: порядок аргументов — семантика
(это массив), порядок relations — нет (это набор связей).

---

## 3. Валидность и типизация диакритик

### 3.1. Базовые правила валидности

1. **Корень обязателен:** либо `root`, либо `discourse` (не оба, не ни одного).
2. **GlyphId существует в каноне:** 0x20–0x60 — примы, 0x80+ — макро (проверка по registry.json).
3. **Макро 0x80+ обязаны иметь decomposition** (D11).
4. **Ref указывает на существующий node_id** в том же сообщении.
5. **Discourse с >1 юнитом требует relations** (иначе смысл связей потерян).
6. **Envelope и diacritics не конфликтуют:** tone в envelope ≠ VALENCE в маске (разные уровни).

### 3.2. Типизация диакритик (какие оси к каким глифам применимы)

| Ось | Применима к | Не применима к | Пример |
|---|---|---|---|
| INTENSITY | GOOD/BAD, FEEL, эмоции | I/YOU (субстантивы), кванторы → прим **VERY** | GOOD:INTENSITY=high → «очень хорошо» |
| VALENCE | FEEL, GOOD/BAD, события | субстантивы → прямое GOOD/BAD | FEEL:VALENCE=negative |
| TIME | DO/HAPPEN/MOVE, предикаты | субстантивы → примы **BEFORE/AFTER** | DO:TIME=past |
| ASPECT | DO/HAPPEN, действия | субстантивы | DO:ASPECT=ongoing |
| EPISTEMIC | ASSERT-предикаты (KNOW, SAY) | субстантивы | KNOW:EPISTEMIC=unsure |
| EVIDENTIAL | KNOW, SAY | субстантивы | KNOW:EVIDENTIAL=reported |
| POWER/RESPECT | аргументы (к кому обращено) | сам глиф | REQUEST:POWER=defer_up |
| SOCIAL | REQUEST/COMMAND/SAY | субстантивы | SAY:SOCIAL=formal |
| AGENCY | DO/HAPPEN | субстантивы | DO:AGENCY=accidental |
| REALIS | любые предикаты | субстантивы | DO:REALIS=counterfactual (D-02) |

**Формализация (P1 Review Qwen, В2):** для каждой оси при Фазе 2 добавим в registry.json
поле `applicable_categories` (список категорий, к которым применима ось) — машиночитаемо,
чтобы валидатор мог проверять диакритики по типам глифов.

**Принцип:** диакритики модулируют **предикат** (что происходит), не **субстантив**
(кто/что). Исключение — POWER/SOCIAL, которые привязаны к *отношению* между
агентами и кодируются на предикате/интенте, а не на субстантиве.

---

## 4. Примеры

### 4.1. Одноклаузное (root)

```
NG/0.3 act=EMOTE mode=GENERIC tone=sad root=0xC0:0x0008[Arg0=0x20]
(* TOSKA, negative, про меня *)
```

### 4.2. Макро с decomposition

```
NG/0.3 act=INITIATE mode=INVITE root=0x80:0x0024[Arg0=0x20,ArgM-COM=0x21]~
    [0x37,0x3F,0x49,0x31]
(* INITIATE_JOINT = WANT+DO+NOW+GOOD *)
```

### 4.3. Многоклаузное (discourse)

```
NG/0.3 act=ASSERT discourse={units:[
    {id:u1, act:ASSERT, root=0x3F[Arg0=#x1,Arg1=#x2]},
    {id:u2, act:ASSERT, root=0x41[Arg0=#x1,Arg1=#x2,ArgM-LOC=0x51]}
  ], relations:[{rel:SEQUENCE, from:u1, to:u2}]}
(* «Он взял её и положил туда» — кореференция через node_id *)
```

### 4.4. Ирреалис (контрфакт)

```
NG/0.3 act=ASSERT mode=GENERIC root=0x3F:0x00020000[Arg0=0x20]
(* REALIS=counterfactual: «…не пришёл бы» *)
```

---

## 5. Открытые вопросы (в v0.4)

1. **Единый `discourse` контейнер** (D-01): `units=[root]` всегда, `root` на верхнем уровне убрать. Решит многословность простых случаев.
2. **Пресуппозиции** (D-03): слой `presuppositions` в discourse — отложено до v1.1.
3. **Литералы:** `time` — какой формат точный (ISO-8601 с таймзоной?).
4. **Нейтральные роли для макро:** INITIATE_JOINT требует ArgM-COM — достаточно ли это выразительно для «давай, но если не против»?
