dsh-vibe-math 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -223,6 +223,9 @@ const spawnsFor = (root) => spawns.filter(s => s.rootId === root.id)
223
223
 
224
224
  let votePlan = new Map() // memberId -> verdict number for the next verify prompts
225
225
  let replyOverride = new Map() // memberId -> the exact reply its NEXT wake must produce
226
+ // Roots whose MEETING prompts the driver must NOT answer, so the meeting stays in flight
227
+ // (case 10b needs a live meeting to test that a verification cannot preempt it).
228
+ const hushed = new Set()
226
229
  // Handle queued sends. `delivered` collects what was actually sent for the case under
227
230
  // test, because the queue is consumed here and assertions must not read it afterwards.
228
231
  // Wakes belonging to OTHER roots are skipped over rather than allowed to block: a case
@@ -231,7 +234,8 @@ let replyOverride = new Map() // memberId -> the exact reply its NEXT wake mus
231
234
  async function drainWakes(budget, root) {
232
235
  let n = 0
233
236
  while (n < budget) {
234
- const idx = wakes.findIndex(w => !root || w.rootId === root.id)
237
+ const idx = wakes.findIndex(w => (!root || w.rootId === root.id)
238
+ && !(hushed.has(w.rootId) && /【研究所会议/.test(w.prompt)))
235
239
  if (idx === -1) break
236
240
  const w = wakes.splice(idx, 1)[0]
237
241
  const owner = memberOfChild(w.childId)
@@ -793,6 +797,44 @@ assert(/【研究所·致全体表决者 from r-1】[^\n]*提议开会/.test(pro
793
797
  'the meeting proposal is relayed SIGNED BY ITS TRUE PROPOSER r-1, not by whoever was woken last')
794
798
  await endCase(RI)
795
799
 
800
+ // =============== CASE 10b: meetings and verifications are mutually exclusive ======
801
+ section('10b a verification proposed DURING a meeting must queue, never preempt it')
802
+ const RN = makeRoot()
803
+ await callTool('vibe_v5_start', { problem: '会议与验证互斥测试', researcherCount: 1 }, RN)
804
+ for (const sp of spawnsFor(RN)) { fireEnd(sp.childId, { progress: memberOfChild(sp.childId) + ':初始见解。', solved: false, contextPct: 10 }); await settle() }
805
+ await settleInstitute(RN)
806
+ await callTool('vibe_v5_set', { maxParallel: 8 }, RN)
807
+ await callTool('vibe_v5_record_proposition', { id: 'p-mid', statement: '会议期间提出的对象', value: 0.6, motive: 'm', p: 0.7 }, childAgent(childOf(RN, 'r-1')))
808
+ // Convene a meeting and stop before it has collected every input, so it stays in flight.
809
+ hushed.add(RN.id)
810
+ delivered.length = 0
811
+ const convened = await callTool('vibe_v5_meeting', { agenda: '先开这个会', kind: 'sync' }, childAgent(childOf(RN, 'acad')))
812
+ assert(convened.ok === true, 'a meeting was convened (' + JSON.stringify(convened).slice(0, 80) + ')')
813
+ await settle(); await drainWakes(1, RN)
814
+ const during = await callTool('vibe_v5_status', {}, RN)
815
+ assert(!!during.meeting, 'the meeting is still in flight (not everyone has spoken)')
816
+ // A member proposing a verification mid-meeting must NOT start a second, concurrent
817
+ // consensus process: the design says meetings and verifications never overlap, and a
818
+ // verification that preempts a meeting starves the meeting's watchdog clock.
819
+ const propMid = await callTool('vibe_v5_propose_verify', { target: 'p-mid', kind: 'proposition', reason: '想在会上定' }, childAgent(childOf(RN, 'r-1')))
820
+ assert(propMid.ok === true, 'the proposal is accepted (' + JSON.stringify(propMid).slice(0, 90) + ')')
821
+ await settle()
822
+ const afterProp = await callTool('vibe_v5_status', {}, RN)
823
+ assert(!!afterProp.meeting, 'the meeting is STILL in flight after the proposal')
824
+ assert(afterProp.verify === null,
825
+ 'NO verification started while the meeting was in flight (got ' + JSON.stringify(afterProp.verify && afterProp.verify.target) + ')')
826
+ assert((afterProp.verifyQueue || []).indexOf('p-mid') !== -1,
827
+ 'the proposal is QUEUED instead (queue=' + JSON.stringify(afterProp.verifyQueue) + ')')
828
+ // Once the meeting ends, the queued proposal must run — queueing must not drop it.
829
+ hushed.delete(RN.id)
830
+ await settleInstitute(RN)
831
+ const afterMtg = await callTool('vibe_v5_status', {}, RN)
832
+ assert(afterMtg.meeting === null, 'the meeting finished')
833
+ assert(afterMtg.verify !== null || afterMtg.undecided.indexOf('p-mid') !== -1 || afterMtg.verified.indexOf('p-mid') !== -1,
834
+ 'the queued proposal was started after the meeting ended (verify=' + JSON.stringify(afterMtg.verify && afterMtg.verify.target)
835
+ + ', queue=' + JSON.stringify(afterMtg.verifyQueue) + ')')
836
+ await endCase(RN)
837
+
796
838
  // =============== CASE 11: no unpaced re-wake loop ===============================
797
839
  section('11 a task owner is pushed on a PACED cadence, not in a tight loop')
798
840
  const RJ = makeRoot()
@@ -2254,6 +2254,16 @@ export function apply(ctx) {
2254
2254
  let beginLock = false
2255
2255
  async function armNextVerify() {
2256
2256
  dbg.arm += 1
2257
+ // A MEETING and a VERIFICATION never run at the same time. `startMeeting` already
2258
+ // parks a meeting while a verification is in flight; this is the missing mirror for
2259
+ // the other direction. Without it, a member replying `propose_verify` while a
2260
+ // meeting was live started a second consensus process immediately, because
2261
+ // `maybeQueueVerify` calls this directly (bypassing schedulePass, whose meeting
2262
+ // check is what used to hide the asymmetry). The meeting's watchdog clock would
2263
+ // then be starved while two coordination processes competed for the same members.
2264
+ // The proposal stays in the queue; schedulePass reaches this again once the
2265
+ // meeting is over.
2266
+ if (meeting) return
2257
2267
  // Only one begin may be in flight. Without this, two callers (a scheduling pass
2258
2268
  // and a fresh proposal) could both pass the `currentVerify()` check before either
2259
2269
  // has published its verdict record and would start the SAME object twice.
@@ -898,6 +898,20 @@ pass,而该 pass 又立刻唤醒同一个任务所有者 —— 一个未完
898
898
  节流后,⑥ 成为兜底:空闲窗口到期时,**拥有进行中任务的成员收到的是常规工作轮**
899
899
  (其状态块里写着"我的任务"),而不是泛泛的心跳轮。
900
900
 
901
+ **① / ② 的相对顺序不可观测,但"互斥"必须双向成立。** 上面的列表把"进行中的验证"写在
902
+ "进行中的会议"之前,代码里则是先看会议再看验证 —— 这**没有差别**,因为两者**永不同时存在**。
903
+ 关键是这条互斥必须**两个方向都堵住**:
904
+
905
+ - 会议侧:`startMeeting` 在 `hasVerifyInFlight()` 时把会议**暂存**(pendingMeeting);
906
+ - 验证侧:`armNextVerify` 在 `meeting` 非空时**直接返回**,让提议留在队列里,
907
+ 等会议收口后由下一次 `schedulePass` 启动。
908
+
909
+ 只做前一半是不够的:`maybeQueueVerify` **直接**调用 `armNextVerify`(绕过了 `schedulePass`
910
+ 的会议检查),所以成员在会议进行中回执 `propose_verify` 时,第二个共识过程会**真的并发启动**,
911
+ 进而与会议争夺同一批成员、饿死会议的看门狗时钟。
912
+ (这个不对称是在为 v5 画架构图、被迫把"会议与验证互斥"写成明确的不变式之后,
913
+ 拿它去逐条对代码才暴露出来的 —— 见 `../AUDIT-CHECKLIST.md` §3。)
914
+
901
915
  与 v4 的实质差别:v4 靠 `activityTimeoutMs` 心跳**轮询**;v5 的主驱动是 **M1 的一次性活动等待**(DSH 官方机制),心跳降级为兜底,token 消耗显著下降。
902
916
 
903
917
  **永不永久停死**(v4 §25 的血泪教训):任何一次唤醒失败都必须**重新武装**调度(`armHeartbeat()`);`scheduleNext` 心跳分支失败后不得无条件 `return`。
@@ -0,0 +1,369 @@
1
+ # Vibe Math V5 架构图
2
+
3
+ > 本文件是 v5 的图源(GitHub 上 Mermaid 会**原生渲染**)。总览大图另有一张生成好的
4
+ > **SVG**:[`../示例图/框架图-v5.svg`](../示例图/框架图-v5.svg),由
5
+ > [`../docs/generate_framework_diagram_v5.mjs`](../docs/generate_framework_diagram_v5.mjs) 生成
6
+ > (零依赖,`node docs/generate_framework_diagram_v5.mjs`)。
7
+ > 文字规格见 [`实现方案.md`](实现方案.md)。
8
+
9
+ **一句话**:v5 = **一个所办**(对外接口,不研究不投票)+ **一所研究所**(院士 / 常驻研究员 / 临时工)
10
+ + **一个只做媒介的框架**(中继 · 沉淀 · 计数 · 调度)。
11
+
12
+ ---
13
+
14
+ ## 1. 总览:三层 + 两个面
15
+
16
+ ```mermaid
17
+ flowchart TB
18
+ subgraph L0["👤 所办(会话根代理 / 人)—— 对外接口"]
19
+ OFF["自然语言接口 + 汇报者<br/>不研究 · 不投票<br/>只汇报、转达人的指令<br/>代持平台要求的一次性创建权"]
20
+ end
21
+
22
+ subgraph L1["🏛️ 研究所(所内自治:编制、组织与分派都在成员之间完成)"]
23
+ ACAD["院士 acad<br/>领头人 · 组织与协调中心<br/>L1 全所视图 / L2 分派 / L3 优先级<br/>L4 主持会议 / L5 督导 / L6 调人 / L7 对外"]
24
+ RES["常驻研究员 r-n(有表决权)<br/>continuable 持久会话 · 自主方向<br/>可自主雇佣/解雇自己的临时工"]
25
+ TMP["临时工 t-n(无表决权)<br/>为特定任务临时雇入<br/>可读/可想/可发言/可写自己的库"]
26
+ end
27
+
28
+ subgraph L2["⚙️ 框架 vibe-v5 —— 只是媒介"]
29
+ MSG["消息中继(持久邮箱)<br/>群聊 / 私信 / 致全体表决者<br/>所办通知 / 分派 / 督办 / 框架提示"]
30
+ MTG["会议 / 辩论<br/>议程 · 名册对账 · 随机发言序<br/>与验证互斥 · 看门狗"]
31
+ TASK["任务板(CAS + DAG)<br/>版本号比较交换 · 依赖环检测"]
32
+ VOTE["m 票共识验证<br/>独立初评 → 公开辩论"]
33
+ CTX["上下文与活性<br/>阈值压缩 · 免轮询活动等待 · 心跳"]
34
+ STAFF["编制与雇佣<br/>真实解雇(释放子会话)"]
35
+ SCHED["调度器<br/>优先级 + 并发闸 maxParallel"]
36
+ end
37
+
38
+ subgraph L3["💾 状态与产物"]
39
+ PROJ["会话日志的 host-only 投影单元<br/>键 vibeMathV5<br/>11 类事件 · 纯折叠 applyV5Event"]
40
+ FS["人可读产物<br/>Members/&lt;id&gt;/* · Shared/* · Verified/ · Problems/"]
41
+ end
42
+
43
+ RULE{{"求真门槛<br/>布尔一致 且 布尔票 ≥ m = min(quorumCap, 有表决权人数)<br/>否则留库 + 平均概率 + 辩论录"}}
44
+
45
+ OFF -->|"vibe_v5_* 工具 / /v5 命令"| SCHED
46
+ SCHED -->|"status / report(自然语言汇报)"| OFF
47
+ SCHED -->|"每轮提示词:状态块 + 本轮问句"| ACAD
48
+ SCHED -->|"每轮提示词:状态块 + 本轮问句"| RES
49
+ SCHED -->|"每轮提示词:状态块 + 本轮问句"| TMP
50
+ ACAD -->|"JSON 回执"| SCHED
51
+ RES -->|"JSON 回执"| SCHED
52
+ TMP -->|"JSON 回执"| SCHED
53
+ ACAD -.->|"所内组织:分派 / 优先级 / 督办(虚线 = 不由框架执行)"| RES
54
+ ACAD -.->|"调配"| TMP
55
+ MSG --- SCHED
56
+ MTG --- SCHED
57
+ TASK --- SCHED
58
+ VOTE --- SCHED
59
+ CTX --- SCHED
60
+ STAFF --- SCHED
61
+ SCHED <-->|"append 事件 / 折叠读状态"| PROJ
62
+ SCHED <-->|"读写产物"| FS
63
+ VOTE --> RULE
64
+ RULE -->|"定论"| FS
65
+
66
+ classDef human fill:#f1eefc,stroke:#5b4bb8,stroke-width:1.5px,color:#2b2359
67
+ classDef house fill:#fdf5e2,stroke:#b26a00,stroke-width:1.5px,color:#6b4200
68
+ classDef frame fill:#eaf3fd,stroke:#0b6fb8,stroke-width:1.5px,color:#084d80
69
+ classDef data fill:#eef7f1,stroke:#1f7a52,stroke-width:1.5px,color:#125a3c
70
+ classDef gate fill:#fdeef0,stroke:#b3202c,stroke-width:2px,color:#7d1620
71
+ class OFF human
72
+ class ACAD,RES,TMP house
73
+ class MSG,MTG,TASK,VOTE,CTX,STAFF,SCHED frame
74
+ class PROJ,FS data
75
+ class RULE gate
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 2. 成员生命周期
81
+
82
+ ```mermaid
83
+ stateDiagram-v2
84
+ direction LR
85
+ [*] --> provisioning : newMember(分配代号,代号永不复用)
86
+ provisioning --> active : spawnMember 成功<br/>先落盘编制、再构造入职提示词
87
+ provisioning --> failed : startContinuable 抛错
88
+ active --> active : 一轮结束(subagent/end)→ 回执 → 调度
89
+ active --> dismissed : fire(真实释放子会话 + 收回任务 + 丢弃邮件)
90
+ dismissed --> [*]
91
+ failed --> [*] : 在 [未就位] 里对全所可见
92
+
93
+ note right of active
94
+ 每轮:框架发「状态块 + 本轮问句」,
95
+ 成员回一个 JSON(say/progress/record/
96
+ verdict/task_*/hire/fire/reject_assign/…)
97
+ end note
98
+ note right of dismissed
99
+ 未完成的任务自动释放回板;
100
+ 代号永不复用,重新雇佣会得到新代号
101
+ end note
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 3. 一轮唤醒的时序(唯一的推进驱动)
107
+
108
+ ```mermaid
109
+ sequenceDiagram
110
+ autonumber
111
+ participant S as 调度器(框架代码)
112
+ participant M as 成员(continuable 子会话)
113
+ participant L as 会话日志投影(vibeMathV5)
114
+
115
+ S->>S: schedulePass:按优先级挑一个成员
116
+ S->>S: 先 ack 收件箱,再构造本轮提示词
117
+ S->>M: 【轮次】+ 状态块(编制/m/我的任务/新消息)+ 回执契约
118
+ M->>M: 读同事的库 · 推导 · 写自己的 Progress/ 与卡片
119
+ M-->>S: subagent/end + 单个 JSON 回执
120
+ S->>L: append 事件(message / progress / verdict / …)
121
+ S->>S: handleReply:发言 / 入库 / 任务 / 提议 / 投票 / 雇佣
122
+ S->>S: 记 contextPct;必要时发压缩指令
123
+ S->>S: scheduleNext()(蹦床:同一时刻只有一个调度 pass)
124
+ ```
125
+
126
+ > 关键点:**成员的一轮结束是唯一的推进驱动**。没有轮询;`vibe_v5_wait` 是一次性活动等待,
127
+ > 心跳只是最后的兜底,且每次唤醒后**永远重新武装**,所以调度器不会永久冻结。
128
+
129
+ ---
130
+
131
+ ## 4. 共识验证状态机(m 票布尔一致)
132
+
133
+ ```mermaid
134
+ stateDiagram-v2
135
+ [*] --> queued : 任何成员 propose_verify
136
+ queued --> initial : armNextVerify(会议进行中则排队等待)
137
+ initial --> debate : 所有表决者都答完<br/>但未定论且轮次 < verdictMaxRounds
138
+ debate --> debate : 重新投票(history 存上一轮)
139
+ initial --> true_ : 布尔票 ≥ m 且全为 1
140
+ initial --> false_ : 布尔票 ≥ m 且全为 0
141
+ debate --> true_
142
+ debate --> false_
143
+ initial --> undecided : 轮次用尽,或布尔票不足 m,或出现 1 与 0 分歧
144
+ debate --> undecided
145
+ true_ --> [*] : 写 Verified/卡 + 改写来源卡 + 辩论录
146
+ false_ --> [*] : 同上(结论为假)
147
+ undecided --> [*] : 留原库 + 全组平均概率 + 完整辩论录(不强行裁决)
148
+
149
+ note right of initial
150
+ 票是 [0,1] 的数值:
151
+ 恰好 1 = 断言为真;恰好 0 = 断言为假;
152
+ 严格介于两者 = 弃权/存疑
153
+ (不计入 m,但计入平均)
154
+ end note
155
+ note left of undecided
156
+ 看门狗:超过 2×activityTimeoutMs
157
+ 没有新票 → 放弃该验证并回到自组织
158
+ end note
159
+ ```
160
+
161
+ **判定规则(`judgeVerdict`)**:任何一张反向布尔票都**阻塞**定论 —— 少数派无法靠别人弃权
162
+ 把结论推过去。`quorumMode: "all-unanimous"` 可切回 v4 的"全体有表决权者一致"口径。
163
+
164
+ ---
165
+
166
+ ## 5. 会议流程(与验证互斥)
167
+
168
+ ```mermaid
169
+ flowchart TB
170
+ A["任何人 vibe_v5_meeting"] --> B{"院士或所办?"}
171
+ B -->|"否(含临时工)"| C["只能提议:框架转呈全体表决者<br/>署名 = 真实提议人"]
172
+ B -->|"是"| D{"已有验证进行中?"}
173
+ D -->|"是"| E["暂存 pendingMeeting<br/>(只保留第一条,后到者不覆盖)"]
174
+ D -->|"否"| F["真正召开:写纪要头 + 公告群聊"]
175
+ E -->|"验证清空后"| F
176
+ F --> G["按随机顺序逐个唤醒发言<br/>每人看到此前已发言者的意见"]
177
+ G --> H{"全部在册成员都发言了?"}
178
+ H -->|"否"| I["继续收集(跳过仍在忙的人)<br/>看门狗:2×activityTimeoutMs 无新发言 → 放弃"]
179
+ I --> H
180
+ H -->|"是"| J["收口:汇总表决 / 讨论结论<br/>检查是否全体认为已解决"]
181
+ J --> K["暂存会议补开(若有)"]
182
+ ```
183
+
184
+ **互斥的双向保证**(v2.2.2 起对称):
185
+ - `startMeeting`:验证进行中 → **会议暂存**;
186
+ - `armNextVerify`:会议进行中 → **验证排队**(此前只做了前一半,成员在会上提议验证会真的
187
+ 并发启动第二个共识过程,进而饿死会议的看门狗时钟)。
188
+
189
+ ---
190
+
191
+ ## 6. 调度优先级(`schedulePass`)
192
+
193
+ ```mermaid
194
+ flowchart TB
195
+ S["一轮结束 / 提议 / 定时器到期 → scheduleNext()(蹦床:只有一个 pass 在跑)"] --> P1
196
+ P1{"有进行中的会议?"} -->|"是"| M["continueMeetingRound()<br/>缺人则继续问;齐了就收口"]
197
+ P1 -->|"否"| P2{"有进行中的验证?"}
198
+ P2 -->|"是"| V["continueVerifyRound()<br/>缺票则继续问;齐了就裁定"]
199
+ P2 -->|"否"| P3["armNextVerify():启动队列里的下一个验证"]
200
+ P3 --> P4{"有暂存会议?"}
201
+ P4 -->|"是"| P5["beginMeeting()"]
202
+ P4 -->|"否"| P6["按 maxParallel 填并发预算:<br/>① 已认领/被分派的在办任务(按 activityTimeoutMs 节流)<br/>② 加急邮件(私信/分派/督办/框架提示)<br/>③ 到期群聊摘要"]
203
+ P6 --> P7{"本所停滞超过 stallAutoMeetingMs<br/>且无人在跑?"}
204
+ P7 -->|"是"| P8["自动召集一次同步会议(只促成,不指派)"]
205
+ P7 -->|"否"| P9{"最久未动者已空闲 ≥ activityTimeoutMs?"}
206
+ P9 -->|"是"| P10["推一把:有在办任务 → 常规工作轮;否则 → 心跳轮"]
207
+ P9 -->|"否"| P11["armDigest() + armHeartbeat()"]
208
+ ```
209
+
210
+ ---
211
+
212
+ ## 7. 状态:纯折叠 + 双后端
213
+
214
+ ```mermaid
215
+ flowchart LR
216
+ subgraph EV["11 类事件"]
217
+ E["institute · member · task · message · delivered ·<br/>meeting · debate · verdict · queue · counters · progress"]
218
+ end
219
+ EV --> FOLD["applyV5Event(纯函数折叠,只此一份)"]
220
+ FOLD --> ST["V5State<br/>{ institutes, order, diagnostics }"]
221
+ ST --> R1["读:stateOf(session, 'vibeMathV5')"]
222
+ ST --> R2["未知/损坏事件 → 跳过 + 记入 diagnostics<br/>(可用性优先,绝不因一条坏事件卡死全场)"]
223
+
224
+ subgraph B1["主后端(宿主有 sessionProjections)"]
225
+ P1["事件只追加进会话日志<br/>不进模型上下文 = 零 token 成本"]
226
+ P2["checkpoint 由 DSH 负责;restore 时<br/>从快照 + 日志尾部重新折叠"]
227
+ end
228
+ subgraph B2["回退后端(宿主没有该服务)"]
229
+ F1["加固 JSON:State/&lt;institute&gt;.v5state.json<br/>同一份折叠 · 串行写 · 写前必 load"]
230
+ end
231
+ R1 --> B1
232
+ R1 --> B2
233
+
234
+ style FOLD fill:#eaf3fd,stroke:#0b6fb8
235
+ style P1 fill:#eef7f1,stroke:#1f7a52
236
+ style P2 fill:#eef7f1,stroke:#1f7a52
237
+ style F1 fill:#fdf5e2,stroke:#b26a00
238
+ ```
239
+
240
+ **为什么用投影**:v4 的 `State/*.json` 直写带来"损坏静默覆盖 / 并发丢写 / 跨进程陈旧快照"一整类问题。
241
+ v5 的副作用只是往会话日志追加事件,跨进程与同进程恢复**走同一条代码路径**。
242
+
243
+ ---
244
+
245
+ ## 8. 提示词是怎么构成的(成员读到的文字 = 产品)
246
+
247
+ ```mermaid
248
+ flowchart LR
249
+ subgraph P["人设 persona(入职时冻结,随会话持久化,压缩后依然有效)"]
250
+ CH["公共规章(十节)<br/>编制与同事 · 通用规章 · 资料库与 progress<br/>组织与协调 · 表决规则 · 每轮节奏<br/>雇佣解雇 · 任务板 · 上下文纪律 · 停止条件"]
251
+ end
252
+ subgraph R["每轮提示词(临时,短)"]
253
+ ST["状态块<br/>&#91;状态&#93; 你是 &lt;代号&gt;(职位)|轮次|m|表决者数<br/>&#91;在册&#93; … &#91;未就位&#93; …<br/>&#91;任务板&#93; … &#91;新到的消息/通知&#93; …"]
254
+ ASK["本轮问句(常规 / 心跳 / 会议 / 表决)<br/>+ 收件箱(先 ack 再构造,绝不重复投递)"]
255
+ SPEC["回执契约(按职位裁剪)<br/>say / progress / record / propose_verify / verdict<br/>propose_meeting / task_* / reject_assign / input<br/>hire / fire / vote_solved / solved / contextPct / compacted"]
256
+ end
257
+ P --> MEM["成员(子会话)"]
258
+ R --> MEM
259
+ MEM --> OUT["单个 JSON 回执"]
260
+
261
+ style CH fill:#fdf5e2,stroke:#b26a00
262
+ style ST fill:#eaf3fd,stroke:#0b6fb8
263
+ style ASK fill:#eaf3fd,stroke:#0b6fb8
264
+ style SPEC fill:#eaf3fd,stroke:#0b6fb8
265
+ ```
266
+
267
+ **身份不变式(有测试与语料保证)**:
268
+
269
+ - 提示词里的身份 = 这条提示词**实际发给的成员**;题头、`[状态]`、人设资料库路径三者必须一致;
270
+ - 成员**先落盘进编制、再构造**它的入职提示词(否则会漏掉自己、并按加入前的编制算 m);
271
+ - 章程快照**冻结在入职时**(它写着"你入职时的在册编制"),重建会话时原样复用;
272
+ - 会话重建框为 `【会话重建】`,**不**自称"刚入职";
273
+ - 领袖叙事跟随真实编制:没有院士时,章程不得声称存在院士或承诺其派活;
274
+ - 框头按**真实来源**标注(所办分派 ≠ 院士分派;督办 ≠ 分派;致全体表决者 ≠ 私信);
275
+ - 框架反馈有独立发送者(`【框架提示】`),且**一次提示词只投递一条消息**。
276
+
277
+ ---
278
+
279
+ ## 9. 任务板(compare-and-set + DAG)
280
+
281
+ ```mermaid
282
+ flowchart LR
283
+ A["task_create<br/>subject / description / blocked_by / write_scopes / priority"] --> B["pending"]
284
+ B -->|"task_claim(全部依赖已完成才可认领)"| C["in_progress"]
285
+ C -->|"complete"| D["completed"]
286
+ C -->|"release"| B
287
+ D -->|"reopen"| B
288
+ B -->|"delete(有依赖者则拒绝)"| E["deleted"]
289
+ C -->|"reassign(仅院士/所办)"| C
290
+ F["任何修改都要带 expected_revision<br/>版本不符 → V5_TASK_STALE_REVISION"] -.-> A
291
+ G["owner 被解雇 → releaseTasksOf 自动收回"] -.-> B
292
+
293
+ style B fill:#eaf3fd,stroke:#0b6fb8
294
+ style C fill:#fdf5e2,stroke:#b26a00
295
+ style D fill:#eef7f1,stroke:#1f7a52
296
+ ```
297
+
298
+ ---
299
+
300
+ ## 10. 职权矩阵
301
+
302
+ | 动作 | 院士 | 常驻研究员 | 临时工 | 所办 |
303
+ |---|---|---|---|---|
304
+ | 发言 / 私信 / 致全体表决者 | ✅ | ✅ | ✅ | ✅(所办通知) |
305
+ | 写自己的成果库 / 认领任务 | ✅ | ✅ | ✅ | ❌(不是成员) |
306
+ | **表决**(进 `Verified/`) | ✅ 一票 | ✅ 一票 | ❌ | ❌ |
307
+ | 提议验证 / 提议开会 | ✅ | ✅ | ✅ | ✅(可直接召开) |
308
+ | **直接召开会议** | ✅ | ❌(只能提议) | ❌ | ✅ |
309
+ | **分派任务**(`vibe_v5_assign`) | ✅ | ❌ | ❌ | ✅ |
310
+ | 设定优先级(`vibe_v5_prioritize`) | ✅ | ❌ | ❌ | ✅ |
311
+ | 督导(`vibe_v5_nudge`) | ✅ | ❌ | ❌ | ✅ |
312
+ | **雇佣临时工** | ✅ | ✅(自己的) | ❌ | ✅ |
313
+ | **解雇临时工** | ✅(任何) | ✅(自己雇的) | ❌ | ✅ |
314
+ | 增聘 / 解聘**常驻研究员** | ❌(建议) | ❌(建议) | ❌ | ✅ |
315
+ | 暂停 / 恢复 / 停止 / 调参 | ❌ | ❌ | ❌ | ✅ |
316
+
317
+ > **分派不改变真假**:院士能分派工作、定优先级,但不能让任何断言因此变正确 ——
318
+ > 对象进 `Verified/` 仍然只能靠 m 票布尔一致。
319
+
320
+ ---
321
+
322
+ ## 11. 目录结构与制品
323
+
324
+ ```text
325
+ <会话工作区>/VibeMath/Projects/<项目>/Institutes/<研究所>/
326
+ ├─ Institutes.md # 编制镜像(人读快照,勿手改)
327
+ ├─ Problems/<id>.md # 原问题
328
+ ├─ Problems/conclusion.md # 结题记录(全体有表决权者一致认为已解决时生成)
329
+ ├─ Members/<代号>/
330
+ │ ├─ Progress/progress.md # 研究日志(叙述体,可追加;压缩后恢复状态的主要依据)
331
+ │ ├─ Propos/<id>.md # 命题
332
+ │ ├─ Methods/<id>.md # 方法 / 理论 / 工具
333
+ │ └─ Subproblems/<id>.md # 子问题
334
+ ├─ Shared/
335
+ │ ├─ Chat/<日期>.md # 群聊记录
336
+ │ ├─ Meetings/<mt-id>.md # 会议纪要(含表决小节)
337
+ │ ├─ Debates/<对象>.md # 辩论录(各轮票与理由 + 平均概率)
338
+ │ ├─ TaskBoard.md # 任务板镜像
339
+ │ └─ State-of-institute.md # 各成员对"是否已解决"的判断快照
340
+ ├─ Verified/<类型>/<id>.md # 定论(只读;只有它能被当作已确立)
341
+ └─ State/
342
+ ├─ README.md # 说明"权威状态在会话日志投影里,不是这里"
343
+ └─ <研究所>.v5state.json # 仅当宿主缺 sessionProjections 时的回退权威源
344
+ ```
345
+
346
+ **可信分层**:只有 `Verified/` 与标注"已验证·真/假"的卡片**绝对可信**;
347
+ `Progress/`、`Methods/`、未定论命题、他人推测都只是**经验参考**,引用时必须注明"未验证"。
348
+
349
+ ---
350
+
351
+ ## 12. 不变式速查(这些都有测试守着)
352
+
353
+ | 不变式 | 由谁保证 |
354
+ |---|---|
355
+ | 提示词身份 = 收件人;题头/状态块/人设三者一致 | `prompt-v5-integrity.test.mjs`(逐条断言 + 语料) |
356
+ | 成员先落盘进编制、再构造入职提示词 | 同上 |
357
+ | 章程冻结在入职时;重建会话自称"重建" | 同上 |
358
+ | 无院士时不出现任何院士叙事 | 同上 |
359
+ | 框头署名 = 真实来源 | 同上 |
360
+ | 一次提示词不重复投递同一条消息 | 同上 |
361
+ | 回执契约涵盖框架真正处理的每个字段 | 同上 |
362
+ | 只有 ≥ m 张一致的布尔票才能定论 | `selfdrive-v5.mjs` / `e2e-v5-round2.test.mjs` |
363
+ | 反向布尔票阻塞结论 | 同上 |
364
+ | 临时工无表决权、不能雇佣 | 同上 |
365
+ | 只有院士/所办能分派、定优先级、督办 | 同上 |
366
+ | 会议与验证互斥(双向) | `prompt-v5-integrity.test.mjs` |
367
+ | 解雇是真实的(释放子会话、收回任务) | `selfdrive-v5.mjs` |
368
+ | 跨进程恢复不丢研究所 | `e2e-v5-round2.test.mjs` |
369
+ | 每条不变式都有能让套件变红的探针 | `audit-v5-sensitivity.mjs` + [`../AUDIT-CHECKLIST.md`](../AUDIT-CHECKLIST.md) |