vicTop-cw/fist-mbt/src/engine does not have a README file

    AuthMode

    pub enum AuthMode {
    Off
    Soft
    Strict
    } derive(Eq,
    Debug
    )

    认证模式(三档)。

    derive(Eq, Debug):让测试能 assert_eq(auth_mode(), Off) 并打印差值。

    AuthMode::equal

    fn AuthMode::equal(AuthMode, AuthMode) -> Bool

    显式 extend(BUG-478 清理 warn):编译器 implicit_impl_as_method 要求把 derive(Eq, Debug) 生成的 impl 方法显式声明为普通方法,否则将被移除。 不写这两行时 AuthMode 的 .equal() / .to_string()(Debug 路径)会在未来版本失效。

    AuthMode::not_equal

    fn AuthMode::not_equal(x : AuthMode, y : AuthMode) -> Bool

    显式 extend(BUG-478 清理 warn):编译器 implicit_impl_as_method 要求把 derive(Eq, Debug) 生成的 impl 方法显式声明为普通方法,否则将被移除。 不写这两行时 AuthMode 的 .equal() / .to_string()(Debug 路径)会在未来版本失效。

    AuthMode::to_repr

    AuthMode::to_string

    fn AuthMode::to_string(self : AuthMode) -> String

    转回 FIST_AUTH_MODE 的取值字符串(小写,与 env 里人配的字面量一致)。

    ⚠️ 为什么不用 Debug:Debug 给的是 Off/Soft/Strict(大写), 与人配的 off/soft/strict 不一致 ⇒ 人读启动日志会以为配错了。

    DepsReport

    pub struct DepsReport {
    state : DepsState
    missing_deps : Array[String]
    pending_deps : Array[String]
    } derive(
    Debug
    )

    依赖体检报告:三态结论 + 点名(哪个缺了、哪个还没完成)。

    两个点名列表同时填充,不做"只报一种"的取舍(裁定见 deps_report), 因此调用方拿到 DepsMissing 时仍能看到"除了配置错之外还差谁"。

    DepsReport::is_satisfied

    fn DepsReport::is_satisfied(self : DepsReport) -> Bool

    依赖体检结论是否可执行(等价于 state == DepsSatisfied)。

    DepsReport::to_repr

    显式 extend(BUG-478 清理 warn):见 auth_mode.mbt 同名说明。

    DepsState

    pub enum DepsState {
    DepsSatisfied
    DepsPending
    DepsMissing
    DepsNoTask
    } derive(Eq,
    Debug
    )

    依赖体检三态(BUG-460):把 deps_satisfied 里被压成同一个 false 的三种处境拆开。

    为什么必须拆:deps_satisfied 对「依赖存在但未完成」与「依赖 id 根本不存在」都回 false ⇒ 上游无从区分「等一等会好」与「配置错了永远等不到」,后者会把任务永久卡死而看板看不出异常。

    加法式:deps_satisfied 的签名与语义一字不动(false 仍然覆盖 Pending / Missing / NoTask 三档), 本类型只是给需要分辨的调用方多一个出口。第四态 DepsNoTask 的裁定见 deps_report。

    DepsState::equal

    fn DepsState::equal(DepsState, DepsState) -> Bool

    显式 extend(BUG-478 清理 warn):见 auth_mode.mbt 同名说明。

    DepsState::not_equal

    fn DepsState::not_equal(x : DepsState, y : DepsState) -> Bool

    显式 extend(BUG-478 清理 warn):见 auth_mode.mbt 同名说明。

    DepsState::to_string

    fn DepsState::to_string(self : DepsState) -> String

    稳定机器标签(供 dag_check 回执等展示面直接复用,避免各处自己拼 match 拼出两套口径)。

    FistEngine

    核心引擎:持有 store,暴露指挥-执行-验收完整闭环。 store 为后端路由(内存 / SQLite),业务逻辑不感知具体实现。

    FistEngine::agents_acl_check

    fn FistEngine::agents_acl_check(self : FistEngine, agent_id~ : String, ns~ : String, write~ : Bool, current_pid~ : Int) -> Result[Unit, String]

    ACL 检查(调用前先 ensure;仅 SQLite 后端)。

    FistEngine::agents_acl_count_writable

    fn FistEngine::agents_acl_count_writable(self : FistEngine, ns~ : String) -> Int

    统计某命名空间下 can_write=1 的 agent 数(BUG-411;仅 SQLite 后端,内存 stub 回 0)。

    用途:publish 成功时判断「这个 ns 到底有没有人能认领」,让调用方在 「发布成功但 claim 会被拒」之间拿到预警而不是撞墙。

    FistEngine::agents_acl_ensure

    fn FistEngine::agents_acl_ensure(self : FistEngine, agent_id~ : String, ns~ : String, now~ : String) -> Result[Unit, String]

    确保默认 ACL(fail-closed 底线;仅 SQLite 后端)。

    FistEngine::agents_acl_grant

    fn FistEngine::agents_acl_grant(self : FistEngine, agent_id~ : String, ns~ : String, can_read~ : Bool, can_write~ : Bool, pid_bound~ : Bool, now~ : String, by~ : String) -> Result[Unit, String]

    授权/收权(仅 SQLite 后端)。

    FistEngine::agents_get

    fn FistEngine::agents_get(self : FistEngine, agent_id : String) ->
    AgentRecord
    ?

    读单个 agent 的持久化身份(无记录 None;仅 SQLite 后端)。

    FistEngine::agents_register

    fn FistEngine::agents_register(self : FistEngine, agent_id~ : String, agent_type~ : String, role~ : String, soldier~ : String, pubkey_fp~ : String, pid~ : Int, now~ : String) -> Result[Unit, String]

    注册 agent(进程级指纹身份;仅 SQLite 后端可用,内存后端 Err)。 agent_id 由调用方(server 层)以 fnv1a64 指纹计算,store 保持哈希无关。

    FistEngine::agents_set_pid

    fn FistEngine::agents_set_pid(self : FistEngine, agent_id~ : String, pid~ : Int) -> Result[Unit, String]

    换绑进程实例(server 启动时调用;仅 SQLite 后端)。

    FistEngine::agents_stale_check

    fn FistEngine::agents_stale_check(self : FistEngine, cutoff : String) -> Array[
    AgentRecord
    ]

    陈旧 agent 扫描(仅 SQLite 后端;内存后端返回空)。

    FistEngine::all_subtasks_completed

    fn FistEngine::all_subtasks_completed(self : FistEngine, parent :
    Task
    ) -> Bool

    是否为「非叶节点且所有直接子任务都已完成」

    FistEngine::archive

    fn FistEngine::archive(self : FistEngine, task_id~ : String, by~ : String, now~ : String) -> Result[
    Task
    , String]

    人类确认归档:Completed -> Archived(仅限人类指挥官)

    FistEngine::archive_force

    fn FistEngine::archive_force(self : FistEngine, task_id~ : String, by~ : String, force~ : Bool, now~ : String) -> Result[
    Task
    , String]

    强制归档:force=false 与 FistEngine::archive 完全同义(含同一拒绝文案); force=true 放行 已暂停/已打回 僵尸任务直接入档(在流态仍拒,见 core Task::archive_force)。

    FistEngine::assistant_active_primary

    找现任 active 主助理(is_primary 且 status=active,取 registered_at 最新)。

    FistEngine::assistant_get

    FistEngine::assistant_last_primary

    最近登记的曾任主助理(不要求 status == "active")—— BUG-431。

    为什么必须与 assistant_active_primary 分开: assistant_sweep 会把心跳陈旧的主助理标 dead,而 active_primary 只认 active ⇒ sweep 跑过之后 claim 看到 prev = None ⇒ assistant_claim_action 判 elected 且 prev_agent = "" ⇒ server.mbt 的留痕分支(只在 taken_over 时记)不记违例。 ⇒ 同一个业务事件(B 心跳陈旧、A 接管)会因「sweep 是否恰好先跑过」 产出两种不同的账本结果(一种有 force_takeover 案底、一种没有)。

    语义边界(刻意的):
    • dead 参与 —— 它正是「曾持有、已陈旧」的那一档,接管要认它
    • resigned 排除 —— 主动退位后应重新选举(旧语义),不算被接管
    • 排序键与 active_primary 一致(registered_at 最新),只放宽 status 过滤

    FistEngine::assistant_list

    FistEngine::assistant_set_status

    fn FistEngine::assistant_set_status(self : FistEngine, agent : String, status : String) -> Bool

    FistEngine::assistant_upsert

    FistEngine::audit_execute_gate

    fn FistEngine::audit_execute_gate(_self : FistEngine, _task_id : String, deliverable : String) -> Result[Unit, String]

    预执行审计门禁(P2 BACKLOG「Pre-execution audit gate」): 在 can_execute 通过之后、execute() 迁移之前插入,拦截「空交付物」与「占位符交付物」—— 「已完成但无交付物」的病灶形状:执行者写了空串/纯空白/「done」/「完成」/「ok」这类占位符 deliverable,账本记「完成」但没有任何产物可核。判据两条: ① deliverable trim 后非空;② 非纯占位符(如 "done"/"完成"/"ok"/"yes"/"通过",大小写不敏感;不做全角归一化)。 只拦确定性问题,不做过度拦截;零回归(正常交付物一定含具体改动/路径/验证信息)。

    FistEngine::budget_split_by_dag

    fn FistEngine::budget_split_by_dag(self : FistEngine, budget : Int, root? : String, ns? : String) -> Json

    预算按依赖图阶段切分(R81,ZEBRA 背包水填充蒸馏简化版):给定总预算, 按任务 DAG 阶段(slack 分析的 earliest 层级 0..makespan)切分——每阶段基础份额 = 该阶段任务数 × 难度权重(难3/中2/易1,复用难度档单一来源;无难度标记按 1), 总权重归一化后乘总预算(向下取整,余数补给最大权重阶段)。 返回 { verdict, stages:[{level, tasks, difficulty_sum, share}], total_budget, makespan, scope, note }。 ——"预算在依赖图上按阶段切分:瓶颈阶段(关键路径层)占额可见,超支先预警",机制设计叙事。

    BUG-20 改的三件事: ① 过去只吃 budget,task_id/namespace 传了也不生效,算的永远是整个 store(实测 某轮 T0r292 子树 7 个任务,它报 tasks=1152=全库数)——现在 root/ns 真的参与取数, 与同族 dag_slack/dag_mc 的作用域口径一致; ② budget<=0(含负数)过去直接产出负份额(-50 → -42/-5/-2),既不拒也不警——现在拒绝; ③ 切了哪些任务写进返回体的 scope 自证。root/ns 都不给=全库(旧行为,零回归), 但这一点从此写在返回体里,而不是靠调用方记得自己没传。

    FistEngine::challenge

    fn FistEngine::challenge(self : FistEngine, task_id~ : String, by~ : String, factor? : Int, strat? : String, now~ : String, critic? :
    Archive
    ?) -> Result[Json, String]

    对一条已完成/已归档任务发起 Challenger 进阶变体:发布一个新根任务 [challenge]。 可选 critic(DGM 档案库,缺省 None = 关闭防漂移门禁,旧行为零回归):传入时先对生成 的挑战题做 Critic 防漂移评审(与 evolve_critic 同一单一真源),全部策略皆漂移则拒发。 返回 { new_id, source, by, factor, strat, importance, ns, description, critic, critic_status }。

    FistEngine::challenge_cleanup

    fn FistEngine::challenge_cleanup(self : FistEngine, cutoff : String) -> Int

    BUG-466:清理过期/已消费的 challenge(防表无限增长)。返回删除行数。

    cutoff 之后的票保留;已消费的票一律清。建议由定时任务调用 (如 watchdog_tick 或 task_cleanup 旁边),不要在每次消费时清 —— 那会把审计证据一起删掉。

    FistEngine::challenge_cleanup_on_issue

    fn FistEngine::challenge_cleanup_on_issue(self : FistEngine, cutoff : String) -> Int

    签发时顺带清理(BUG-467 的接线点):返回本次清掉的行数。

    在 s3_issue_challenge 里调用 —— 每次签发顺手清一次过期票, 于是「表的增长」与「表的回收」绑在同一个动作上,不需要任何外部调度器。

    cutoff 由调用方算(server.mbt 用 iso_minus_secs(now, 86400))—— engine 包不依赖 @ops,也不该为了算一个时间戳引入依赖。 engine 只负责策略(保守档:保留 cutoff 内的审计证据)。

    FistEngine::challenge_pending_count

    fn FistEngine::challenge_pending_count(self : FistEngine, agent : String, ns? : String) -> Int

    BUG-466:某 agent 在 ns 下未消费的票数(只读诊断,供白盒锁用)。

    FistEngine::challenge_persisted

    fn FistEngine::challenge_persisted(self : FistEngine) -> Bool

    BUG-466:本次部署里 challenge 是否真的持久化(true = SQLite 落库、跨进程可见; false = 内存后端,只在本次进程生命周期内有效)。

    为什么要单独暴露:同一个 +challenge_verified 在两种后端下可信度不同 —— 内存后端换进程就失效,多实例部署下还会误判重放。调用方拿到 false 就知道「这次验签只在本次进程内成立」,而不是以为它跨进程也成立。

    FistEngine::challenge_table_stats

    fn FistEngine::challenge_table_stats(self : FistEngine) -> (Int, Int, Int)

    BUG-467:表大小可观测 —— 供回执/诊断回答「表在长吗」。

    返回 (total, consumed, pending):总数 / 已消费数 / 未消费数。 三个数一起看才知道状态: · pending 高 ⇒ 签发了但没人用(正常,票有时效) · consumed 高而 total 不降 ⇒ 清理没跑(本单要暴露的形态)

    FistEngine::children_index

    fn FistEngine::children_index(self : FistEngine) -> Map[String, Array[String]]

    R12-R8 性能:children_of 每查一个父任务都 list_tasks()(全库遍历), 在 subtree_ids/plan_exec_order 这类"按子树递归遍历"里是 O(N×M) (N=子树大小,M=全库任务数)——与 R7 的 deps_satisfied N×(1+D) 同款病灶。 这里一次 list_tasks() 建出 parent→children 索引,之后每次查都是 O(1) Map。 遍历型调用方(subtree_ids 等)先用它取索引,再在索引上递归。 pub 以便 engine_dag_ext / server 复用。

    FistEngine::children_of

    fn FistEngine::children_of(self : FistEngine, task_id : String) -> Array[
    Task
    ]

    列出某任务的所有直接子任务

    FistEngine::circuit_fail

    fn FistEngine::circuit_fail(self : FistEngine, ns? : String, circuit~ : String, now_secs~ : Int, window_secs? : Int, threshold? : Int, recovery_secs? : Int) -> Json

    上报一次失败并推进熔断状态机。窗口内失败达阈值 → Open(记 opened_at);Half-Open 探测失败 → 回 Open。

    FistEngine::circuit_status

    fn FistEngine::circuit_status(self : FistEngine, ns? : String, circuit~ : String, now_secs~ : Int, probe_limit? : Int) -> Json

    查询熔断状态(含 Half-Open 到期判定:Open 且 elapsed≥recovery → 转 Half-Open 放行探测)。

    BUG-34:半开态必须节流——旧实现 allow = state != "open" 让恢复窗内所有调用全部放行, 半开退化成"换个名字的 closed",正是要防的下游踩踏(工具描述一直承诺"放行探测请求,其余仍快速失败", 承诺与实现相反)。 配额计数复用已持久化的 failures 列(半开态下它的含义是"本恢复窗已放行探测数"): 不扩 cb 表列 ⇒ 不动 native/js 双后端与既有迁移,也就不必把"跨进程计数"做成分布式预算 (上一轮 NOT-FIXED 的理由 ① 因此不成立)。 探测丢失兜底:拿到配额却既不 succeed 也不 fail 时,超过一个 recovery 窗就把配额重置—— 否则一次被吞掉的探测会把半开态永久锁在快速失败(那是把旧 bug 换成新 bug)。

    FistEngine::circuit_succeed

    fn FistEngine::circuit_succeed(self : FistEngine, ns? : String, circuit~ : String, now_secs~ : Int) -> Json

    上报一次成功:Closed 复位计数;Half-Open 探测成功 → Closed;Open 保持。

    FistEngine::claim

    fn FistEngine::claim(self : FistEngine, task_id~ : String, assignee~ : String, now~ : String, challenge_nonce? : String?, challenge_sig? : String?) -> Result[
    Task
    , String]

    认领任务(executor 认领,待领取 -> 已领取 -> 执行中) 领取前检查依赖是否满足

    FistEngine::cold_publish

    fn FistEngine::cold_publish(self : FistEngine, ns~ : String, description~ : String, created_by~ : String, project_dir~ : String, now~ : String, level? : Int) -> Result[String, String]

    冷启动发布(无上一轮根任务时创建首个任务)。 仅用于 cron-auto 等自动化命名空间,语义等同「人类指挥官发布首轮」 但审计日志标记为 watchdog 自动发布。

    参数 created_by(BUG-354 出路 c):本函数体内不消费它——上面这句 「审计日志标记为 watchdog 自动发布」是设计意图,当前实现并未把 created_by 写进任何审计字段(该语义只靠调用方自己传值,无落点)。保留形参 理由同 plan:删它会收缩 pub 契约;要真正消费需在 store 层加标记字段。

    安全边界:
    • 仅允许作用于非 default 命名空间;
    • 该命名空间必须为空(已有任务时拒绝,避免重复发布)。

    FistEngine::cost_stats

    fn FistEngine::cost_stats(self : FistEngine) -> Json

    成本聚合统计:委托给 store 后端(SQLite 从 executions 表聚合,内存返回零值)。

    FistEngine::critical_path

    fn FistEngine::critical_path(self : FistEngine) -> Array[String]

    关键路径:全图中按 depends_on 建图后的最长依赖链(任务 id 序列)。 多条等长链取先遇到的一条;空仓库返回空数组。 BUG-215:环成员被 longest_chain_ending 标记为 None,此处不再计入。

    FistEngine::dag_ascii

    fn FistEngine::dag_ascii(self : FistEngine, ns~ : String) -> String

    ASCII 树/缩进图:展示某命名空间下的任务依赖结构。 层级由 parent_id(子任务归组)与 depends_on(依赖深度)共同决定, 同层任务按 id 字典序稳定输出;跨命名空间的父/依赖不计入缩进。 每行格式:<缩进> [<状态>](有依赖时追加 " (依赖: a,b)"),行尾换行。 BUG-215:dag_level 对环成员返回 -1 哨兵,此处钳到 0(顶层), 让环成员不被渲染成「看似有合法深度」的中间层。

    FistEngine::dag_cost_route

    fn FistEngine::dag_cost_route(self : FistEngine) -> Json

    依赖图成本路由(R77,STAR 蒸馏简化版):在 dag_schedule 排程视图之上补「成本」维度—— 对 flexible(slack>0)批中未认领任务,按拓扑序贪心给出建议执行者:在「能力覆盖的 已注册执行者」里选 执行成本 + 切换成本 之和最小者。
    • 执行成本:难度档映射(易/中/难 → 1/2/3,复用 extract_difficulty 单一来源);
    • 能力约束:任务所需能力 = 描述命中的最长已注册能力标签(复用 auto_want), 未命中标签的执行者被排除(want 空则全命中);
    • 切换成本:任务依赖链上已路由执行者若与候选不同则 +1(STAR 的上下文切换税);
    • 成本相等时取活跃负载最低者(与 R75 一致)。 纯计算、无副作用(只给建议不 claim)。——排程优化闭环最终形态: critical_path→slack→schedule→suggest→cost_route。

    FistEngine::dag_depend

    fn FistEngine::dag_depend(self : FistEngine, task_id~ : String, dep_id~ : String, now~ : String) -> Result[
    Task
    , String]

    显式给任务追加一条前置依赖(构建 DAG 依赖边,不只靠 plan_deep 隐式父子)。 校验:task 与 dep 均存在、非同一任务、且不会形成直接自环;去重追加并落库。 返回追加后的任务 JSON。失败返回 Err(含人语原因)。

    FistEngine::dag_mc

    fn FistEngine::dag_mc(self : FistEngine, ns? : String, root_task_id? : String, deadline? : Int, samples? : Int, seed? : Int) -> Json

    Monte Carlo 概率式完工预测(R94,Van Slyke 1963 首倡 MCS 求网络完工分布)。 对作用域内每个任务按难度档采样三角分布时长,整网模拟 samples 次, 输出完工时间分布(min/mean/p50/p90/max)+ 按期概率 + 关键度排行。 deadline>0 时给出 P(完工 ≤ deadline);seed 固定则结果可复现。 空作用域返回 { insufficient:true, note },无 p_on_time/makespan/criticality (无法预测,不伪造——BUG-182 文档契约)。

    FistEngine::dag_schedule

    fn FistEngine::dag_schedule(self : FistEngine) -> Json

    排程视图(R75 增强):在 dag_slack_analysis 基础上把任务落成可执行排程—— 按 slack 分两层批:critical 批(slack=0,关键路径瓶颈,须串行盯紧)与 flexible 批(slack>0,可按最早开始排序并行的弹性任务),每批附 assignee 当前占用, 并对 flexible 批中未认领任务给出「负载感知建议派单」(suggest:活跃负载最低的已注册执行者)。 复用 dag_slack_analysis(不重复算图)。——"谁在瓶颈、谁可并行派单"一目了然,排程优化闭环。

    FistEngine::dag_slack_analysis

    fn FistEngine::dag_slack_analysis(self : FistEngine) -> Json

    瓶颈与松弛分析(PERT/CPM 硬核调度能力,R71):对每个任务计算 earliest(依赖全部就绪的最早开始步数)、latest(不拖慢总工期的最晚开始步数)、 slack(latest-earliest;0=关键路径上,>0=可灵活并行安排)。 返回 { critical:[关键路径任务id], slack_map:{id:{earliest,latest,slack}}, makespan, cycle:[成环任务] }。 ——"找出谁在关键路径上、谁有松弛可并行",直接支撑排程优化(不是视图叠加)。

    FistEngine::delete_task

    fn FistEngine::delete_task(self : FistEngine, task_id : String) -> Result[Unit, String]

    删除任务(仅限已归档,ARCHIVING 清理)

    FistEngine::deps_report

    fn FistEngine::deps_report(self : FistEngine, task_id : String) -> DepsReport

    依赖体检(BUG-460 加法式新 API):返回三态结论 + missing_deps / pending_deps 点名。

    与 deps_satisfied 的契约(dag460 白盒锁逐格钉住):永不反向 —— is_satisfied() == true ⟺ deps_satisfied(task_id) == true。

    两条裁定,写在这里免得下一个人重猜: ① 任务自身不存在 ⇒ DepsNoTask,不是 DepsMissing。DepsMissing 的语义是"依赖里有幽灵", 而此刻连"依赖表"本身都没有;把两者混成一档,等于让调用方把"task_id 打错了"读成"某个上游被删了"。 也不返回 Err:同包既有风格是 Bool/纯值(deps_satisfied 无错误通道),Err 会逼调用方 为一个"查不到"改签名或 unwrap;显式第四态在模式匹配上是穷尽的,漏处理会编译期报错。 ② 混合(同时有 pending 与 missing)⇒ 结论取 DepsMissing(missing 优先),但两个列表都照实填。 优先级反映"先修哪个":pending 会随时间自愈,missing 不会,所以整体不可执行的原因归档为 missing; 而两个列表都填,保证"除了配置错之外还差 A"这种信息不被优先级吃掉。

    FistEngine::deps_satisfied

    fn FistEngine::deps_satisfied(self : FistEngine, task_id : String) -> Bool

    检查某任务的直接依赖是否全部完成

    FistEngine::dispatch_next

    fn FistEngine::dispatch_next(self : FistEngine, ns : String, agent : String, want : String, now : String) -> Json

    引擎层自治派单(纯增量、零回归):取 triage 顶部可领取任务 → 用 store 持久化执行者能力 注册经 executor 包 route_pick 按「能力覆盖 → 负载」路由最佳执行者 → 将任务直接认领给该执行者; 无注册/路由失败时回退到 agent。want 为空时自动从顶部任务描述抽取所需能力(R58)。 返回 { dispatched, task_id, to_executor, remaining, note?/error?, want? }。

    FistEngine::docs_verify_gate

    fn FistEngine::docs_verify_gate(self : FistEngine, task_id : String) -> Result[Unit, String]

    「文档即实现」验收门禁:task 需为 [docs:required] 或显式传 docs_check=true 时生效。 校验结构与回传格式,任一项缺失/不达标 → Err(含明细)。

    FistEngine::eval_feedback

    fn FistEngine::eval_feedback(_self : FistEngine, feedback : String, task_id? : String) -> Json

    反馈收敛:归一为四段式 schema + verdict(E/O 蒸馏:生成→评估→定向修订→收敛)。 返回 { ok, task_id, schema:{defects,evidence,fix,acceptance}, counts, verdict, flags, note }。

    FistEngine::execute

    fn FistEngine::execute(self : FistEngine, task_id~ : String, deliverable~ : String, now~ : String) -> Result[
    Task
    , String]

    记录执行交付物

    FistEngine::execute_with_meta

    fn FistEngine::execute_with_meta(self : FistEngine, task_id~ : String, deliverable~ : String, now~ : String, executor? : String, model? : String, tokens_in? : Int, tokens_out? : Int, cost? : Double, duration_ms? : Int, rate_limited? : Bool, failure_reason? : String) -> Result[
    Task
    , String]

    记录执行交付物 + 执行元数据(executor/model/tokens/cost/duration)。 元数据写入 executions 表(供成本统计使用);task 本身状态照常流转。

    FistEngine::executor_clear

    fn FistEngine::executor_clear(self : FistEngine) -> Bool

    清空全部执行者能力注册(Marketplace 重置/整洁)。

    FistEngine::executor_list

    fn FistEngine::executor_list(self : FistEngine) -> Array[(String, String, String)]

    读取全部已注册执行者 (name, abilities_json, created_at)。

    FistEngine::executor_save

    fn FistEngine::executor_save(self : FistEngine, name~ : String, abilities_json~ : String, created_at~ : String) -> Result[String, String]

    持久化写入/覆盖一个执行者的能力注册(abilities_json 为 JSON 数组串)。

    FistEngine::executor_trust

    fn FistEngine::executor_trust(self : FistEngine) -> Map[String, Double]

    执行者历史信任分(R80,SwarmHarness 蒸馏):各 assignee 名下 已完成任务数 / 名下任务总数 (分母含打回/执行中/待验收等,防"只认领不交付");名下无任务取 0.5 中性。 供 executor_route 历史信任轴(排序键 能力覆盖→信任→负载)。

    FistEngine::gate_check_record

    fn FistEngine::gate_check_record(self : FistEngine, task_id : String, check_json : String, status : String, now : String) -> Result[Json, String]

    记录一次外部命令检查结果(门禁状态从 check_json 的 ok/exit_code==0 推导, status 参数已不参与判定,仅保留以维持公开签名兼容)。 记录 id = check:<task_id>:r(round = 该任务已有 check 记录数 + 1)。

    FistEngine::gate_verify_gate

    fn FistEngine::gate_verify_gate(self : FistEngine, task_id : String) -> Result[Unit, String]

    验收前闸门:[gate:required] 任务须存在最新且通过且新鲜的 check 记录。 未标记任务直接放行(默认关闭即现状)。

    FistEngine::get_task

    fn FistEngine::get_task(self : FistEngine, task_id : String) ->
    Task
    ?

    查询单个任务

    FistEngine::goal_drift_check

    fn FistEngine::goal_drift_check(self : FistEngine, root_task_id : String, subtask_id? : String, ns? : String) -> Json

    全局目标校验(R100,goal drift arXiv 2505.02709 / Repetitiveness Rate 2603.12710 / IntentCUA 2602.17049 / HiMAP ICML2026 蒸馏落地)——advisory 判据,不是硬门(BUG-25)。

    词法口径(单真源 @evolve.tokens):similarity = max(jaccard, coverage),drift = 1 - similarity。 · coverage 非对称(|根∩子|/|根|):只问"根目标的词还在不在",子任务多写模板不被长度惩罚; · jaccard 保留给兄弟冗余(对称量,语义正确)。

    为什么必须降级成 advisory(标定实测,scripts/calibrate_goal_drift.py): 真实父子对全集 1222 条 drift_suspect=1.9%,随机错配对照 400 条=81.3%(有分离); 但改写型父子对(去掉前缀拼接桩与模板桩后剩 39 条,如 T0r61→T0r61.1「依赖图恒空」 被写成「增量编译链与依赖图修复:cypyc/incremental/...」)假阳性 56~59%, 3 个可测家族上绝对阈值 0.7 命中 15/15——合法拆解与真漂移在词面上不可分。 所以:判据照旧发红(离群时最有信息量),但 verdict 一律带 advisory=true, 下游按它打回就是把 15/15 的正常子任务反复打回(BUG-25 影响 2 的病灶)。 subtask_id 缺省校验根下全部后代。纯计算、只读不写库(决策建议,执行权在 agent/指挥官)。

    FistEngine::heartbeat_backend

    fn FistEngine::heartbeat_backend(self : FistEngine) -> String

    心跳后端自述 "sqlite" / "memory"(BUG-119:落库与否必须让调用面看得见)。

    FistEngine::list_all

    列出全部任务

    FistEngine::list_by_status

    按状态过滤列出

    FistEngine::list_in_ns

    fn FistEngine::list_in_ns(self : FistEngine, ns : String) -> Array[
    Task
    ]

    按命名空间列出任务(ns 为空串时回退默认命名空间)。 供 watchdog 等运维编排按 ns 扫描使用。

    FistEngine::lock_violations_list

    FistEngine::lock_violations_record

    fn FistEngine::lock_violations_record(self : FistEngine, ts~ : String, agent~ : String, scope~ : String, reason~ : String, lock_id~ : String) -> Unit

    记一条锁违例(助理接管等治理动作落案底)。

    FistEngine::locks_get

    fn FistEngine::locks_get(self : FistEngine, scope : String) ->
    FileLock
    ?

    FistEngine::locks_list

    FistEngine::locks_release

    fn FistEngine::locks_release(self : FistEngine, scope : String, agent : String) -> Bool

    FistEngine::locks_sweep

    fn FistEngine::locks_sweep(self : FistEngine, now : String) -> Int

    FistEngine::locks_try_acquire

    fn FistEngine::locks_try_acquire(self : FistEngine, lock :
    FileLock
    , challenge_nonce? : String?, challenge_sig? : String?) ->
    LockAcquireOutcome

    按裁决抢锁(BUG-145):互斥由后端定,工具面只渲染结论——不许在这里先读后写。

    BUG-424:权限门已接(原注释称「暂时没接」,写于 2026-10-22、挂了 4 天)。 拆解那个「技术障碍」,两条都要纠正: · 「LockAcquireOutcome 是 read-only 类型」—— 说法不成立:字段只是包内私有 (跨包写 literal 被编译器拒,BUG-413 已实测 4036/4033),与 read-only 无关; 而 BUG-413 早就用 RsvOutcome::relabel 解决过同一个障碍,只是没人回头看这条注释。 · 「必须在 store 内部创建」—— 对,但 store 侧已补 LockAcquireOutcome::denied_by_permission()(拒绝态,不碰库 ⇒ agent/ttl 为空) 与 FileLock::agent_of() / scope_of() 两个读访问器(本笔同批)⇒ 无需后端扩展。

    三态语义与 check_scope_permission 一致(BUG-413): · RoleDenied ⇒ 硬拒:granted=false + action="permission_denied",不写库 · Unregistered ⇒ advisory:正常抢锁,拿到时把 action 标 unregistered(不参与授权裁决) · Allowed ⇒ 原样

    角色来源走 resolve_agent_role(权威面 agents.role 优先、名字推断兜底)—— 不是裸 infer_role_from_agent,否则 BUG-418 刚收窄的那层会被这里绕开 (与 rsv_set / rsv_try_set 同口径)。

    本门覆盖不到的面(诚实标注): · locks_upsert(底层原语)仍是裸通道 —— 它被 locks_sweep 等内部路径依赖, 加门会破坏那些路径;工具面 lock_acquire 走的是本函数,故攻击面已关闭 · lock.agent 是调用方自报字符串 ⇒ 「身份未经核验」这一根因仍在(BUG-422 的诚实边界): BUG-421 已删掉整名 leader/lead 提权,BUG-422 已把 server 自身份记录剔出裁决, 但「报一个非 server 类型的高角色登记名」仍可继承 —— 彻底收口需认证身份(A/C 案)。

    FistEngine::locks_upsert

    FistEngine::omega_auto_lesson

    fn FistEngine::omega_auto_lesson(self : FistEngine, seq : Int, cat : String, reason : String, fix : String, now : String) -> Bool

    /// Omega 打回自动落一条教训。cat 为类目 tag,reason 为失败原因,fix 为纠偏提示。 seq 用于同一类目、同一时刻多次打回的 id 趋唯一;id 纳入 cat, 保证 spec 打回 / 复验打回 / 超限升级在同一时间戳下互不覆盖(upsert 语义防丢史)。

    FistEngine::omega_count

    fn FistEngine::omega_count(self : FistEngine, task_id : String, spec_type : String, status : String) -> Int

    统计某类型记录中处于指定状态的条数。

    FistEngine::omega_count_type

    fn FistEngine::omega_count_type(self : FistEngine, task_id : String, spec_type : String) -> Int

    统计某类型的全部记录条数。

    FistEngine::omega_execute_gate

    fn FistEngine::omega_execute_gate(self : FistEngine, task_id : String) -> Result[Unit, String]

    执行前门禁:强验证任务必须先有「已通过」语料;默认关闭的任务直接放行。

    FistEngine::omega_latest

    fn FistEngine::omega_latest(self : FistEngine, task_id : String, spec_type : String) ->
    SpecRecord
    ?

    取最新一条指定类型记录。

    FistEngine::omega_records

    fn FistEngine::omega_records(self : FistEngine, task_id : String) -> Array[
    SpecRecord
    ]

    列出某任务的全部语料 / 复验 / 升级记录。

    FistEngine::omega_result_verify

    fn FistEngine::omega_result_verify(self : FistEngine, task_id~ : String, reviewer~ : String, verdict~ : String, reason? : String, now~ : String, max_rounds? : Int) -> Result[Json, String]

    成果复验(角色:验证者 verifier,或人类指挥官)。 verdict = pass 达标;其余视为打回重做(超限升级人工)。

    FistEngine::omega_spec_create

    fn FistEngine::omega_spec_create(self : FistEngine, task_id~ : String, author~ : String, content~ : String, now~ : String, max_rounds? : Int) -> Result[Json, String]

    语料创建(角色:语料创建者 spec_author,或人类指挥官)。 落库 specs 表:id = spec:<task_id>:r,status = pending。

    FistEngine::omega_spec_review

    fn FistEngine::omega_spec_review(self : FistEngine, task_id~ : String, reviewer~ : String, verdict~ : String, reason? : String, now~ : String, max_rounds? : Int) -> Result[Json, String]

    语料审核(角色:验证者 verifier,或人类指挥官)。 verdict = approve 通过;其余视为打回(打回累计超限则升级人工)。

    FistEngine::omega_status

    fn FistEngine::omega_status(self : FistEngine, task_id : String) -> Result[Json, String]

    查询某任务的强验证状态(开关、语料轮次、打回次数、复验状态、是否已升级)。

    FistEngine::omega_verify_gate

    fn FistEngine::omega_verify_gate(self : FistEngine, task_id : String) -> Result[Unit, String]

    验收前门禁:强验证任务必须先有「已通过」的成果复验记录。

    FistEngine::open_sqlite

    fn FistEngine::open_sqlite(db_path : String) -> FistEngine?

    以指定 SQLite 数据库打开持久化引擎(内建数据库入口;失败返回 None 供调用方回退)。

    FistEngine::parallelism

    fn FistEngine::parallelism(self : FistEngine) -> Int

    当前可并行执行的任务数:状态为待领取且依赖已满足的任务数。

    FistEngine::pause_task

    fn FistEngine::pause_task(self : FistEngine, task_id~ : String, now~ : String) -> Result[
    Task
    , String]

    暂停任务:任意活跃状态 -> 已暂停

    FistEngine::pick_next

    fn FistEngine::pick_next(self : FistEngine, ns_filter : String, agent : String, want : String, now : String, skill? : String) -> Json

    下一步自动取单(自驱推进):按 triage 的能力/skill 推荐取顶部候选并认领给 agent(0 状态迁移:待领取→已领取)。 复用 triage(排行+suggestion)与 claim(认领),只动一条推荐任务,不动看门狗主流程(零回归)。 skill 可选(逗号分隔 skill 关键词):非空时按 skill 命中度路由取单;空串 = 不做 skill 路由(零回归)。 返回 { picked, remaining, claimed, ns, want, skill, task?, note|error? }。

    FistEngine::plan

    fn FistEngine::plan(self : FistEngine, task_id~ : String, split_n? : Int, by~ : String, now~ : String) -> Result[Array[String], String]

    对指定任务生成子任务树(一层)。非原子任务才可拆分。 返回生成的子任务 id 列表。

    参数 by(BUG-354 出路 c):拆解者身份。本函数体内不消费它——任务面 没有「谁拆解的」落点,审计面靠 instrumented_tool 的 call_log 记原始入参 (跨进程可查)。保留形参是为契约稳定(plan 是 pub 接口 + MCP 入参 schema 的一部分,删它会收缩对外契约)。要真正消费需动 store schema(加历史字段), 属行为变更,未在本轮做。

    FistEngine::plan_deep

    fn FistEngine::plan_deep(self : FistEngine, task_id~ : String, split_n? : Int, by~ : String, spec? : Json?, now~ : String, omega_strong_verify? : Bool, gradient? : Bool, calibrate? : Array[Double]?, gradient_dag? : Bool, reinject_context? : Bool, boundary_probe? : Bool) -> Result[Json, String]

    AO 式递归拆解入口:对指定任务拆出整棵子任务树并写库。

    入参: task_id 要拆解的根/拳节任务 id(非原子任务才可拆); split_n 每层默认拆分数(无 spec.laws 时生效,默认 3); by 执行拆解的拳长身份(仅记录); spec 可选 omega spec JSON(laws 作切割依据,fingerprint 作验收基准); now 时间戳。 reinject_context 是否把「父计划 + 剩余兄弟」回注进每条子任务描述(R63,ReCAP 借鉴; 默认 false 零回归)——让原子片知道自己"为什么做、旁边还有谁",防上下文漂移。 boundary_probe 是否为「spec 没写的域外行为」补一个 owner(R115+,atgc-merge 归因报告 机理 1「递归拆解损失全局视角」+ 机理 2「验收锚定 + 弱语料」): true 时本层末尾追加一条「边界审视叶」(全局输入域 owner), 并给其余每条叶挂「边界四问」提示。默认 false 零回归。

    返回:{ "root", "by", "created", "tree" },tree 为多层子树结构; 任一子节点非叶时将继续递归(depth 递减,is_leaf=depth<=1 停止)。

    FistEngine::plan_exec_order

    fn FistEngine::plan_exec_order(self : FistEngine, root_id : String) -> Json

    生成整棵拆解子树的「执行计划」视图:收集根任务全部后代(BFS),按 DAG depends_on 拓扑排序,输出一张扁平、按依赖可安全执行的清单(每条含 id/depth/难度档/依赖/状态)。供 agent 拆解后拿到即可照单执行——"一张计划、 一目了然",而非再全库去翻。纯读、无副作用。

    FistEngine::plan_revise

    fn FistEngine::plan_revise(self : FistEngine, root_task_id : String, feedback? : Array[Json], ns? : String) -> Json

    反馈驱动的计划修订(R98,ReAct arXiv 2210.03629 / CoPAL arXiv 2310.07263 蒸馏落地): 把"拆完即弃"升级为"执行中持续修订"——给定根任务及子任务执行反馈,计算计划三分: keep(已证有效、承诺保留)/ rework(失败或其依赖链受牵连,需返工/重做)/ ready (依赖全部有效且未执行 = 下一步可做)。feedback 缺省时读真实状态(已完成/已归档 =ok;已打回/已暂停=否;其余=未证)。级联:失败任务的下游依赖者一并 rework (plan commitment 控涟漪——与 saga_repair 依赖者闭包同构)。纯计算、只读不写库 (决策建议,执行权在 agent/指挥官,与 saga 返回补偿序列同一抽象层)。

    FistEngine::progress_gate

    fn FistEngine::progress_gate(self : FistEngine, task_id : String, budget : Int, spent_override : Int) -> Json

    进度预算路由门控(R88,PROGROUTER arXiv 2608.25992 蒸馏简化版):在线进度引导—— 对任务子树按「已消耗预算(难度权重 易/中/难→1/2/3,复用 extract_difficulty 单一来源) 与 完成进度(已完成+已归档 / 子树任务数)」做双路径剩余成本预测: 线性 = 燃尽率(burn_rate=spent/progress) × 剩余工作量(1-progress); 保守 = 线性 × 1.2(PROGROUTER 双路径,缓冲 20%)。 元门控给决策:OK(预算充足继续)/ CAUTION(线性可行但缓冲不足 → 建议降档/缩范围)/ ESCALATE(线性已超支 → 建议追加预算或暂停)。纯计算、无副作用。 spent_override>0 可覆盖自动估算(如按真实 token/成本统计注入);0 或缺省=自动。

    FistEngine::publish

    fn FistEngine::publish(self : FistEngine, project_dir~ : String, description~ : String, created_by~ : String, now~ : String, ns? : String, level? : Int) -> Result[String, String]

    发布根任务(仅限人类指挥官)

    FistEngine::publish_next_round

    fn FistEngine::publish_next_round(self : FistEngine, ns~ : String, description~ : String, created_by~ : String, prev_task_id~ : String, now~ : String, level? : Int) -> Result[String, String]

    自动续轮(仅供定时任务看门狗 watchdog_tick 调用,见 ops_watchdog.mbt)。

    语义:把已完成的上一轮根任务标记为已续轮并归档,同时发布下一轮根任务。 安全边界(watchdog 计划文档第 0 节):
    • 仅允许作用于非 default 命名空间,避免干扰人工指挥流程;
    • 上一轮必须是「已完成」的根任务。

    返回新根任务 id(T0r2、T0r3 ...)。

    FistEngine::publish_parallel

    fn FistEngine::publish_parallel(self : FistEngine, ns~ : String, description~ : String, created_by~ : String, project_dir~ : String, now~ : String, level? : Int) -> Result[String, String]

    并行追加独立根任务:连续发布多条互不依赖的根任务(自驱式编程「审视 -> 自我派活」用)。

    与 cold_publish(要求 ns 为空)和 publish_next_round(要求前轮已完成并续轮归档)不同, 本方法允许命名空间已有任务,直接以唯一 id(T0 / T0r2 / T0r3 ...)追加为新的独立根任务, 不续轮、不归档、不要求前轮状态,专用于同一审视报告的多条 Next Tasks 并行落地。

    FistEngine::publish_with_deps

    fn FistEngine::publish_with_deps(self : FistEngine, project_dir~ : String, description~ : String, depends_on? : Array[String], created_by? : String, now? : String, ns? : String, level? : Int) -> Result[String, String]

    创建任务时指定依赖(depends_on 为依赖的任务 id 列表)

    FistEngine::ready_tasks

    fn FistEngine::ready_tasks(self : FistEngine, ns_filter? : String) -> Array[
    Task
    ]

    获取当前命名空间内所有 Pending 且依赖满足、可领取的任务

    FistEngine::reject_task

    fn FistEngine::reject_task(self : FistEngine, task_id~ : String, reason? : String, by~ : String, now~ : String) -> Result[
    Task
    , String]

    验收拒绝(reject):待验收 -> 已打回

    参数 by(BUG-354 出路 c):驳回者身份。本函数体内不消费它——「谁驳回的」 在任务状态面没有落点(reason 会落库,by 不会)。审计面靠 call_log 的原始 入参。保留形参理由同 plan:它是 pub 接口 + MCP 入参 schema 的一部分,删它会 收缩对外契约;要真正消费需动 store schema,属行为变更。

    FistEngine::reopen_task

    fn FistEngine::reopen_task(self : FistEngine, task_id : String, now : String) -> Result[
    Task
    , String]

    回滚/重派:任意非归档任务 -> 已领取(M4 heal watchdog 用)。返回回滚后的任务。

    FistEngine::resume_task

    fn FistEngine::resume_task(self : FistEngine, task_id~ : String, now~ : String) -> Result[
    Task
    , String]

    恢复任务:已暂停 -> 已领取

    FistEngine::retry_task

    fn FistEngine::retry_task(self : FistEngine, task_id~ : String, now~ : String) -> Result[
    Task
    , String]

    打回后重试(retry):已打回 -> 执行中

    FistEngine::rsv_get

    fn FistEngine::rsv_get(self : FistEngine, scope : String) -> (String, String, String)?

    FistEngine::rsv_list

    fn FistEngine::rsv_list(self : FistEngine) -> Array[(String, String, String)]

    FistEngine::rsv_release

    fn FistEngine::rsv_release(self : FistEngine, scope : String, agent : String) -> Bool

    FistEngine::rsv_set

    fn FistEngine::rsv_set(self : FistEngine, scope~ : String, agent~ : String, ttl_until~ : String, created_at~ : String, challenge_nonce? : String?, challenge_sig? : String?) -> Result[String, String]

    FistEngine::rsv_try_set

    fn FistEngine::rsv_try_set(self : FistEngine, scope~ : String, agent~ : String, ttl_until~ : String, created_at~ : String, challenge_nonce? : String?, challenge_sig? : String?) -> Result[
    RsvOutcome
    , String]

    按裁决抢预订(BUG-136):互斥由后端定,工具面只渲染结论——不许在这里先读后写。 额外加了权限门禁(engine_scope_perm):没有编辑资格的角色不许抢预订。

    FistEngine::s3_consume_nonce

    fn FistEngine::s3_consume_nonce(self : FistEngine, agent : String, nonce : String, ns? : String) -> Bool

    BUG-466:跨进程可消费的一次性消费(engine 版,先查 store 再落内存)。

    与模块级 s3_consume_nonce 的差别:后者只认本进程的 s3_pending_nonces, 多实例部署时 A 进程签发的票 B 进程消费不掉(那正是 BUG-466 的病灶)。 本函数走 store.challenge_consume ⇒ SQLite 后端下跨进程可见。

    双写对齐:签发时同时写了 store 与内存表,故这里也两边都消费 (内存那边是 best-effort,失败不影响结论 —— store 的结果才是权威)。

    FistEngine::s3_issue_challenge

    fn FistEngine::s3_issue_challenge(self : FistEngine, agent : String, counter : Int, ns? : String, issued_at? : String, cleanup_cutoff? : String) -> String

    S3 第二段:签发一个 challenge 并登记为「待消费」。

    返回 nonce 字符串(调用方交客户端去签)。同一 agent 可同时有多个未消费 nonce (并发挑战),但每个只能用一次。

    ⚠️ counter 由调用方传入 —— 只适合测试/内部路径。 生产面要「服务端自选 counter」,请用 s3_issue_challenge_auto: 若让调用方自选 counter,调用方就能预计算 nonce 并预先签好, 那「挑战」就退化成一个固定字符串 ⇒ 验签失去意义(BUG-430 S3 第六段)。

    FistEngine::s3_issue_challenge_auto

    fn FistEngine::s3_issue_challenge_auto(self : FistEngine, agent : String, ns? : String, issued_at? : String, cleanup_cutoff? : String) -> String

    BUG-430 / S3 第六段:服务端自选 counter 签发 challenge(生产面用这个)。

    为什么必须服务端自选:issue_challenge_nonce(agent, counter) 是纯函数 ⇒ 若 counter 由调用方给,调用方可以离线预计算 nonce、提前签好, 然后任何时候提交都成立 ⇒ 「挑战」退化为固定字符串 ⇒ 验签形同虚设。

    计数器是进程级单调递增(s3_challenge_counter),每个 challenge 的 nonce 都不同。 与 s3_pending_nonces 一样是进程内的 ⇒ 多实例部署各自计数(见缺口)。

    FistEngine::saga_register

    fn FistEngine::saga_register(self : FistEngine, ns? : String, root_task_id~ : String, step~ : String, task_id? : String, compensation~ : String, now~ : String) -> Result[Json, String]

    登记一个 Saga 补偿步骤(幂等:同 (ns, root_task_id, step) 重登记会把 status 重置为 pending 并刷新 compensation——重试安全,不改 LIFO 位置)。

    FistEngine::saga_repair

    fn FistEngine::saga_repair(self : FistEngine, ns? : String, root_task_id : String, failed_step~ : String, mark? : Bool) -> Result[Json, String]

    局部补偿(控级联,R96,Plan Commitment / scope-aware repair 蒸馏落地): 给定失败步骤,计算最小补偿切片——失败步骤 + 其依赖下游(任务 depends_on 传递闭包中仍 pending 的步骤)——只补偿切片、保留切片外已接受承诺(keep), 与 saga_rollback(全局 LIFO 整链收尾)互补;无 task_id 时按注册序保守兜底。 mark=true(默认)返回后即把切片标记 done(幂等),keep 步骤保持 pending 供后续按需补偿(局部修复保留承诺,不涟漪撤销)。

    FistEngine::saga_rollback

    fn FistEngine::saga_rollback(self : FistEngine, ns? : String, root_task_id : String, mark? : Bool) -> Result[Json, String]

    按 LIFO(严格倒序)返回某根任务待补偿步骤序列;mark=true(默认)返回后一并标记 done(幂等:重复调用返回空 pending,防重复回滚;mark=false 仅预览不消费)。 补偿是"业务逆转"动作描述,调用方(agent/指挥官)按序列顺序执行真实补偿。

    FistEngine::store_delete_heartbeat

    fn FistEngine::store_delete_heartbeat(self : FistEngine, task_id~ : String) -> Result[Unit, String]

    删除心跳记录(委托给 store 后端)。

    FistEngine::store_list_heartbeat_intervals

    fn FistEngine::store_list_heartbeat_intervals(self : FistEngine, task_id : String) -> Array[Int]

    读取心跳间隔历史(R90,时间序旧→新;委托给 store 后端)。

    FistEngine::store_list_heartbeats

    fn FistEngine::store_list_heartbeats(self : FistEngine) -> Array[(String, String, String, String)]

    列出全部心跳记录(供启动时加载)。

    FistEngine::store_list_saga_actions

    fn FistEngine::store_list_saga_actions(self : FistEngine, ns : String, root_task_id : String) -> Array[(Int, String, String, String, String)]

    列出某根任务的 Saga 补偿步骤(R92;前向顺序)。

    FistEngine::store_mark_saga_actions_done

    fn FistEngine::store_mark_saga_actions_done(self : FistEngine, ns? : String, root_task_id~ : String, ids~ : Array[Int]) -> Result[Unit, String]

    把指定 Saga 补偿步骤标记 done(R92;幂等)。

    FistEngine::store_read_heartbeat_last_seen

    fn FistEngine::store_read_heartbeat_last_seen(self : FistEngine, task_id : String) -> String

    读单个任务的持久化心跳 last_seen(BUG-119:跨进程真相的唯一读取点;无记录返回空串)。

    FistEngine::store_recent_call_logs

    fn FistEngine::store_recent_call_logs(self : FistEngine, limit? : Int) -> Array[Json]

    读取最近 N 条调用日志(委托给 store 后端,新的在前)。

    FistEngine::store_write_call_log

    fn FistEngine::store_write_call_log(self : FistEngine, ts~ : String, tool~ : String, caller~ : String, ns~ : String, params_json~ : String, result_json~ : String, ok~ : Bool, runtime_ms~ : Int, seq~ : Int) -> Result[Unit, String]

    写入一条调用日志(委托给 store 后端;best-effort,调用方应忽略错误)。

    FistEngine::store_write_heartbeat

    fn FistEngine::store_write_heartbeat(self : FistEngine, agent_id~ : String, task_id~ : String, last_seen~ : String, status~ : String) -> Result[Unit, String]

    写入心跳记录(委托给 store 后端)。

    FistEngine::store_write_heartbeat_interval

    fn FistEngine::store_write_heartbeat_interval(self : FistEngine, task_id~ : String, interval_sec~ : Int, ts~ : String) -> Result[Unit, String]

    追加心跳间隔历史(R90,Phi Accrual 判活数据源;委托给 store 后端)。

    FistEngine::store_write_saga_action

    fn FistEngine::store_write_saga_action(self : FistEngine, ns? : String, root_task_id~ : String, step~ : String, task_id? : String, compensation~ : String, created_at~ : String) -> Result[Unit, String]

    登记 Saga 补偿步骤(R92,durable action log;委托给 store 后端)。

    FistEngine::submit

    fn FistEngine::submit(self : FistEngine, task_id~ : String, now~ : String) -> Result[
    Task
    , String]

    FistEngine::supervisor_kill_apply_pause

    fn FistEngine::supervisor_kill_apply_pause(self : FistEngine, task_id~ : String, now~ : String) -> Result[
    Task
    , String]

    可选的可逆出口:按 kill 方案走 pause(已暂停可 resume,可逆)。

    FistEngine::supervisor_kill_plan

    fn FistEngine::supervisor_kill_plan(self : FistEngine, task_id~ : String, by~ : String, reason~ : String, force? : Bool, apply_pause? : Bool, now? : String) -> Json

    kill 硬门链评估(只读纯计算,不执行任何终止): G1 身份门:by == human_steward 或现任主助理 G2 目标存在 G3 不可杀目标:根任务(拳长承诺,design v2 的 kill 权唯一性)不可杀 G4 终态检查:已完成/已归档不可杀 G5 force+reason:force=true 且 reason 非空才 allowed,否则 only_plan verdict ∈ allowed / only_plan / blocked

    FistEngine::supervisor_reminder_tick

    fn FistEngine::supervisor_reminder_tick(self : FistEngine, now~ : String, stale_hours? : Int, pending_hours? : Int) -> Json

    提醒者轮询(纯计算,只读不修改):扫描全库任务产出提醒清单。 R2 停滞:活跃任务最近更新早于 now-stale_hours; R3 事项超时:待处理任务最近更新早于 now-pending_hours。 时间解析失败的任务跳过(不阻断轮询)。

    FistEngine::supervisor_status

    fn FistEngine::supervisor_status(self : FistEngine, ns_filter? : String, detail? : Bool) -> Json

    全库(或指定 ns)状态聚合:供 supervisor_status 使用。纯读。 detail=false(默认)时 active 里每个任务的 description 截断到 200 字节 (字符边界安全,见 @core.desc_clip),控 MCP 返回体积——task_plan_deep 开满 gradient/reinject_context/boundary_probe 后描述会递归膨胀到数 KB,全量回传会炸。 detail=true 返回完整描述(调用方按需取全文)。

    FistEngine::supervisor_task_tree

    fn FistEngine::supervisor_task_tree(self : FistEngine, root_id : String, detail? : Bool) -> Result[Json, String]

    任务树状态与进度汇报:root 起递归,每节点带 subtree_done/subtree_total。 detail=false(默认)时各节点 task 的 description 截断到 200 字节(与 supervisor_status 同口径)。

    FistEngine::supervisor_task_upsert

    fn FistEngine::supervisor_task_upsert(self : FistEngine, project_dir~ : String, by~ : String, items : Array[Json], now~ : String, ns? : String) -> Json

    更新目标项目任务列表:无 task_id 的 item=追加新根任务(publish,仅 human_steward), 有 task_id 的 item=更新描述/优先级/追加依赖。终态任务拒改。逐条独立成败。

    FistEngine::topo_sort

    fn FistEngine::topo_sort(self : FistEngine, task_ids : Array[String]) -> Array[String]

    拓扑排序(Kahn 算法):task A 在 depends_on 中的任务之后执行(依赖先输出)。 仅按 task_ids 集合内的依赖建图;集合外的依赖视为已满足(不影响本集合内排序)。 稳定:同批 indegree=0 的节点按 task_ids 原序输出;环残留节点按原序追加尾部。

    FistEngine::transition_contract

    fn FistEngine::transition_contract(self : FistEngine, task_id : String, action : String, assignee? : String, completed_by? : String, force? : Bool) -> Json

    迁移契约检查(R109 · Design by Contract 蒸馏):precondition / invariant / postcondition 三件套只读预检。 任一失败 → verdict=rejected 整笔拒绝,状态 A 回稳(不落库);全部通过 → verdict=allowed(建议仍走正式生命周期工具执行)。

    FistEngine::triage

    fn FistEngine::triage(self : FistEngine, ns_filter : String, agent : String, want : String, skill? : String) -> Json

    下一步推荐(Triage,带能力偏好路由 + skill 命中度路由):返回 { count, ns, agent, want, skill?, suggestion?, tasks:[...] }。 ns_filter 为空表示全量;只统计"待领取且依赖已满足"的任务。want 为能力关键词(Marketplookup 雏形): 非空时,命中该关键词的候选排在头部(按能力匹配路由"这个 agent 擅长啥就先领啥"),不命中者照旧跟进。 skill 可选(逗号分隔 skill 关键词):非空时按 skill 命中度排在能力匹配之后、优先级之前,并在 suggestion 里给出建议的开发模式(SKILL→MODE 建议表);空串 = 不做 skill 路由,排序与输出与旧版完全一致(零回归)。

    FistEngine::verify

    fn FistEngine::verify(self : FistEngine, task_id~ : String, verifier~ : String, now~ : String, docs_check? : Bool) -> Result[
    Task
    , String]

    验收(verifier):Reviewing -> Completed;若因此父任务全部子任务完成,则父任务自动待验收。

    PermissionRule

    type PermissionRule

    权限规则:pattern 是 glob 前缀("src/core/" 匹配该目录所有文件)

    ScopeVerdict

    pub enum ScopeVerdict {
    Allowed
    RoleDenied(String)
    Unregistered(String)
    }

    权限判定三态(BUG-413)——三种拒绝面必须能被调用方区分: · Allowed 已登记且角色够格 ⇒ 正常预订 · RoleDenied 已登记但角色不够(HumanSteward 保护等)⇒ 必须继续拒绝,绝不放行 · Unregistered 根本没有匹配规则 ⇒ advisory 登记,不参与授权裁决

    为什么必须拆开:旧实现把两种 Err 混成同一个 String,调用方无法分辨 「这条路我不能碰」(有规则、我不够格)与「这条路没人管、你自己负责字面不相交」。 后果是 explore 模式的第 4 道并行门恒不可过——reports/ 这类未登记路径永远拿不到 granted。

    Unregistered 不是放行授权:它只表示「预订登记成功」; 真实写权限仍由 RoleDenied 那一侧决定(HumanSteward 保护路径照旧拒绝)。

    为什么 Allowed 不带 PermissionRule 载荷:PermissionRule 是包内私有 struct, 作为 pub enum 的 variant 载荷会触发可见性错误。需要规则详情时用 matching_rule()。

    COMPLEXITY_L1

    let COMPLEXITY_L1 : String

    复杂度分级枚举(文本常量)

    COMPLEXITY_L2

    let COMPLEXITY_L2 : String

    COMPLEXITY_L3

    let COMPLEXITY_L3 : String

    COMPLEXITY_L4

    let COMPLEXITY_L4 : String

    COST_TIER_FREE

    let COST_TIER_FREE : String

    成本档枚举

    COST_TIER_HOLD

    let COST_TIER_HOLD : String

    COST_TIER_PREMIUM

    let COST_TIER_PREMIUM : String

    advance_mark_prefix

    fn advance_mark_prefix() -> String

    自动续轮标记前缀:已完成但尚未续轮的根任务,其 deliverable 不带该前缀。

    agent_fp_of

    fn agent_fp_of(raw : String) -> String

    agent 原始名 → 落库主键指纹(BUG-418)。

    为什么 engine 里要有这一份:server.mbt 的 agent_fp 是包内私有 (fn agent_fp(raw),无 pub),engine 包看不到它;而 reserve_scope 收到的 agent 是调用方自由填的原始名(文档示例就是 exec_1), 而 agents.agent_id 落的是 16 位指纹。

    ⇒ 想查 agents.role 必须先自己算指纹。直接传原始名永远查不到 (那会让「查权威角色」静默失效,等于没修)。实现必须与 server.mbt 逐字同构: @omega.fnv1a64("agent_id:" + raw)。

    本条被白盒锁 rswb_4 钉住:断言「原始名查不到、指纹查得到」。

    auth_mode

    fn auth_mode() -> AuthMode

    唯一的 env 读取点(单一真源,判例-29)。

    首次调用读 FIST_AUTH_MODE 并缓存;之后直接返回缓存。 任何其它地方要问「当前是什么模式」都必须调 auth_mode(),不得直接读 env。

    auth_mode_parse

    fn auth_mode_parse(raw : String) -> AuthMode

    解析 FIST_AUTH_MODE 的原始字符串(纯函数,唯一判定面)。

    取值(大小写不敏感、两端空白忽略): · "off" ⇒ Off · "soft" ⇒ Soft · "strict" ⇒ Strict · 空串 / 未设 ⇒ Off(默认值) · 其它任何值 ⇒ Off + warning(见 auth_mode_warning)

    ⚠️ 非法值回退 Off 而不是 Strict:写错一个字母不该让现网停摆。 fail-closed 在这里是错的方向 —— 因为本开关的「严」档会让 56 个调用方失败, 而「回退默认」只是维持现状。配置写错 ≠ 环境不安全。

    auth_mode_reset_cache_for_test

    fn auth_mode_reset_cache_for_test() -> Unit

    测试专用:重置缓存(让同一进程内能切换模式跑多格测试)。

    ⚠️ 生产代码不要调 —— 它会让「读一次就定」的保证失效。

    auth_mode_startup_warning

    fn auth_mode_startup_warning() -> String

    仅在进程启动时调一次,拿到「配置有问题」的 warning(无问题则空串)。

    两类问题都返回非空:
    1. 非法值(如 garbage)⇒ 已回退 off
    2. strict 尚未真正生效(见下)

    ⚠️ 为什么 strict 也要告警:本版只做到「返回专用 challenge_missing 标签 + 降权」, 各门的消费者还没据此硬拒 ⇒ 实际效果接近 soft。 若有人设 strict 以为「现在严格了」,那是比不设更危险的状态(以为自己安全了)。

    auth_mode_warning

    fn auth_mode_warning(raw : String) -> String

    非法值时的 warning 文案;合法值 / 未设时返回空串(调用方据此决定是否打日志)。

    为什么要单独一个函数:auth_mode_parse 必须保持纯(可测、无副作用), 而「要不要警告」是另一个判断(判例-29:判定面与渲染面分开)。

    auth_tag

    fn auth_tag(role_source : String) -> String

    boundary_probe_hint

    fn boundary_probe_hint() -> String

    边界四问·叶级提示(R115 后置改进,对准 atgc-merge 归因报告机理 2「验收锚定 + 弱语料」)。 语料只断言"做了没"时,执行体理性地停在判据边界上;这一行把"域外行为"变成每条叶子的显式义务。

    boundary_probe_slice

    fn boundary_probe_slice() -> String

    边界审视叶的切片文本(对准机理 1「递归拆解损失全局视角 ⇒ 全局不变量无 owner」)。 拆完之后"spec 没写的域外行为"是无人负责地带;本切片给这类思考一个可领取、可验收的归属。

    budget_check

    fn budget_check(limit : Double, current : Double) -> Json

    预算检查结果 返回 JSON:{ limit, current, valid, exceeded, remaining, action, reason }

    BUG-181 契约(三条回归锁钉在 src/engine/cost_tool_test.mbt): ① 超支时 remaining 为负(= current - limit 的相反数,即 limit-current), 不静默归零——超支额必须可见; ② limit<=0 视为非法预算:valid=false + exceeded=false + action=rejected, 不报误导性的 "switch_free"(旧实现里 current>=limit 恒真 ⇒ 谎报超支)。

    budget_check_json

    fn budget_check_json(args : Json) -> Json

    从 JSON 参数计算 budget_check(供 MCP 工具调用)。 输入 JSON:{ "limit": Number, "current": Number }

    ch_desc

    fn ch_desc(task_id : String, src_desc : String, frame : String) -> String

    组装一条候选挑战题的描述(任一策略共用同一模板,保证可比)。

    challenge_strategies

    fn challenge_strategies() -> Array[String]

    进阶策略白名单:为已完成任务"上难度"的几种规则化变体框架。 generalize:去具体场景 → 抽象为通用能力并覆盖边界 unhint :去提示/约束 → 从空白规格重推且不降正确性 constrain :加硬约束 → 本次在性能/并发/兼容上加严并保证不回归 scale :扩规模 → 把规模/负载提升 factor 倍并保持语义正确

    check_scope_permission

    fn check_scope_permission(scope : String, agent_role : String) -> ScopeVerdict

    判断 agent(按角色)是否有权限编辑 scope。 不再用单一 Result[_, String] 表达三种不同结论 —— 见 ScopeVerdict(BUG-413)。

    deps_ready_memo

    fn deps_ready_memo(dep_ids : Array[String], completed : Map[String, Bool]) -> Bool

    判断 dep_ids 是否全部在 completed 映射中为已完成。 供 ready_tasks / parallelism 在 O(1) Map 查询下做依赖判断 (替代逐条 store.get_task)。pub 以便 engine_dag_ext 复用。

    extract_difficulty

    fn extract_difficulty(desc : String) -> String

    从任务描述中抽取 LADDER 难度档([难度梯度 idx/n:档] 中档位子串),无则返回空串。

    gate_enabled

    fn gate_enabled(t :
    Task
    ) -> Bool

    任务是否开启外部判据闸门。

    gate_mark

    fn gate_mark() -> String

    外部判据标记:出现在任务 description 中即表示该任务验收必须过闸门。

    infer_role_from_agent

    fn infer_role_from_agent(agent_name : String) -> String

    从 agent 名字推断角色(BUG-418 收窄 + BUG-421 二次收窄:只做缺省提示,不是授权依据)。

    ⚠️ BUG-418 前的实现用 contains("commander") 做子串匹配 ⇒ 任何自称 commander-fake 的 agent 都能拿到 HumanSteward,进而过 HumanSteward 保护门。 那不是「文档不准」,是可主动利用的越权面。

    收窄规则(BUG-418 裁决 (b) + BUG-421 收口,按「整名精确匹配」): · 禁止「前缀/子串命中即给高角色」—— 这才是攻击面 · 只允许人类指挥官的整名:human_steward / human ⇒ HumanSteward · 其余一律 Executor,包括整名 leader / lead(BUG-421): 那两条白名单写于 BUG-418 修法期,理由是「关掉会把军官自报这条路关死」—— 但「军官自报」本身就是「名字即身份」,与 BUG-418 要堵的是同一个东西, 只是从「任意子串」收窄成了「一个单词」。实测 agent=leader 可订 src/engine/ / src/core/ / src/router/ / cmd/(军官面), 与「只有 human_steward/human 两个精确匹配算特权」的口径不一致。

    ⇒ 现在 leader / lead / LEADER → Executor(与 commander-fake 同一档) ⇒ human_steward / human → HumanSteward(人类指挥官唯一路径,保留)

    ⚠️ 硬约束:角色不可从名字推断 Leader。Leader 只来自 agents.role(已登记身份)。 需要 Leader 面的 lane 必须在 agents 表被显式登记成 role="Leader" (唯一入口是 server 启动注册;当前没有面向 agent 的自助登记工具面, 这是 BUG-421 关闭后的既定代价 —— 见 memory/bugs.md BUG-421「风险」段与 reports/2026-10-27-bug421-decision-brief.md Q2 的步数推算)。

    权威面仍是 agents.role(见 resolve_agent_role):本函数只在查不到声明时做兜底, 且回执会带 role_source="inferred" 让调用方知道这是猜的。

    inheritable_role

    fn inheritable_role(declared_role : String) -> String?

    哪些角色允许从「自报名字 → agents 登记表」这条路继承(BUG-422 加固)。

    · Executor / Leader ⇒ 可继承(Leader 是「派下来的军官」的唯一正道) · HumanSteward 与任何未知值 ⇒ None,永不继承

    为什么把 HumanSteward 挡在继承面之外:HS 是人类指挥官的唯一身份, 独占 4 条不可逆路径(store_sqlite.mbt / store.mbt / server.mbt / moon.mod)。 「报一个已登记名字 ⇒ 继承它的 role」如果能把 HS 交出去,等于把 BUG-418 刚关掉的统帅面从登记表那一侧又开回来,而且这次连 human_steward 这个整名都不用猜。 现网 agents 表没有任何 HS 记录(agent_touch_first_seen 只写 Executor, server 启动只写一条 Leader)⇒ 本限制的现实代价为 0,纯属把未来的口先堵上。

    issue_challenge_nonce

    fn issue_challenge_nonce(agent : String, counter : Int) -> String

    S3:生成一次挑战的 nonce(纯函数,不落库、不读时钟)。

    为什么用「计数器 + agent」而不是随机数:moonbitlang/x 没有独立的 random 包 (可行性核查已确认)。而 Ed25519 的 nonce 并不需要密码学随机 —— nonce 的作用是「一次性、不可重放」,由「单调计数器 + 主体标识」即可满足: 同一 agent 每次挑战拿到不同 nonce,且 nonce 里绑定了 agent(换主体即失效)。

    ⚠️ 本函数不保证全局唯一(纯函数不可能做到)。真正的唯一性由调用方传入的 计数器保证(服务端单调递增)。这是有意的:纯函数可以在测试里完全确定。

    judged_role_gated

    fn judged_role_gated(eng : FistEngine, agent : String, challenge_nonce : String?, challenge_sig? : String?) -> (String, String)

    S3 第二段:四道门专用的取数面(保留原有 role_source 语义)。

    与 judged_role_with_challenge 的关键区别 —— None 分支不做任何改变: · challenge_nonce = None ⇒ 原样返回 judged_role 的结果(declared / inferred)。 理由:四道门的回执文案(如 RANK_GATE: ... role_source=inferred)与既有测试 都依赖这两个取值;把 None 改标成 "unauthenticated" 会改掉现网语义 (实测代价:3 格 rank_gate 测试立刻红)—— 那是第三段的「全量切换到认证」才该做的事。 · challenge_nonce = Some(_) ⇒ 必须验签:通过 ⇒ (role, "challenge_verified") (真实身份,可以拿高权);失败或未登记公钥 ⇒ ("Executor", "challenge_failed")(fail-closed)。

    ⇒ 语义是「加严不放宽」:不传挑战 = 完全维持现状;传了挑战 = 必须验过才给真角色。 真正想「没挑战就不给高权」是第三段(全量切换),本段刻意不做,以免半途改坏现网。

    judged_role_with_challenge

    fn judged_role_with_challenge(eng : FistEngine, agent : String, challenge_nonce : String?, challenge_sig? : String?) -> (String, String)

    S3:带挑战的角色取数(第一段的核心 —— 挑战真的接进了取数面)。

    语义(三态): · challenge_nonce = None ⇒ 走现有 judged_role 逻辑(零回归),但 role_source 强制标注 "unauthenticated" —— 因为没有挑战就意味着身份未认证。 这是 S1 公告的延续:S1 说「自报不可验证」,S3 第一段说「未认证就别装成 declared」。 · challenge_nonce = Some(nonce) 且验签通过 ⇒ 返回 (role, "challenge_verified"),role 来自 declared_role_for_judgment (即 agents 表里的 declared 角色)。 · challenge_nonce = Some(_) 但验签失败(含公钥未登记、hex 坏、签名不匹配) ⇒ 降级为 ("Executor", "challenge_failed") —— fail-closed,绝不放行高权。

    ⚠️ role_source 的取值扩展(调用方可见的变化): 原有 "declared" / "inferred" 之外新增 "unauthenticated" / "challenge_verified" / "challenge_failed"。这不是改语义,是让「认证状态」显式可见。

    ⚠️ 诚实边界:本函数不改变 judged_role(4 个现有调用点零回归)。 要把 S3 真正生效,还需要让四条门都改调本函数 —— 那是第二段。

    S3 第三段:新增 challenge_sig : String? 参数 —— 由调用方提交签名。 第一段用 s3_last_sig_probe(返回空串,永远验不过)做占位;第三段删掉了它, 因为「服务端自己造签名」等于自己给自己发通行证。 签名只能来自调用方私钥,服务端只负责验 —— 这是 S3 的命门。

    judged_role_with_nonce

    fn judged_role_with_nonce(eng : FistEngine, agent : String, challenge_nonce : String?, challenge_sig? : String?) -> (String, String)

    S3 第二段:带 nonce 防重放的挑战判定(四条门统一入口)。

    与 judged_role_with_challenge 的区别:本函数在验签前后各多一道 nonce 闸:
    1. challenge_nonce = None ⇒ 直接走无挑战路径(不消费任何 nonce);
    2. Some(nonce) ⇒ 先验签,验签通过才消费 nonce(避免「验签失败也烧掉 nonce」 导致合法调用方白白重试);
    3. nonce 已被消费过 ⇒ challenge_replayed(不降级为 unauthenticated, 而是明确告诉调用方「你重放了」)。

    ⚠️ 返回值 role_source 的新增取值:"challenge_replayed"。

    key_revoke_not_needed

    let key_revoke_not_needed : String

    key_revoke_ok

    let key_revoke_ok : String

    key_revoke_verdict

    fn key_revoke_verdict(prev_key : String) -> String

    吊销判定(纯函数):只有当前确实有公钥的 agent 才谈得上吊销。

    为什么单独判定:吊销一个没键的 agent 应报「无需吊销」而不是「吊销成功」—— 否则回执会让调用方以为「我把他锁掉了」,实际什么都没变(假安全)。

    key_rotate_allowed

    let key_rotate_allowed : String

    key_rotate_not_needed

    let key_rotate_not_needed : String

    轮换判定取值常量(单一真源)。

    key_rotate_reject_bad_proof

    let key_rotate_reject_bad_proof : String

    key_rotate_reject_no_proof

    let key_rotate_reject_no_proof : String

    key_rotate_verdict

    fn key_rotate_verdict(prev_key : String, pubkey_hex : String, rotate_sig : String, old_sig_ok : Bool) -> String

    BUG-430 / S3 第八段:密钥轮换的准入判定(纯函数 —— 唯一判定面)。

    背景:key_pin_verdict 对「已有公钥且与本次不同」返回 reject_different_key, 拒绝文案里写着「轮换通道尚未实现」。没有轮换 = 私钥丢了就永久锁死 (攻击者抢注成功后,正主再也拿不回自己的身份)。本函数补上那条通道。

    规则(只加严,不放宽): · 库里没有公钥 ⇒ rotate_not_needed(走首登路径,不需要旧钥证明) · 库里公钥 == 本次公钥 ⇒ rotate_not_needed(幂等,不需要旧钥证明) · 库里公钥 != 本次公钥 ⇒ 必须由旧私钥对本次 challenge 签名: - 没给 rotate_sig ⇒ rotate_reject_no_proof(拒绝) - 给了但旧公钥验不过 ⇒ rotate_reject_bad_proof(拒绝) - 给了且旧公钥验过 ⇒ rotate_allowed(允许换)

    ⚠️ 为什么必须验旧钥:换钥若不需要任何证明,则任何能调到本工具的调用方 都能把某身份的公钥换成自己的 ⇒ 身份劫持(正是 key_pin 那条锁要防的事)。 「旧私钥签得出」是本仓唯一能证明「你就是原来那个主体」的手段。

    ⚠️ 本函数只判定、不 IO:验签留在 handler(纯件才可测,判例-29)。

    matching_rule

    fn matching_rule(scope : String) -> PermissionRule?

    取 scope 命中的规则(Allowed 之外的调用方若需要规则详情,走这里)。

    mc_percentile_index

    fn mc_percentile_index(n : Int, p : Int) -> Int

    最近秩分位索引:n 个有序样本(升序)取 p 分位(0..100)应用的 0-based 索引。 BUG-206:旧公式 p50 用 (n-1)/2、p90 用 n9/10,两条取法不对称——p90 比最近秩 ceil(pn/100)-1 恒高 1,系统性高估 p90 一个样本值。这里统一为 (n-1)*p/100。

    omega_author_prefix

    fn omega_author_prefix() -> String

    语料 author 前缀(纯文本行,避免为嵌入 author 而引入 JSON 转义)。

    omega_clean_slice

    fn omega_clean_slice(s : String) -> String

    去掉切片文本中已存在的强验证标记(避免递归下钻时标记逐层累积)。

    omega_enabled

    fn omega_enabled(t :
    Task
    ) -> Bool

    任务是否处于强验证模式(未打标记 = 关闭 = 既有行为)。 BUG-389 方向②:门禁判据以 tasks 元数据列 omega_required 为准(description 被 supervisor_task_upsert 覆盖后仍成立);description 里的 [omega:required] 标记 保留为旧数据的兜底兼容(两者取或)。

    omega_escalation_id

    fn omega_escalation_id(task_id : String, stage : String, round : Int) -> String

    升级记录 id:escalation:<task_id>::r

    omega_mark

    fn omega_mark() -> String

    强验证标记:出现在任务 description 中即表示该任务处于强验证模式。

    omega_max_rounds_default

    fn omega_max_rounds_default() -> Int

    默认最大打回轮次。

    omega_norm_max_rounds

    fn omega_norm_max_rounds(v : Int) -> Int

    归一化轮次上限:非正数取默认,超过 10 硬截断(防死循环的最终边界)。

    omega_orphan_of_archived_parent

    fn omega_orphan_of_archived_parent(t :
    Task
    , parent_status :
    TaskStatus
    ?) -> Bool

    「孤儿 Omega 任务」判据(BUG-417):本任务自己没开 omega(元数据列 = false), 描述里的 [omega:required] 是从父层切片继承来的残留,且直接父任务已归档。

    为什么需要这个判据:omega_enabled 的文本兜底(description.contains(mark)) 不区分标记归属 —— 一个 omega_required=0 的任务,只因描述里引用了父层文本 (含标记)就被判为强验证任务。task_plan_deep 递归下钻 + reinject_context 把父层切片拼进子层描述时,标记会逐层累积。

    实测(Lane T):孤儿 T0r636.1.2 描述长 4683 字节,[父计划: 出现 4 次, 两个 omega 标记都在嵌套段内 ⇒ strip_plan_annotations 的单层截断有盲区。

    与 BUG-389 正相反:那条是「标记被静默覆盖」,本条是「标记被静默继承」。

    语义依据:omega_strong_verify 的语义是「这棵任务树的成果要被验证」。 父已归档 ⇒ 那棵树的验收语境已终结 ⇒ 让一个没有父亲的叶子继续满足门禁是判据错位。

    只豁免「直接父已归档」这一种:父未归档 / 取不到 / 祖父归档 / 根任务 一律 false(fail-closed)。 parent_status 由调用方查库传入 ⇒ 本函数保持纯、可单测、无 IO。

    omega_result_id

    fn omega_result_id(task_id : String, round : Int) -> String

    成果复验记录 id:result:<task_id>:r

    omega_spec_id

    fn omega_spec_id(task_id : String, round : Int) -> String

    语料记录 id:spec:<task_id>:r

    phi_accrual

    fn phi_accrual(intervals : Array[Int], elapsed : Int, threshold? : Int) -> Json

    Phi Accrual 主计算:给定心跳间隔历史 intervals(秒)与自上次心跳起的 elapsed(秒), 返回 { verdict: rejected|insufficient|healthy|suspect, phi, mean, stddev, window, threshold, note, basis }。 threshold 默认 8(原论文口径);窗口默认取最近全部传入间隔(≤1000 条)。 取值域外返回 rejected 且不出结论——以前这类输入带着饱和后的均值照样回 healthy, 看护据此不 heal,任务永久卡在已领取。

    phi_coerce_seconds

    fn phi_coerce_seconds(v : Double) -> Int?

    JSON 秒数 → Int 的安全转换(BUG-26 的上溢落点):Double::to_int() 对超出 Int 域的 值(如实测的 1e300)饱和成 2147483647,随后仍被当成均值继续算 φ;带小数的值被截断、 非数字元素被整条丢掉。这里把这三类失真一律收成 None,让调用面在转换之前拒绝。

    phi_domain_error

    fn phi_domain_error(intervals : Array[Int], elapsed : Int) -> String

    取值域校验(BUG-26):φ 的建模输入一旦失真(负 elapsed、非正间隔),输出仍是格式完好的 healthy/suspect——看起来是结论,实际是溢出后的算术。把「不可建模」提到结论之前。 返回拒绝原因(空串=通过)。间隔的上溢发生在 JSON→Int 的转换点,由调用面拦。

    plan_route

    fn plan_route(description : String, n_files? : Int) -> Json

    组合 schedule + route:输入 description + n_files,输出完整调度+路由方案。 返回 JSON:{ schedule: {...}, route: {...} }

    preserve_plan_markers

    fn preserve_plan_markers(old_desc : String, new_desc : String) -> String

    合并 decomposer 注入的结构标记:新描述缺哪些,就把旧描述里的对应标记补回(BUG-389)。

    supervisor_task_upsert 用 with_description 整体覆盖 description,而用户经超级管家 更新任务描述时通常不会带上 [omega:required] / [边界四问…] / [难度梯度 …] / [父计划:…] / [spec:…] 这些由 decomposer 注入的结构标记;覆盖后标记被静默丢失, 之后 omega_spec_create 判门禁读到 description 不含 [omega:required] 就误报 「未开启 Omega 强验证」,把「标记丢失」伪装成「用户没开 Omega」。

    语义:对 plan_markers() 列出的标记面做「缺失补回」——若旧描述含某标记而新描述不含, 则把该标记(连同其前导空格,omega_mark() 无前导空格)追加到新描述末尾。

    rank_of_role

    fn rank_of_role(role : String) -> Int

    角色 → 军衔数字(BUG-432 正解:role → rank 取代 name → rank)。

    ⚠️ 别把本函数与 role_rank 混成一套:role_rank 是角色档位(数值越大权越大,2/1/0), 本函数是 claim 门用的军衔数字(数值越小权越大,口径见 rank_can_claim 的 arank == 0 || arank <= tlevel)。两者方向相反,这正是 BUG-432 立案时被绕进去的地方 (抬头写「可能拿到更高档」,实测 rank 6 比默认档 5 更低权)。

    rolerank军衔依据
    HumanSteward0司令/统帅人类指挥官通吃(与整名白名单 human_steward/human 同源)
    Leader4团长军官线:必须比默认执行者档(5)更高。旧表把 leader* 判成 6(连长)⇒ 军官比普通执行者更受限,方向反转
    Executor5营长现网默认档,逐字沿用 estimate_rank 的 else 分支 ⇒ 零回归
    未知 / 空串 / 拼错5—fail-closed 到默认档:未知值不提权(也不误伤)

    为什么 Executor 不是 7(排长)/8(士兵):门是 arank <= tlevel,现网 100% 任务 level=6 ⇒ rank 7 会让所有 Executor 连 level=6 都领不了(门直接焊死,全仓 claim 全线 Err)。 5 是现网实际生效的默认档,平移过来即零回归。

    ⚠️ 硬约束:本函数只接受角色,永不接受「名字」。has_prefix("leader") ⇒ 6 已随 BUG-432 删除; claim 侧要拿军衔,必须先经 judged_role(登记优先 + 收窄后推断兜底)拿到角色。 判据见 claim_rank_wbtest.mbt 的 G5(行为翻转锁)+ 入档回执里的变异对照(判例-29/30: 不许写「grep 源码有没有 has_prefix」那种会命中注释的自指判据 —— 它是恒绿的)。

    resolve_agent_role

    fn resolve_agent_role(agent_name : String, declared : String?) -> (String, String)

    解析 agent 的裁决角色与来源(BUG-418 裁决 (a) + BUG-422 B 案收口)。

    BUG-422 前的语义(已废):agents.role 无条件优先(declared > inferred)。 那条规则的致命之处不在「读表」,而在查表键是调用方自报的名字: 报 agent="fist-mbt" 就继承了 server.mbt 启动时写下的 role="Leader"(实测 ALLOWED)。

    现在的语义(三件硬约束,缺一不可):
    1. inferred 是下限 —— 声明不许降级:名字已经推出 HumanSteward 时, 一条 role="Executor" 的登记记录不能把它压回 Executor(BUG-423 的注册面毒化同族)。
    2. 只有可继承角色生效 —— 见 inheritable_role:Executor/Leader 可继承, HumanSteward 与未知值一律忽略声明(fail-closed 到推断)。
    3. 声明必须来自「非本进程自身份」的登记记录 —— 这一条不在本纯函数里做, 而在它的取数侧:engine_rsv.mbt 的 declared_role_for_judgment 走 SqliteStore::agents_role_for_judgment,后者把 agent_type == "server" 的记录 整条剔除(那是服务器自己的身份,任何外部 agent 报它的名字都不该继承它)。 ⇒ 本函数保持纯、无 IO、可单测;「谁能被冒用」的政策落在 store 那一层,且有单测钉住。

    返回 (role, source): · "declared" = 裁决角色来自一条通过上述过滤的登记记录 · "inferred" = 没有可用声明(未登记 / 被过滤 / 角色不可继承 / 低于推断)

    role_rank

    fn role_rank(role : String) -> Int

    角色档位(数值越大权越大)。未知/拼错的值一律 0(= Executor 档)—— fail-closed。

    为什么需要它(BUG-422):resolve_agent_role 要把「名字推断」当作下限, 就不能再用字符串相等判「谁更高」。档位表是唯一真源,别处不要再手写比较。

    root_task_id

    fn root_task_id() -> String

    根任务 id

    route

    fn route(complexity : String, cost_tier : String) -> Json

    路由结果 返回 JSON:{ executor, cost_tier, hold, priority, reason }

    s3_consume_nonce

    fn s3_consume_nonce(agent : String, nonce : String) -> Bool

    S3 第二段:一次性消费(原子:查到即删除,第二次查必空)。

    语义:nonce 必须在已登记且未消费集合里,才返回 true 并立即移除。 重复提交同一 nonce ⇒ 第二次 false ⇒ 防重放。

    为什么是「消费」而不是「标记 consumed」:本表只存未消费的, 消费即删除 ⇒ 表不会无限增长,且**不存在「标记了但没生效」**的中间态。

    s3_pending_nonces

    let s3_pending_nonces : Map[String, Array[String]]

    S3 第二段:nonce 登记表(进程内,不落库)。

    key = agent 的归一化 fp,value = 「已签发但未消费」的 nonce 集合。

    为什么用进程内而不是新表:store_sqlite.mbt 是 HumanSteward 独占面(本轮改不了), 且 nonce 的语义天然是短命的(一次挑战一用即弃),进程内存储足够; 代价是跨进程不共享(多实例部署时各自的 nonce 互不可见)—— 已在 §缺口 登记。

    schedule_json

    fn schedule_json(args : Json) -> Json

    从 JSON 参数中计算 schedule(供 MCP 工具调用)。 输入 JSON:{ "description": String, "n_files"? : Int }

    schedule_task

    fn schedule_task(desc : String, n_files? : Int) -> Json

    核心调度函数:根据描述长度、文件数、风险词计算分级与参数。

    输入: desc 任务描述文本 n_files 涉及文件数(可选,默认 1) 返回 JSON: { complexity, n_split, depth, parallel, cost_tier, hold, reason }

    scope_permissions

    let scope_permissions : Array[PermissionRule]

    项目级"谁能改哪个目录"的宪法——所有角色向上开放,向下封锁。 Executor(士兵)只能改测试/守卫门脚本/文档/流水账; Leader(军官)再加 src/router/engine/display + cli/moon.pkg; HumanSteward(统帅)独占 store_sqlite.mbt、server.mbt、moon.mod。

    strict_challenge_denial

    fn strict_challenge_denial(role_source : String, action : String) -> String?

    BUG-476:strict 模式下「无挑战 ⇒ 一律拒绝」的门层判定。

    返回 Some(错误文案) ⇔ 必须拒;None ⇔ 放行(由门继续走原有逻辑)。

    判据是 role_source == "challenge_missing" —— 该值只在 Strict 分支产生 (见 judged_role_gated),soft 产生的是 challenge_required, 所以本判定天然区分 soft 与 strict,不需要再读一次 env。

    strip_plan_annotations

    fn strip_plan_annotations(s : String) -> String

    去掉「拆解引擎自己追加」的计划标注,只留任务本体描述(R113)。 omega_clean_slice 只洗强验证标记,而 gradient / reinject_context / boundary_probe / spec 指纹四项同样会在递归下钻时被逐层重喂(child.description 当作下一层的 parent_desc,且又被嵌进「更简单变体」引用与「父计划」字段),实测 2 层拆分叶子 1996 字节、根部仅 12 字节,并出现同一叶两个互相矛盾的梯度档。 逐标记顺序截断 = 截在最靠前的那个标记之前,因此对已叠加多层的文本也能一次洗净。 标记面来自 plan_markers()(单一真源)。

    verify_challenge_sig

    fn verify_challenge_sig(pubkey_hex : String, msg : String, sig_hex : String) -> Bool

    S3:验签(纯函数)。

    pubkey_hex = Ed25519 公钥的 hex(64 字符 = 32 字节); sig_hex = 签名的 hex(128 字符 = 64 字节); msg = 被签的消息(通常 = nonce)。

    hex 解码复用 @mooncry.hex_to_bytes_or(与 src/router/star_gate.mbt:112 同一个函数)。 验签复用 @mooncry.ed25519_verify(与 star_gate.mbt:301 同一个函数, 本仓已在 Star 门生产使用)。

    任何一步不合法(hex 坏、公钥非 32 字节、签名非 64 字节、验签失败)⇒ false。 fail-closed:宁可拒绝,绝不在格式错误时放行。