sprawling-kernel 0.0.9

Every decision of a sprawling city - addresses, events, gates, plans and errors - with nothing that touches a disk.
Documentation
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-- Copyright (c) 2026 2youg1 and the sprawling contributors

import crates.kernel.spec.Event.Kind

/-!
# kernel::event

规定 `kernel::event`(`crates/kernel/src/event.rs` 与 `crates/kernel/src/event/` 下的 `identity`、`payload`、`scope`、`who`、`moment`)以及 `kernel::origin`(`crates/kernel/src/origin.rs`):信封、规范字节、`EventRef` 的铸造、信封 `t` 的语义与可忽略性。本文件是 `crates/kernel/Spec.lean` 的一个分部;下面每一节保留它在 kernel 规格里的标签 §8-n,别处引作 `crates/kernel/Spec.lean §8-n`。
-/

/-!
### 8-4 kernel::event

```rust
pub struct RunId(Uuid);                 // uuid v7 仅人读标识;kernel 不生成
impl RunId { pub const CITY: RunId;     // nil UUID:city 级事件哨兵
             pub fn from_bytes([u8;16]) -> Self;  pub fn parse(&str) -> Result<Self, AxError>;
             pub fn as_bytes(&self) -> &[u8; 16]; }
// 反序列化:人读格式(JSON 行、帧)只收 parse 认的那一种拼写;二进制格式(views 快照的 postcard)
// 按 uuid 的 16 字节读回,与序列化对称——否则存着 RunId 的 views 快照与 twin 都解不回来。
pub struct Seq(u64);
impl Seq { pub const FIRST: Seq;        // 0;创世行
           pub fn next(self) -> Result<Seq, AxError>; }   // checked_add
pub struct TimeMs(u64);                 // UTC 整数毫秒;只入参不采样

pub enum EventKind { CityInitialized, /* …余下 variant 与两分见本节表,serde 蛇形 */ }
pub enum WindowClass { InWindow, RecordOnly }
impl EventKind {
    pub fn window_class(&self) -> WindowClass;  // 穷尽 match;二分权威
    pub const ALL: [EventKind; N];              // N=本节表的行数;与本节表同一名册:specalign 与计数断言的数据面
}

pub struct Payload(serde_json::Map<String, Value>);
impl Payload {
    /// Sole constructor: rejects any float anywhere in the tree
    /// (determinism rule 6) and any nesting deeper than
    /// PAYLOAD_DEPTH_MAX (126: the 127 containers serde_json reads, minus the envelope).
    /// Deserialize re-validates on read.
    pub fn new(map: Map<String, Value>) -> Result<Self, AxError>;  // E_INVALID_ARGS
    pub fn empty() -> Self;
    /// 写侧唯一门:record 结构 -> 载荷;非对象即 E_INVALID_ARGS。
    pub fn of(record: &impl Serialize) -> Result<Self, AxError>;
    /// 读侧唯一门:载荷 -> record 结构;读不动即 E_WIRE_MISMATCH。
    /// 借出而不复制:serde_json 的 `&Map` 反序列化器直接读持有的载荷,
    /// 读一次只分配 T 自己拥有的字段,不先克隆整张 map。
    pub fn read<T: DeserializeOwned>(&self) -> Result<T, AxError>;
    pub fn as_map(&self) -> &Map<String, Value>;
    /// 交出 map 本身:就地改写再造新载荷的读者不必多复制一份。
    pub fn into_map(self) -> Map<String, Value>;
}

pub struct EventDraft {                 // 调用方给的一半:语义内容
    pub run: RunId, pub t: TimeMs, pub who: String,
    pub addr: Option<Address>, pub kind: EventKind, pub data: Payload,
    pub ig: bool,                       // 「可忽略」标记;写方默认 false
}
pub struct EventRecord { /* v, run, seq, prev, t, who, addr, kind, data, ig —— 字段私有 */ }
impl EventRecord {
    /// Adapter-side assembly: the Ledger implementation owns seq/prev/v.
    pub fn from_draft(draft: EventDraft, seq: Seq, prev: B3Hash) -> Self;  // v = EVENT_LOG_V
    /// Canonical bytes: serde_json, struct field order = declaration order,
    /// payload keys sorted (serde_json BTreeMap), no trailing newline.
    pub fn canonical_line(&self) -> Result<Vec<u8>, AxError>;
    pub fn to_ref(&self) -> EventRef;   // 铸造需持有整条记录
    pub fn parse_line(raw: &[u8]) -> Result<Self, AxError>;  // 读侧:逐字段复验
    pub fn seq(&self) -> Seq;  pub fn kind(&self) -> EventKind;  pub fn v(&self) -> u32;
}
// event::moment:一行的 t 记的是不是它自己那一刻(见下「信封 t 记的是什么」)
impl EventKind { pub fn records_a_moment(&self) -> bool; }   // model_called、model_returned、tool_called、tool_result
impl EventRecord { pub fn moment(&self) -> Option<TimeMs>; }  // 记时刻的种类且 v ≥ 2 → Some(t);其余 None
pub struct EventRef { seq: Seq, kind: EventKind }   // 字段私有;无公开构造子
// kernel::origin:一个 session 从哪里分出来
pub struct Origin { pub run: RunId, pub at_seq: Seq }   // 来源 run,与分支继承的它的最后一行
// event::who:载荷里一行的行动者(信封的 who 仍是字符串,见 kernel 规格 §3 第 5 条)
pub enum Who { City, Person, Resident(Address) }    // 线上 "city" | "person" | 居民地址,与账本旧行同拼
impl Who {
    pub fn resident(addr: Address) -> Result<Who, AxError>;  // 地址拼作 city 或 person 即 E_INVALID_ARGS
    pub fn parse(raw: &str) -> Result<Who, AxError>;          // as_str 的逆;既非保留词也非规范地址即 E_INVALID_ARGS
    pub fn as_str(&self) -> &str;
    pub fn handed_down_by(&self, predecessor: Option<RunId>, parent: Option<RunId>) -> String;
        // 模型在任务文件与开场消息里读到的「由谁交下来」:居民带交活的 run(继任者优先,其次委派的父 run),
        // 敲门两者皆无只写地址;任务文件与开场消息由两个 crate 写,所以拼法只在这里(city SpineFiles D21)
}
pub fn escape_markup(text: &str) -> String;  // `<` `>` `&` 写成实体:标记段落里的唯一转义器,读回三个实体即得原文
```
-/

