@seasonkoh/webaz 0.1.16 → 0.1.18

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.
Files changed (39) hide show
  1. package/README.md +60 -5
  2. package/dist/layer0-foundation/L0-2-state-machine/engine.js +3 -0
  3. package/dist/layer1-agent/L1-1-mcp-server/server.js +899 -720
  4. package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +287 -0
  5. package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +102 -0
  6. package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +180 -0
  7. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +16 -0
  8. package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +1 -0
  9. package/dist/mcp.js +7 -3
  10. package/dist/pwa/data/onboarding-cases.js +345 -0
  11. package/dist/pwa/data/onboarding-quiz.js +247 -0
  12. package/dist/pwa/public/app.js +1459 -96
  13. package/dist/pwa/public/i18n.js +303 -2
  14. package/dist/pwa/public/icon-192.png +0 -0
  15. package/dist/pwa/public/icon-512.png +0 -0
  16. package/dist/pwa/public/manifest.json +5 -2
  17. package/dist/pwa/public/openapi.json +1 -1
  18. package/dist/pwa/public/sw.js +1 -1
  19. package/dist/pwa/routes/admin-protocol-params.js +80 -2
  20. package/dist/pwa/routes/admin-reports.js +14 -9
  21. package/dist/pwa/routes/auth-read.js +3 -1
  22. package/dist/pwa/routes/build-feedback.js +82 -0
  23. package/dist/pwa/routes/build-reputation.js +10 -0
  24. package/dist/pwa/routes/build-tasks.js +73 -0
  25. package/dist/pwa/routes/disputes-write.js +149 -1
  26. package/dist/pwa/routes/governance-auto-deactivate.js +108 -0
  27. package/dist/pwa/routes/governance-onboarding.js +785 -0
  28. package/dist/pwa/routes/leaderboard.js +10 -2
  29. package/dist/pwa/routes/orders-action.js +5 -1
  30. package/dist/pwa/routes/products-meta.js +30 -0
  31. package/dist/pwa/routes/profile-identity.js +1 -1
  32. package/dist/pwa/routes/public-utils.js +44 -0
  33. package/dist/pwa/routes/rewards-apply.js +210 -0
  34. package/dist/pwa/routes/rewards-auto-downgrade.js +65 -0
  35. package/dist/pwa/routes/rewards-escrow-expire.js +48 -0
  36. package/dist/pwa/routes/wallet-write.js +17 -31
  37. package/dist/pwa/routes/webauthn.js +1 -1
  38. package/dist/pwa/server.js +641 -64
  39. package/package.json +6 -3
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Governance Onboarding 案例库(spec §4.2)
3
+ * task #1093 阶段 2b — arbitrator 5 案例 / verifier 3 案例
4
+ *
5
+ * **Status**: v1 draft(2026-06-02,user 后期 review 调整)
6
+ *
7
+ * 设计原则:
8
+ * - 脱敏 + 简化(phase A 不从真 disputes 表抽样,初版用编造案例;后续真用户出现后可补真案例)
9
+ * - 申请者写"我会怎么判 + 理由"(≥ 200 字,spec §4.2)
10
+ * - **不立即自动评分** — maintainer 在 §4.4 上岗签字前对比实际 expected_verdict,评估 reasoning 方向
11
+ * - expected_verdict 字段仅 maintainer 可见(server 端剥离)
12
+ * - key_principles 引用 spec(arbitration PLAYBOOK 案例 / META-RULES / framework §X)
13
+ *
14
+ * 覆盖范围:
15
+ * - arbitrator: 4 种 verdict(release_seller / refund_buyer / partial_refund / liability_split)+ Case 2 物流卡顿(资金流)
16
+ * - verifier: claim 真伪(pass / fail / no_fault)
17
+ *
18
+ * Phase B+ 升级方向(留 hook):
19
+ * - 真 disputes 表抽样脱敏 fixture
20
+ * - 给 maintainer review UI:对比申请者 review 与 expected_verdict + 给评语
21
+ */
22
+ export const ONBOARDING_CASES = [
23
+ // ── arbitrator 5 案例 ─────────────────────────────────────
24
+ {
25
+ id: 'arb-1',
26
+ role_filter: 'arbitrator',
27
+ scenario_zh: '卖家发货严重延迟,无任何物流证据',
28
+ scenario_en: 'Seller severely delayed shipping with no logistics evidence',
29
+ facts_zh: [
30
+ '买家 2026-04-01 下单 100 WAZ 商品(标注 7 天内发货)',
31
+ '截至 2026-05-15(超期 38 天),卖家未发货也未沟通',
32
+ '买家提供:下单时的承诺截图 + 多次催促但卖家未回复',
33
+ '卖家提供:无任何物流单 / 沟通记录 / 备货证明',
34
+ '卖家 reputation 75(未达正常孵化阈值)',
35
+ ],
36
+ facts_en: [
37
+ 'Buyer ordered 100 WAZ product on 2026-04-01 (promised: ship within 7 days)',
38
+ 'As of 2026-05-15 (38 days overdue), seller has neither shipped nor communicated',
39
+ 'Buyer provides: order screenshot + multiple unreplied reminders',
40
+ 'Seller provides: no logistics doc / communication / inventory proof',
41
+ 'Seller reputation: 75 (below normal threshold)',
42
+ ],
43
+ decision_options: [
44
+ { key: 'release_seller', text_zh: 'release_seller(放款给卖家)', text_en: 'release_seller (release to seller)' },
45
+ { key: 'refund_buyer', text_zh: 'refund_buyer(全额退款给买家)', text_en: 'refund_buyer (full refund to buyer)' },
46
+ { key: 'partial_refund', text_zh: 'partial_refund(部分退款)', text_en: 'partial_refund (partial refund)' },
47
+ { key: 'liability_split', text_zh: 'liability_split(平摊)', text_en: 'liability_split (split liability)' },
48
+ ],
49
+ expected_verdict: 'refund_buyer',
50
+ key_principles: [
51
+ 'ARBITRATION-PLAYBOOK 4 种 verdict 适用情境',
52
+ 'META-RULES #4 不撒谎(卖家承诺 + 无履约证据)',
53
+ '举证责任在卖家(他承诺了发货)',
54
+ ],
55
+ min_review_chars: 200,
56
+ },
57
+ {
58
+ id: 'arb-2',
59
+ role_filter: 'arbitrator',
60
+ scenario_zh: 'Case 2 物流卡顿:卖家发货,买家未收到,物流方失联',
61
+ scenario_en: 'Case 2 logistics stuck: seller shipped, buyer never received, logistics gone',
62
+ facts_zh: [
63
+ '买家下单 200 WAZ 商品',
64
+ '卖家提供:有效发货单 + 物流取件签名',
65
+ '物流追踪显示"中转中"45 天无更新',
66
+ '物流方联系不上(电话停机,后台账号下线)',
67
+ '买家:未收到货,要求退款',
68
+ '物流方 stake 余额 70 WAZ(< 200 WAZ 订单价)',
69
+ ],
70
+ facts_en: [
71
+ 'Buyer ordered 200 WAZ product',
72
+ 'Seller provides: valid shipping doc + logistics pickup signature',
73
+ 'Logistics tracking shows "in transit" for 45 days, no updates',
74
+ 'Logistics party unreachable (phone off, backend offline)',
75
+ 'Buyer: never received, demands refund',
76
+ 'Logistics stake balance: 70 WAZ (< 200 WAZ order value)',
77
+ ],
78
+ decision_options: [
79
+ { key: 'release_seller', text_zh: 'release_seller', text_en: 'release_seller' },
80
+ { key: 'refund_buyer', text_zh: 'refund_buyer + 物流 stake 优先赔买家 + management_bonus_pool 兜底', text_en: 'refund_buyer + logistics stake to buyer first + management_bonus_pool covers shortfall' },
81
+ { key: 'partial_refund', text_zh: 'partial_refund 50/50', text_en: 'partial_refund 50/50' },
82
+ { key: 'liability_split', text_zh: 'liability_split(卖家一半 / 物流方一半)', text_en: 'liability_split (half seller / half logistics)' },
83
+ ],
84
+ expected_verdict: 'refund_buyer',
85
+ key_principles: [
86
+ 'ARBITRATION-PLAYBOOK Case 2 物流卡顿 — 卖家无过错应保护',
87
+ 'management_bonus_pool 来源 = ECONOMIC §3 ④a + 失效活动罚没',
88
+ '资金流向:物流 stake 优先赔买家(不入 pool),pool 仅兜底差额',
89
+ '物流方 debt_to_protocol 累计 → > 1000 WAZ 角色暂停',
90
+ ],
91
+ min_review_chars: 200,
92
+ },
93
+ {
94
+ id: 'arb-3',
95
+ role_filter: 'arbitrator',
96
+ scenario_zh: '商品描述部分不符 — 主要功能 OK 但 minor 偏差',
97
+ scenario_en: 'Product description partial mismatch — main feature OK but minor deviation',
98
+ facts_zh: [
99
+ '买家下单 50 WAZ 二手手机',
100
+ '描述标注:"95 新,无明显划痕"',
101
+ '买家收到后:功能完好,但侧面有 1 个 2cm 浅划痕',
102
+ '买家提供:开箱照片(确实有划痕)+ 描述截图(承诺无划痕)',
103
+ '卖家承认:"漏拍了那一面,无意误导"',
104
+ '订单价低(50 WAZ),非贵重物品',
105
+ ],
106
+ facts_en: [
107
+ 'Buyer ordered 50 WAZ second-hand phone',
108
+ 'Description: "95% new, no visible scratches"',
109
+ 'Buyer received: functioning, but 2cm light scratch on side',
110
+ 'Buyer provides: unboxing photo (scratch exists) + description screenshot (promised no scratches)',
111
+ 'Seller acknowledges: "missed that side, no intent to mislead"',
112
+ 'Order value low (50 WAZ), not a high-value item',
113
+ ],
114
+ decision_options: [
115
+ { key: 'release_seller', text_zh: 'release_seller(卖家承担轻微误差合理)', text_en: 'release_seller (acceptable minor deviation)' },
116
+ { key: 'refund_buyer', text_zh: 'refund_buyer(全退)', text_en: 'refund_buyer (full refund)' },
117
+ { key: 'partial_refund', text_zh: 'partial_refund(部分退款,5-15 WAZ 差异补偿)', text_en: 'partial_refund (5-15 WAZ compensation for deviation)' },
118
+ { key: 'liability_split', text_zh: 'liability_split', text_en: 'liability_split' },
119
+ ],
120
+ expected_verdict: 'partial_refund',
121
+ key_principles: [
122
+ '描述不符但功能完好 → partial_refund(差异补偿)',
123
+ 'META-RULES #4 不撒谎 — 卖家描述有瑕疵但非恶意',
124
+ '"无意误导"与"恶意欺诈"不同,处置不同',
125
+ '比例:轻微偏差 5-15% 补偿,严重偏差 30-50%',
126
+ ],
127
+ min_review_chars: 200,
128
+ },
129
+ {
130
+ id: 'arb-4',
131
+ role_filter: 'arbitrator',
132
+ scenario_zh: '双方各执一词 + 证据部分有效',
133
+ scenario_en: 'Both parties insist + partial valid evidence on both sides',
134
+ facts_zh: [
135
+ '买家下单 80 WAZ 美妆产品',
136
+ '买家:"收到时已开封,要求退款"提供开箱照(可见塑封破损)',
137
+ '卖家:"快递可能粗暴搬运,我发货时是完好的"提供发货前完好照片',
138
+ '物流方:无监控证据,无法判定途中是否破损',
139
+ '双方都 reputation 良好(无前科)',
140
+ '没有第三方目击或客观证据决定责任在哪',
141
+ ],
142
+ facts_en: [
143
+ 'Buyer ordered 80 WAZ cosmetics',
144
+ 'Buyer: "received already opened, demands refund" provides unboxing photo (visible seal damaged)',
145
+ 'Seller: "logistics may have mishandled, was intact when shipped" provides pre-ship intact photo',
146
+ 'Logistics: no surveillance evidence, cannot determine in-transit damage',
147
+ 'Both parties have good reputation (no prior incidents)',
148
+ 'No third-party witness or objective evidence to determine fault',
149
+ ],
150
+ decision_options: [
151
+ { key: 'release_seller', text_zh: 'release_seller', text_en: 'release_seller' },
152
+ { key: 'refund_buyer', text_zh: 'refund_buyer', text_en: 'refund_buyer' },
153
+ { key: 'partial_refund', text_zh: 'partial_refund', text_en: 'partial_refund' },
154
+ { key: 'liability_split', text_zh: 'liability_split(三方各承担一份)', text_en: 'liability_split (three-way split)' },
155
+ ],
156
+ expected_verdict: 'liability_split',
157
+ key_principles: [
158
+ '证据双方部分有效,无法 100% 归责 → liability_split',
159
+ 'META-RULES #5 不偏袒 — 凭证据均衡时不向任一方倾斜',
160
+ '本案应考虑物流方分摊(stake)— 即便物流方无监控,procedural 上有责任',
161
+ 'split 比例可参 stake 余额 / reputation 算',
162
+ ],
163
+ min_review_chars: 200,
164
+ },
165
+ {
166
+ id: 'arb-5',
167
+ role_filter: 'arbitrator',
168
+ scenario_zh: '买家恶意 dispute — 已退款后又申诉',
169
+ scenario_en: 'Buyer malicious dispute — re-filing after already refunded',
170
+ facts_zh: [
171
+ '买家 2026-03 下单 120 WAZ 商品',
172
+ '2026-04 因物流延迟自动退款(release_buyer)',
173
+ '商品 2026-04-20 自动签收(买家未拒收)',
174
+ '买家 2026-05 提 dispute:"我没收到货,要求再退一次"',
175
+ '物流追踪:确认 4-20 签收(签名指纹匹配买家)',
176
+ '买家 history:近 3 个月已 5 次类似 pattern dispute',
177
+ '其他 4 次中 3 次被仲裁判 fault_buyer',
178
+ ],
179
+ facts_en: [
180
+ 'Buyer ordered 120 WAZ on 2026-03',
181
+ 'Auto-refunded 2026-04 due to logistics delay (release_buyer)',
182
+ 'Product auto-signed 2026-04-20 (buyer did not reject)',
183
+ 'Buyer 2026-05 raises dispute: "never received, demands re-refund"',
184
+ 'Logistics tracking confirms 4-20 signature (fingerprint matches buyer)',
185
+ 'Buyer history: 5 similar pattern disputes in last 3 months',
186
+ 'Of other 4, 3 ruled fault_buyer by arbitration',
187
+ ],
188
+ decision_options: [
189
+ { key: 'release_seller', text_zh: 'release_seller + 标买家恶意 + 信誉惩罚', text_en: 'release_seller + mark buyer malicious + reputation penalty' },
190
+ { key: 'refund_buyer', text_zh: 'refund_buyer', text_en: 'refund_buyer' },
191
+ { key: 'partial_refund', text_zh: 'partial_refund', text_en: 'partial_refund' },
192
+ { key: 'liability_split', text_zh: 'liability_split', text_en: 'liability_split' },
193
+ ],
194
+ expected_verdict: 'release_seller',
195
+ key_principles: [
196
+ '物流签收事实 + 买家 history pattern → 买家恶意',
197
+ 'META-RULES #6 不滥用 — 系统性 dispute 是滥用',
198
+ '判决要标 buyer fault + reputation 惩罚(防再犯)',
199
+ '与正常退款情境(arb-1)对比看:有签收事实 vs 无证据',
200
+ ],
201
+ min_review_chars: 200,
202
+ },
203
+ // ── verifier 3 案例 ──────────────────────────────────────
204
+ {
205
+ id: 'ver-1',
206
+ role_filter: 'verifier',
207
+ scenario_zh: 'claim_verify:卖家声称商品 origin "Made in Japan"',
208
+ scenario_en: 'claim_verify: seller claims product origin "Made in Japan"',
209
+ facts_zh: [
210
+ '卖家上架商品标注 origin:"Made in Japan"',
211
+ '买家发起 claim verify,提供事实证据:',
212
+ ' - 收到的实物包装清晰印刷 "Made in China"',
213
+ ' - 高清照片证实底面 sticker',
214
+ '卖家未提供任何"Made in Japan"的进口证明 / 海关单',
215
+ '其他 verifier 中 1 人投 fail,1 人投 pass,需第 3 票决定',
216
+ ],
217
+ facts_en: [
218
+ 'Seller listed product origin: "Made in Japan"',
219
+ 'Buyer raised claim verify with factual evidence:',
220
+ ' - Physical product packaging clearly printed "Made in China"',
221
+ ' - HD photo confirms bottom sticker',
222
+ 'Seller provided no proof of Japan origin (no import doc / customs)',
223
+ 'Other verifiers: 1 voted fail, 1 voted pass, needs 3rd vote to decide',
224
+ ],
225
+ decision_options: [
226
+ { key: 'pass', text_zh: 'pass(claim 真实,Made in Japan)', text_en: 'pass (claim true, Made in Japan)' },
227
+ { key: 'fail', text_zh: 'fail(claim 不实,实际 Made in China)', text_en: 'fail (claim false, actually Made in China)' },
228
+ { key: 'no_fault', text_zh: 'no_fault(争议无法判定 / 中立)', text_en: 'no_fault (unable to determine / neutral)' },
229
+ ],
230
+ expected_verdict: 'fail',
231
+ key_principles: [
232
+ 'verifier 投 pass/fail/no_fault 是对 claim 真伪的判定',
233
+ '事实证据(物理实物 + 高清照片)> 卖家空口承诺',
234
+ 'META-RULES #4 不撒谎 — 描述事实必须真',
235
+ '与 arbitrator 判 dispute 不同:verifier 只判 claim 真伪,不分配赔偿',
236
+ ],
237
+ min_review_chars: 200,
238
+ },
239
+ {
240
+ id: 'ver-2',
241
+ role_filter: 'verifier',
242
+ scenario_zh: 'claim_verify:卖家声称 condition "全新",轻微 deviation',
243
+ scenario_en: 'claim_verify: seller claims condition "brand new", minor deviation',
244
+ facts_zh: [
245
+ '卖家上架商品标注 condition:"全新,未拆封"',
246
+ '买家收到后发现:外包装 完好,但内部商品有 1 个轻微指纹(可擦除)',
247
+ '买家发起 claim verify',
248
+ '卖家解释:"运输中可能 minor 接触,商品本身全新"',
249
+ '事实判定:本质是新的(未使用),但 100% "未接触" claim 不成立',
250
+ ],
251
+ facts_en: [
252
+ 'Seller listed condition: "brand new, sealed"',
253
+ 'Buyer found: outer packaging intact, but inner product has 1 minor fingerprint (erasable)',
254
+ 'Buyer raises claim verify',
255
+ 'Seller explains: "minor contact during transit possible, item itself is new"',
256
+ 'Fact: essentially new (unused), but 100% "untouched" claim not perfect',
257
+ ],
258
+ decision_options: [
259
+ { key: 'pass', text_zh: 'pass(本质全新)', text_en: 'pass (essentially new)' },
260
+ { key: 'fail', text_zh: 'fail(claim 不准)', text_en: 'fail (claim inaccurate)' },
261
+ { key: 'no_fault', text_zh: 'no_fault(轻微偏差非欺诈)', text_en: 'no_fault (minor deviation not fraud)' },
262
+ ],
263
+ expected_verdict: 'no_fault',
264
+ key_principles: [
265
+ '"全新"实质成立,但 100% 严格 claim 略偏差 — 不算 fail',
266
+ 'no_fault = 卖家无主观恶意 + 实质 claim 成立',
267
+ '与 ver-1(本质事实不符)对比:此案是轻微 deviation',
268
+ '过严判 fail 会让所有 minor 包装变化都"被欺诈"',
269
+ ],
270
+ min_review_chars: 200,
271
+ },
272
+ {
273
+ id: 'ver-3',
274
+ role_filter: 'verifier',
275
+ scenario_zh: 'claim_verify:卖家声称 warranty 30 天,实际只给 7 天',
276
+ scenario_en: 'claim_verify: seller claims 30-day warranty, only honors 7 days',
277
+ facts_zh: [
278
+ '卖家上架商品标注:warranty 30 天',
279
+ '买家收到后产品在第 20 天损坏,联系卖家要保修',
280
+ '卖家拒绝:"我的店铺政策实际只 7 天"',
281
+ '买家提供:商品页 warranty 30 天截图',
282
+ '卖家解释:"标错了,实际是 7 天"— 但商品页未改',
283
+ ],
284
+ facts_en: [
285
+ 'Seller listed: warranty 30 days',
286
+ 'Buyer\'s product broke on day 20, contacted seller for warranty',
287
+ 'Seller refuses: "my store policy is actually 7 days"',
288
+ 'Buyer provides: screenshot of 30-day warranty on product page',
289
+ 'Seller explains: "labeled wrong, actually 7 days" — but product page not updated',
290
+ ],
291
+ decision_options: [
292
+ { key: 'pass', text_zh: 'pass(卖家解释合理)', text_en: 'pass (seller explanation reasonable)' },
293
+ { key: 'fail', text_zh: 'fail(claim 不实,以商品页 30 天为准)', text_en: 'fail (claim false, product page 30 days is canonical)' },
294
+ { key: 'no_fault', text_zh: 'no_fault(标错而非欺诈)', text_en: 'no_fault (labeling error, not fraud)' },
295
+ ],
296
+ expected_verdict: 'fail',
297
+ key_principles: [
298
+ '商品页明示的 claim 是承诺,事后说"标错"不成立',
299
+ 'META-RULES #4 不撒谎 — 公示=承诺,改了 ≠ 没承诺过',
300
+ 'META-RULES #2 代码即规则 — 公开数据是 canonical truth',
301
+ '区别于 ver-2:此案 claim 与事实有实质 gap(30 vs 7),非 minor 偏差',
302
+ ],
303
+ min_review_chars: 200,
304
+ },
305
+ ];
306
+ // 给前端 GET:剥离 expected_verdict + key_principles(防泄答案,maintainer 视角才看)
307
+ export function getCasesForRole(role) {
308
+ return ONBOARDING_CASES
309
+ .filter(c => c.role_filter === role)
310
+ .map(({ expected_verdict: _expected_verdict, key_principles: _key_principles, ...rest }) => rest);
311
+ }
312
+ // 给 maintainer 看的完整数据(server 端用,validate review 时)
313
+ export function getCasesForMaintainer(role) {
314
+ return ONBOARDING_CASES.filter(c => c.role_filter === role);
315
+ }
316
+ export function validateCaseReviews(role, reviews) {
317
+ const cases = getCasesForMaintainer(role);
318
+ const requiredIds = new Set(cases.map(c => c.id));
319
+ const errors = [];
320
+ const submittedIds = new Set(reviews.map(r => r.case_id));
321
+ // 检查未提交的 case
322
+ for (const id of requiredIds) {
323
+ if (!submittedIds.has(id)) {
324
+ errors.push({ case_id: id, reason: '未提交 review' });
325
+ }
326
+ }
327
+ // 检查每个 review
328
+ for (const review of reviews) {
329
+ const c = cases.find(c => c.id === review.case_id);
330
+ if (!c) {
331
+ errors.push({ case_id: review.case_id, reason: '案例 id 不存在或不属本 role' });
332
+ continue;
333
+ }
334
+ // chosen_verdict 必须是 decision_options 之一
335
+ if (!c.decision_options.some(o => o.key === review.chosen_verdict)) {
336
+ errors.push({ case_id: review.case_id, reason: `chosen_verdict='${review.chosen_verdict}' 不在选项内` });
337
+ }
338
+ // reasoning 长度
339
+ const trimmed = (review.reasoning || '').trim();
340
+ if (trimmed.length < c.min_review_chars) {
341
+ errors.push({ case_id: review.case_id, reason: `reasoning 需 ≥ ${c.min_review_chars} 字符,当前 ${trimmed.length}` });
342
+ }
343
+ }
344
+ return { ok: errors.length === 0, errors };
345
+ }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Governance Onboarding 题库(spec §4.3)
3
+ * task #1093 阶段 2a — 10 多选题 + 5 短答题
4
+ *
5
+ * **Status**: v1 draft(2026-06-02 草稿,user 后期 review 调整)
6
+ *
7
+ * 4 个领域(per spec §4.3):
8
+ * - 元规则识别(违反哪条)
9
+ * - Iron-Rule 边界(哪些 action 必须 Passkey)
10
+ * - 4 种 dispute 结算路径(release_seller / partial_refund / liability_split / refund_buyer)
11
+ * - 反 outlier 机制(信誉惩罚原则)
12
+ *
13
+ * 合格线:80%(`protocol_params.governance_onboarding.quiz_pass_score`,默认 80)
14
+ *
15
+ * 评分逻辑:
16
+ * - 多选题:correct_answer 完全匹配 → 1 score
17
+ * - 短答题:phase A 简化 — text.length ≥ 50 chars 且非纯空白 → 1 score
18
+ * (phase B+ maintainer 人工评,可补充 case_review_text 字段)
19
+ * - 总分 = score_count / total_count(15 题)百分比
20
+ */
21
+ export const ONBOARDING_QUIZ = [
22
+ // ── 多选题 1-10 ────────────────────────────────────────────────
23
+ {
24
+ id: 'mcq-1',
25
+ type: 'multiple_choice',
26
+ domain: 'meta-rules',
27
+ question_zh: 'maintainer 在 review PR 时,看到代码作者是创始人,因此放宽审核标准。这违反哪条元规则?',
28
+ question_en: 'A maintainer relaxes review standards because the PR author is the founder. Which meta-rule does this violate?',
29
+ options: [
30
+ { key: 'A', text_zh: '#4 不撒谎', text_en: '#4 no lies' },
31
+ { key: 'B', text_zh: '#5 不偏袒', text_en: '#5 no favoritism' },
32
+ { key: 'C', text_zh: '#8 最小介入', text_en: '#8 minimal intervention' },
33
+ { key: 'D', text_zh: '#10 参与者即 webazer', text_en: '#10 participants are webazers' },
34
+ ],
35
+ correct_answer: 'B',
36
+ },
37
+ {
38
+ id: 'mcq-2',
39
+ type: 'multiple_choice',
40
+ domain: 'iron-rule',
41
+ question_zh: '下列哪个 action 必须真人 Passkey 签发?(注:提现无金额门槛,任何金额都需 Passkey)',
42
+ question_en: 'Which action must require real-human Passkey signature? (Note: withdrawal has no amount threshold for Passkey)',
43
+ options: [
44
+ { key: 'A', text_zh: '查看自己的订单', text_en: 'View own orders' },
45
+ { key: 'B', text_zh: '卖家上架商品', text_en: 'Seller lists product' },
46
+ { key: 'C', text_zh: '用户提现资金(任何金额)', text_en: 'User withdrawal (any amount)' },
47
+ { key: 'D', text_zh: '评价商品', text_en: 'Rate a product' },
48
+ ],
49
+ correct_answer: 'C',
50
+ },
51
+ {
52
+ id: 'mcq-3',
53
+ type: 'multiple_choice',
54
+ domain: 'dispute-verdict',
55
+ role_filter: 'arbitrator',
56
+ question_zh: '买家下单 100 WAZ 商品。卖家发货延迟 30 天且无任何物流证据。买家提 dispute。最合理 verdict?',
57
+ question_en: 'Buyer ordered 100 WAZ product. Seller delayed shipping 30 days with no logistics evidence. Buyer raises dispute. Most reasonable verdict?',
58
+ options: [
59
+ { key: 'A', text_zh: 'release_seller(放款给卖家)', text_en: 'release_seller (release to seller)' },
60
+ { key: 'B', text_zh: 'refund_buyer(全额退款给买家)', text_en: 'refund_buyer (full refund to buyer)' },
61
+ { key: 'C', text_zh: 'partial_refund(部分退款)', text_en: 'partial_refund (partial refund)' },
62
+ { key: 'D', text_zh: 'liability_split(平摊)', text_en: 'liability_split (split liability)' },
63
+ ],
64
+ correct_answer: 'B',
65
+ },
66
+ {
67
+ id: 'mcq-4',
68
+ type: 'multiple_choice',
69
+ domain: 'outlier',
70
+ question_zh: 'verifier A 投 pass(认定该 claim 为真),多数 verifier 投 fail。后续 maintainer 复核证实事实是 fail(claim 是假的)。verifier A 该:',
71
+ question_en: 'Verifier A voted pass (claim is true); majority voted fail. Later maintainer review confirmed the fact was fail (claim was false). Verifier A should:',
72
+ options: [
73
+ { key: 'A', text_zh: '不处罚(投票自由)', text_en: 'No penalty (voting freedom)' },
74
+ { key: 'B', text_zh: '信誉惩罚(偏离已查实事实,非偏离多数)', text_en: 'Reputation penalty (deviated from confirmed fact, not from majority)' },
75
+ { key: 'C', text_zh: '永久禁言', text_en: 'Permanent ban' },
76
+ { key: 'D', text_zh: '退还 stake 让他重投', text_en: 'Refund stake and re-vote' },
77
+ ],
78
+ correct_answer: 'B',
79
+ },
80
+ {
81
+ id: 'mcq-5',
82
+ type: 'multiple_choice',
83
+ domain: 'iron-rule',
84
+ question_zh: '下列哪个 不属于 Iron-Rule 真人 Passkey 路径(必须真人确认的 action)?',
85
+ question_en: 'Which one is NOT among the Iron-Rule real-human Passkey paths (actions requiring real-human confirmation)?',
86
+ options: [
87
+ { key: 'A', text_zh: 'arbitrator 仲裁判决', text_en: 'Arbitrator verdict' },
88
+ { key: 'B', text_zh: '用户提现资金(任何金额)', text_en: 'User withdrawal (any amount)' },
89
+ { key: 'C', text_zh: '浏览公开商品列表', text_en: 'Browse public product list' },
90
+ { key: 'D', text_zh: '删除自己的 Passkey', text_en: 'Delete own Passkey' },
91
+ ],
92
+ correct_answer: 'C',
93
+ },
94
+ {
95
+ id: 'mcq-6',
96
+ type: 'multiple_choice',
97
+ domain: 'governance',
98
+ question_zh: 'framework §3.1 / §3.2 说:二叉树位置是…',
99
+ question_en: 'framework §3.1 / §3.2 says: binary-tree position is...',
100
+ options: [
101
+ { key: 'A', text_zh: '独立收益源(占位就有钱)', text_en: 'Independent income source (hold = earn)' },
102
+ { key: 'B', text_zh: '关系层记录 + 估值层修饰参数(base 必须 > 0)', text_en: 'Relationship-layer record + valuation-layer modifier (base must be > 0)' },
103
+ { key: 'C', text_zh: '与回报完全无关', text_en: 'Completely unrelated to rewards' },
104
+ { key: 'D', text_zh: '仅 arbitrator 可用', text_en: 'Only arbitrators can use it' },
105
+ ],
106
+ correct_answer: 'B',
107
+ },
108
+ {
109
+ id: 'mcq-7',
110
+ type: 'multiple_choice',
111
+ domain: 'governance',
112
+ question_zh: '修改 fault 处置【规则】(属宪法级条款变动,见 CHARTER §4 I-4 — 不是普通协议改动)需要走?',
113
+ question_en: 'Modifying fault-handling **rules** (a CONSTITUTIONAL clause change per CHARTER §4 I-4 — not a regular protocol change) requires?',
114
+ options: [
115
+ { key: 'A', text_zh: '普通 maintainer 1 签', text_en: 'Single maintainer signature' },
116
+ { key: 'B', text_zh: 'user 个人否决', text_en: 'User personal veto' },
117
+ { key: 'C', text_zh: '超级多数多签(2/3) + 60 天公示(宪法级,user 仅是多签一票)', text_en: 'Supermajority multisig (2/3) + 60d public notice (constitutional; user is just one signer)' },
118
+ { key: 'D', text_zh: '任意 contributor 投票', text_en: 'Any contributor vote' },
119
+ ],
120
+ correct_answer: 'C',
121
+ },
122
+ {
123
+ id: 'mcq-8',
124
+ type: 'multiple_choice',
125
+ domain: 'dispute-verdict',
126
+ role_filter: 'arbitrator',
127
+ question_zh: 'Case 2 物流卡顿:卖家提供发货单,买家未收到货,物流方失联 45 天。arbitrator 应:',
128
+ question_en: 'Case 2 logistics stuck: seller has shipping doc, buyer never received, logistics gone 45 days. Arbitrator should:',
129
+ options: [
130
+ { key: 'A', text_zh: 'release_seller(卖家无过错)', text_en: 'release_seller (seller has no fault)' },
131
+ { key: 'B', text_zh: 'refund_buyer + 物流 stake **优先赔买家**(不足部分由 management_bonus_pool 兜底差额)', text_en: 'refund_buyer + logistics stake **goes to buyer first** (shortfall covered by management_bonus_pool)' },
132
+ { key: 'C', text_zh: 'partial_refund 50/50', text_en: 'partial_refund 50/50' },
133
+ { key: 'D', text_zh: 'liability_split,卖家一半物流方一半', text_en: 'liability_split, half seller half logistics' },
134
+ ],
135
+ correct_answer: 'B',
136
+ },
137
+ {
138
+ id: 'mcq-9',
139
+ type: 'multiple_choice',
140
+ domain: 'outlier',
141
+ role_filter: 'arbitrator',
142
+ question_zh: 'arbitrator A 最近 10 次 verdict 有 6 次被 maintainer 复核证实判错(count=6 ≥ 5 阈值, pct=60% ≥ 30% 阈值,双阈值均触发)。A 该:',
143
+ question_en: 'Arbitrator A: 6 of last 10 verdicts confirmed-wrong by maintainer review (count=6 ≥ 5 threshold AND pct=60% ≥ 30% threshold, both triggered). A should:',
144
+ options: [
145
+ { key: 'A', text_zh: '不处罚,投票自由', text_en: 'No penalty, voting freedom' },
146
+ { key: 'B', text_zh: '仅加 outlier 标记(信号,不触发 deactivate)', text_en: 'Only add outlier flag (signal, no deactivate)' },
147
+ { key: 'C', text_zh: '触发 auto_deactivate(双阈值均满足 — ARBITRATION-PLAYBOOK §6.2)', text_en: 'Trigger auto_deactivate (both thresholds met — ARBITRATION-PLAYBOOK §6.2)' },
148
+ { key: 'D', text_zh: '永久驱逐', text_en: 'Permanent ban' },
149
+ ],
150
+ correct_answer: 'C',
151
+ },
152
+ {
153
+ id: 'mcq-10',
154
+ type: 'multiple_choice',
155
+ domain: 'governance',
156
+ question_zh: 'arbitrator 对**具体某个仲裁案件**的 verdict 是否可被创始人 veto?',
157
+ question_en: 'Can the founder veto an arbitrator\'s verdict on a specific individual case?',
158
+ options: [
159
+ { key: 'A', text_zh: '可,创始人是最终仲裁', text_en: 'Yes, founder is final arbiter' },
160
+ { key: 'B', text_zh: '不可,verdict 归 arbitrator 集体,创始人无个案 veto(违 #5)', text_en: 'No, verdict belongs to arbitrator collective; founder has no per-case veto (violates #5)' },
161
+ { key: 'C', text_zh: '仅大额案件可', text_en: 'Only large-value cases' },
162
+ { key: 'D', text_zh: '仅 phase A 可', text_en: 'Only in phase A' },
163
+ ],
164
+ correct_answer: 'B',
165
+ },
166
+ // ── 短答题 1-5(phase A 简化评分:length >= 50 chars 且非纯空白) ─────
167
+ {
168
+ id: 'short-1',
169
+ type: 'short_answer',
170
+ domain: 'meta-rules',
171
+ question_zh: '描述一个**违反 #5 不偏袒** 的具体场景(≥ 100 字)。例如:某 user 通过多签提交协议参数修改,maintainer 因为该 user 是早期贡献者而跳过 review。',
172
+ question_en: 'Describe a concrete scenario of **violating #5 no-favoritism** (≥ 100 chars). E.g.: maintainer skips review of a protocol param change because the proposer is an early contributor.',
173
+ min_chars: 100,
174
+ },
175
+ {
176
+ id: 'short-2',
177
+ type: 'short_answer',
178
+ domain: 'iron-rule',
179
+ question_zh: '列出 Iron-Rule 7 paths 中你能记住的 5 条(每条简短描述)。',
180
+ question_en: 'List 5 of the Iron-Rule 7 paths you can remember (brief description each).',
181
+ min_chars: 50,
182
+ },
183
+ {
184
+ id: 'short-3',
185
+ type: 'short_answer',
186
+ domain: 'dispute-verdict',
187
+ role_filter: 'arbitrator',
188
+ question_zh: '解释 4 种 dispute verdict(release_seller / refund_buyer / partial_refund / liability_split)的适用场景。每种简短说一个例子。',
189
+ question_en: 'Explain when to apply each of 4 dispute verdicts (release_seller / refund_buyer / partial_refund / liability_split) with one short example each.',
190
+ min_chars: 100,
191
+ },
192
+ {
193
+ id: 'short-4',
194
+ type: 'short_answer',
195
+ domain: 'governance',
196
+ question_zh: '解释为什么 arbitrator 对个案 verdict 不能被任何个人(包括创始人)veto。引用 CHARTER §4 I-4 + 元规则 #5。',
197
+ question_en: 'Explain why an arbitrator\'s per-case verdict cannot be vetoed by any individual (including founder). Reference CHARTER §4 I-4 + meta-rule #5.',
198
+ min_chars: 100,
199
+ },
200
+ {
201
+ id: 'short-5',
202
+ type: 'short_answer',
203
+ domain: 'outlier',
204
+ role_filter: 'arbitrator',
205
+ question_zh: '描述 outlier 标记(信号)vs auto_deactivate(触发)的区别。前者只标记,后者真触发 deactivate。引用 ARBITRATION-PLAYBOOK §6.1 / §6.2。',
206
+ question_en: 'Describe the difference between outlier flag (signal) and auto_deactivate (trigger). The former is just a flag, the latter actually deactivates. Reference ARBITRATION-PLAYBOOK §6.1 / §6.2.',
207
+ min_chars: 100,
208
+ },
209
+ ];
210
+ // 为前端 GET 端点准备:剥离 correct_answer + min_chars 限制(防泄题)
211
+ export function getQuestionsForRole(role) {
212
+ return ONBOARDING_QUIZ
213
+ .filter(q => !q.role_filter || q.role_filter === role)
214
+ .map(({ correct_answer: _correct_answer, ...rest }) => rest);
215
+ }
216
+ export function scoreQuiz(role, answers, passThreshold = 80) {
217
+ const questions = ONBOARDING_QUIZ.filter(q => !q.role_filter || q.role_filter === role);
218
+ const answerMap = new Map(answers.map(a => [a.question_id, a.answer]));
219
+ const perQuestion = [];
220
+ let correct = 0;
221
+ for (const q of questions) {
222
+ const userAnswer = answerMap.get(q.id) ?? '';
223
+ if (q.type === 'multiple_choice') {
224
+ const ok = userAnswer === q.correct_answer;
225
+ perQuestion.push({ id: q.id, ok });
226
+ if (ok)
227
+ correct++;
228
+ }
229
+ else {
230
+ // short-answer: phase A 简化 — length >= min_chars 且非纯空白 → ok
231
+ const trimmed = userAnswer.trim();
232
+ const minChars = q.min_chars ?? 50;
233
+ const ok = trimmed.length >= minChars;
234
+ perQuestion.push({
235
+ id: q.id,
236
+ ok,
237
+ reason: ok ? undefined : `需要至少 ${minChars} 字符,当前 ${trimmed.length}`,
238
+ });
239
+ if (ok)
240
+ correct++;
241
+ }
242
+ }
243
+ const total = questions.length;
244
+ const score_pct = total > 0 ? Math.round((correct / total) * 100) : 0;
245
+ const passed = score_pct >= passThreshold;
246
+ return { total, correct, score_pct, passed, per_question: perQuestion };
247
+ }