sprawling-runtime 0.0.12

One run of a sprawling resident, from dispatch to freeze: turns, tools, replay and the sandbox that executes code.
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

/-!
# runtime::mode

规定 `mode`(`crates/runtime/src/` 下同名的文件)。每个 mode 在目录里怎么介绍,以及一次 run 的产出准不准合并。本文件是 `crates/runtime/Spec.lean` 的一个分部;下面每一节保留它在 runtime 规格里的标签 §8-n,别处引作 `crates/runtime/Spec.lean §8-n`。
-/

/-!
### 8-12 runtime::mode(形状 6;dev 入口)


```rust
pub const DEV_ENTRY: &str = "dev";
pub fn dev_entry() -> CatalogEntry;   // 一行披露,全部细则归 expansion
```

- **一个 Run 只被告知它所在的那个 mode**,于是没有任何 Agent 知道这座城自己的代码与 SPEC 是可改的。`dev` 行补上这一句,**而且只补一句**:三种准入证据要求与两种落地策略的意思、阅读次序(SPEC → 代码 → 旁边的测试)与「下一步去跟人要它们」全在 expansion 里,由 `read` 按需取。**大多数会话不改这座城,就只付一行的价。**
-/

/-!
### 8-12b runtime::mode 原有面


```rust
pub fn catalog_entry(mode: kernel::Mode) -> CatalogEntry;     // chat 与 work 两行
```

`Chat` 的目录行只有一句:专心同人交谈,就对方说的话作答。除这一行提示之外它什么也不做,它存在的理由是让一句闲话不被当成一件要做的活。`Work` 的目录行说:朝人给的目标干活,任务要一份计划时先用 `plan` 工具写进 `Roadmap.md`,目标达成时报告。

哪些 mode 存在、各自拼成什么词,只由 `kernel::Mode` 回答(线、账本都读它);本模块只持每个 mode 在目录里怎么介绍、以及一次 run 的产出准不准合并(§8-54)。runtime 不再有自己的 `Mode`:两份同成员的枚举要靠装配层一个恒等的 `match` 维系,新增一个 mode 时那是第二处必须同步改的地方。
-/

/-!
### 8-54 合并时的准入,按运行策略判(`runtime::mode::admits`,形状 1 判定)


```rust
pub struct Produced { pub tests_passed: Option<bool>, pub contract_moved: bool,
                      pub held_in: Option<bool>, pub held_out: Option<bool> }
pub enum Admission { Lands, Refused { because: &'static str, alternative: &'static str } }
pub fn admits(policy: &kernel::RunPolicy, produced: &Produced) -> Admission;
```

- **判定序**:先看落地策略——`Experiment` 恒 `Refused`(试验的产出不合并,学到的写进 `Memo.md`,换一次常规落地的派活再做);`Ordinary` 再看准入证据要求:`Standing` 恒 `Lands`(楼自己的规矩已经在别处判过,本函数不加检查);`Tested` 要 `tests_passed == Some(true)`,`Some(false)` 与 `None` 各有自己的拒词;`ContractKept` 在 `contract_moved` 时拒;`DoubleValidated` 要 held-in 与 held-out 两半都是 `Some(true)`,缺一半与任一半为 `Some(false)` 各有拒词。
- **判的是 run 结束时格里的策略**:会话中换过运行策略的 run(§8-62),合并时读它最后一次在 `BeforeWave` 取用的那一份,即 `PolicyCell` 在 run 冻结时的值;没被它取用的改动属于下一个 run。
- **mode 不参与准入**:交谈与干活产出的东西走同一道合并,要不要证据由证据要求一个值回答(kernel D12)。
- **唯一的调用方是合并那一刻**:`accounting::worker::reviewing` 在 `PrEffect::Merged` 写 `pr_merged` 之前问它,`Refused` 写 `pr_rejected`,理由是 `because; alternative` 两句(`crates/sprawling/Spec.lean` §8-133)。评审说「另一位居民看过」,准入说「这次派活要的证据在」,两个问题两道门。
- **`ContractKept` 今天以城看不见的方式成立**:城读不出一个契约动没动,`Produced.contract_moved` 由装配层恒填 `false`,所以这一要求只在 run 自己报出契约动了的那一天才会拒。这一点照旧写在 §3 而不是假装已经量过。
- 验收:`mode` 测试 `a_work_run_without_the_evidence_it_chose_does_not_land`(`work`+`tested`、没跑测试 → `Refused`),以及每种要求、每种落地各自的拒与放。
-/