/-!
- 序列化细节:`addr` 为 None 与 `ig` 为 false 时省略键;其余八键恒在;键序=声明序 `v,run,seq,prev,t,who,addr,kind,data,ig`。此即 V8 跨平台字节一致的规范。

**信封 `t` 记的是什么。** 本段是时间语义的唯一权威;读者经 `EventRecord::moment` 取用,不从导出它的构建、也不从相邻行的 `t` 推断。
- `t` 是写方经注入的时钟采到的 UTC 整数毫秒,kernel 从不采样。
- `v` 为 2 起,`model_called`、`model_returned`、`tool_called`、`tool_result` 四种行记这一行自己那件事的时刻:一次模型尝试发出、一次回复到齐、一次工具调用开始(放行之后、工具起跑之前)、一次调用答复。`EventKind::records_a_moment` 是这四种的名册,`moment` 对这样的行答 `Some(t)`。
- `v` 为 1 的行里,这四种带的是它所在回合的时间戳,同一回合的行同值。`moment` 答 `None`,意思是「这一刻没有量过」,不是零耗时。
- 其余种类的 `t` 是写方为这一行采的一次读数;一个回合里的其余行沿用回合时间戳(`crates/runtime/Spec.lean` §8-15)。
- `t` 不随 `seq` 单调:并行只读调用的开始可以早于前一条调用的答复,壁钟也会回拨。次序以 `seq` 为准。
- 重启之后由 `runtime::replay::outcome_unknown_draft` 补上的 `tool_result`(错误码 `E_TOOL_OUTCOME_UNKNOWN`)记的是城补上它的时刻,不是工具答复的时刻;量工具用时的读者跳过这样的行。

- 铸造纪律:`EventRef` 唯二铸造路径=Ledger append 流程(适配器持刚组装的 EventRecord 调 `to_ref`)与 replay 验链后逐条 `to_ref`。字段私有,所以在 crate 外写不出 `EventRef` 的字面量。
- `parse_line` 是读侧唯一入口:serde 反序列化+Payload 复验;未知 kind 在此报错(呈现语义见 runtime::replay 章——携 `ig` 的行例外)。
-/

namespace Kernel.Event

open Kernel.Event.Kind

/-! ### 可忽略性:`ig` 授的是跳过权,只在种类未知时有用

读者在一行的 `kind` 里读到的,要么是本构建认得的种类,要么是一个更新的构建写下的词。本构建认得的照常全解,`ig` 不改变什么;不认得的,写方标了 `ig: true` 就跳过它的类型化解读,没有标就拒读整本账本(`E_LOG_VERSION_UNSUPPORTED`,说出方向)。跳过的行照样在链上:它的字节被它的后继取过摘要(`crates/kernel/spec/Ledger.lean`),跳过的只是解读。这条规则的生产实现是 `storage::jsonl::verify` 的 `classify`(以及 `runtime::replay`),本节是它的权威。 -/

/-- 读者在一行的 `kind` 里读到的东西。 -/
inductive Spelled where
  | known (kind : EventKind)
  | unknown (word : String)

/-- 一行在读者那里的去向:类型化全解、跳过、或拒读。 -/
inductive LineClass where
  | typed (kind : EventKind)
  | skipped
  | refused (word : String)
  deriving DecidableEq

/-- 一行的种类与它的 `ig` 决定它的去向。 -/
def classify : Spelled → Bool → LineClass
  | .known kind, _ => .typed kind
  | .unknown _, true => .skipped
  | .unknown word, false => .refused word