namespace Runtime.Mode

/-- 一次派活的产出走不走常规的路(`kernel::LandingPolicy`)。 -/
inductive LandingPolicy where
  | Ordinary
  | Experiment
  deriving DecidableEq, Repr

/-- 一次派活的产出要带什么证据才合并(`kernel::AdmissionRequirement`)。 -/
inductive AdmissionRequirement where
  | Standing
  | Tested
  | ContractKept
  | DoubleValidated
  deriving DecidableEq, Repr

/-- `mode::Produced`:合并那一刻城读得到的证据。`Option Bool` 的 `none` 是「没量过」,不是「没过」。 -/
structure Produced where
  tests_passed : Option Bool
  contract_moved : Bool
  held_in : Option Bool
  held_out : Option Bool
  deriving DecidableEq, Repr

/-- 每一种拒绝各有一句拒词(Rust 里是 `because` 与 `alternative` 两句);这里按拒因区分。 -/
inductive Refusal where
  | ExperimentNeverLands
  | TestsFailed
  | NoTests
  | ContractMoved
  | WorseHeldIn
  | FailedHeldOut
  | HalfMissing
  deriving DecidableEq, Repr

/-- `mode::Admission`。 -/
inductive Admission where
  | Lands
  | Refused (because : Refusal)
  deriving DecidableEq, Repr

/-- `mode::admits_evidence`:常规落地时按证据要求判。 -/
def admits_evidence (required : AdmissionRequirement) (produced : Produced) : Admission :=
  match required with
  | .Standing => .Lands
  | .Tested => match produced.tests_passed with
    | some true => .Lands
    | some false => .Refused .TestsFailed
    | none => .Refused .NoTests
  | .ContractKept => if produced.contract_moved then .Refused .ContractMoved else .Lands
  | .DoubleValidated => match produced.held_in, produced.held_out with
    | some true, some true => .Lands
    | some false, _ => .Refused .WorseHeldIn
    | _, some false => .Refused .FailedHeldOut
    | _, _ => .Refused .HalfMissing

/-- `mode::admits`:先看落地策略,再看证据要求。mode 不是参数:交谈与干活产出的东西走同一道合并(kernel D12)。 -/
def admits (landing : LandingPolicy) (required : AdmissionRequirement) (produced : Produced) :
    Admission :=
  match landing with
  | .Experiment => .Refused .ExperimentNeverLands
  | .Ordinary => admits_evidence required produced

/-- 试验的产出从不合并,不论它带了什么证据。 -/
theorem an_experiment_never_lands (required : AdmissionRequirement) (produced : Produced) :
    admits .Experiment required produced = .Refused .ExperimentNeverLands := rfl

/-- 常规落地时每一种证据要求要的东西:恰是这些在场时合并。没量过的与量了没过的同样不合并。 -/
def evidenced (required : AdmissionRequirement) (produced : Produced) : Bool :=
  match required with
  | .Standing => true
  | .Tested => produced.tests_passed == some true
  | .ContractKept => !produced.contract_moved
  | .DoubleValidated => produced.held_in == some true && produced.held_out == some true

/-- **合并当且仅当常规落地且证据在场。** -/
theorem lands_exactly_with_its_evidence (landing : LandingPolicy) (required : AdmissionRequirement)
    (tests : Option Bool) (contract : Bool) (heldIn heldOut : Option Bool) :
    admits landing required ⟨tests, contract, heldIn, heldOut⟩ = .Lands ↔
      (landing = .Ordinary ∧ evidenced required ⟨tests, contract, heldIn, heldOut⟩ = true) := by
  cases landing <;> cases required <;> rcases tests with _ | ⟨_ | _⟩ <;>
    rcases contract with _ | _ <;> rcases heldIn with _ | ⟨_ | _⟩ <;>
    rcases heldOut with _ | ⟨_ | _⟩ <;> decide

/-- 一次没跑测试的 `work`+`tested` 不合并,拒因是「没有自己的测试」,而不是「测试没过」。 -/
example : admits .Ordinary .Tested ⟨none, false, none, none⟩ = .Refused .NoTests := rfl

end Runtime.Mode