/-- 本构建认得的种类恒被类型化全解,带不带 `ig` 都一样:`ig` 不能把一行认得的历史藏起来。 -/
theorem ig_never_hides_a_known_kind (kind : EventKind) (ig : Bool) :
    classify (.known kind) ig = .typed kind :=
  rfl

/-- 一行被跳过,当且仅当它的种类本构建不认得、且写方标了 `ig`。 -/
theorem a_line_is_skipped_exactly_when_unknown_and_marked (spelled : Spelled) (ig : Bool) :
    classify spelled ig = .skipped ↔ (∃ word, spelled = .unknown word) ∧ ig = true := by
  cases spelled <;> cases ig <;> simp [classify]

/-- 不认得又没有 `ig` 的一行被拒读,拒读带着那个词。 -/
theorem an_unmarked_unknown_kind_is_refused (word : String) :
    classify (.unknown word) false = .refused word :=
  rfl

/-- D10 信封 `t` 记这一行等来的时刻,`EVENT_LOG_V` 因此为 2

**决定**:四种等来的行的 `t` 记各自那一刻(runtime D5),账本版本随之从 1 进到 2;`EventRecord::moment` 读 `v` 与种类,答这一行的 `t` 是不是量出来的时刻。

**理由**:`v` 是每一行自带的版本,读者据它区分两种含义,不用猜。一个 `t` 同时服务结果上的戳、时间窗与调用用时,不在载荷里另加时刻字段。

**被否**:①载荷加时刻字段:一行两个时间;②不进版本,让读者按导出构建或相邻行的 `t` 是否相等推断:同一回合里真的零毫秒的两次调用与旧行分不开;③在 `run_started` 记一个时间语义标记:一次跨越二进制升级继续的 run 会让两种行落在同一个标记之下(推断:`resume` 路径能让 run 跨重启继续)。

**代价**:0.0.7 的构建读到 `v` 为 2 的行,按 `LogVersion::Ahead` 拒开整座城(`E_LOG_VERSION_UNSUPPORTED`,说出方向)。本版新增的事件种类已经让它打不开,所以人看到的结果不变,只是拒因改说版本。

**重开参数**:出现第二种需要逐行区分的时间含义时重开本条,例如回合里其余的行也改记自己的时刻。
-/
def records_a_moment : EventKind → Bool
  | .ModelCalled => true
  | .ModelReturned => true
  | .ToolCalled => true
  | .ToolResult => true
  | _ => false

/-- 一行自己的时刻(`EventRecord::moment`):记时刻的种类、且账本版本不低于 `since`(Rust 的 `MOMENTS_SINCE_V`)时是它的 `t`,其余一律 `none`。`none` 的意思是「这一刻没有量过」,不是零耗时。 -/
def moment (since : Nat) (kind : EventKind) (v t : Nat) : Option Nat :=
  if records_a_moment kind = true ∧ since ≤ v then some t else none

/-- 早于 `since` 写下的行没有自己的时刻,哪一种都一样:那时这四种行带的是回合时间戳。 -/
theorem an_older_line_has_no_moment (since : Nat) (kind : EventKind) (v t : Nat) (older : v < since) :
    moment since kind v t = none := by
  simp only [moment]
  split
  · omega
  · rfl

/-- 有时刻的行恰是那四种之一,且它的时刻就是信封的 `t`。 -/
theorem a_moment_is_the_envelope_t (since : Nat) (kind : EventKind) (v t m : Nat)
    (measured : moment since kind v t = some m) :
    records_a_moment kind = true ∧ since ≤ v ∧ m = t := by
  simp only [moment] at measured
  split at measured
  · rename_i held
    cases measured
    exact ⟨held.1, held.2, rfl⟩
  · cases measured

/-- 记自己时刻的四种都入窗:时刻属于决定模型请求的那几种行,只入账的行沿用写方采的那一次读数。 -/
theorem a_kind_with_a_moment_is_in_window (kind : EventKind) (records : records_a_moment kind = true) :
    kind.windowClass = .InWindow := by
  cases kind <;> simp_all [records_a_moment, EventKind.windowClass]

/-- `Seq::next`:checked 加一。`bound` 是 `u64` 能装下的值的个数,到顶时拒绝(`E_INVALID_ARGS`)而不回绕,所以下一行的 `seq` 恒比上一行大一。 -/
def Seq.next (bound seq : Nat) : Option Nat :=
  if seq + 1 < bound then some (seq + 1) else none

theorem Seq.next_is_one_more (bound seq after : Nat) (stepped : Seq.next bound seq = some after) :
    after = seq + 1 := by
  simp only [Seq.next] at stepped
  split at stepped
  · cases stepped
    rfl
  · cases stepped

/-- 正常路径可实现:创世行之后的一行是 1。 -/
example : Seq.next (2 ^ 64) 0 = some 1 := by decide

end Kernel.Event