@seasonkoh/webaz 0.1.18 → 0.1.19

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.
@@ -199,13 +199,28 @@ function settleFault(db, orderId, faultState) {
199
199
  const buyerId = order.buyer_id;
200
200
  const sellerId = order.seller_id;
201
201
  const isSecondhand = order.source === 'secondhand';
202
- // QA 7 P0 修复 改 per-order stake
203
- // 旧逻辑读 product.stake_amount + stake_locked_at(per-product 模型残留,已废弃)
204
- // 新逻辑:stake = order.total_amount × 0.15,下单时已锁,fault 时必然 lock(除非该订单未到 paid)
205
- const sellerStakeRate = 0.15;
202
+ // RFC-008 stage 1(印钱 bug 修复):违约没收【只按订单的 stake_backing 快照】,绝不假设已锁、绝不超背书。
203
+ // bug:无条件 staked -= 0.15×total,但生产下单根本没锁 stake → staked 转负 + 印钱。
204
+ // 现在:没收 = min(penalty, stake_backing),从 staked 扣,封顶背书额 永不转负、永不印钱。
205
+ // 起步免赔付(require_seller_stake=0 backing=0):没收 0,买家已全额退款,卖家仅掉信誉。
206
+ // stage 2(收紧后):penalty 解耦为 fault_penalty_rate×total + 不足部分扣自由 balance(仅背书订单)。
207
+ const sellerStakeRate = 0.15; // stage 1 penalty 基数(stage 2 改 fault_penalty_rate=0.30 并解耦)
206
208
  const stakeAmount = isSecondhand ? 0 : Math.round(total * sellerStakeRate * 100) / 100;
207
- // 订单到 paid 之后 stake 必然 lock(place_order 已扣,余额不足会 reject)
208
- const stakeLocked = !isSecondhand && Number(order.total_amount) > 0 && order.status !== 'created';
209
+ const orderStakeBacking = Math.max(0, Math.round(Number(order.stake_backing || 0) * 100) / 100);
210
+ // 没收并按 buyer 50% / sys_protocol 50% 分配,返回实扣额(= 0 表示起步免赔付,无没收无印钱)
211
+ const forfeitBackedStake = (penaltyBase) => {
212
+ const actualDeduct = Math.min(penaltyBase, orderStakeBacking); // 封顶背书额 → 绝不超已锁、绝不转负
213
+ if (actualDeduct <= 0)
214
+ return 0;
215
+ db.prepare('UPDATE wallets SET staked = staked - ? WHERE user_id = ?').run(actualDeduct, sellerId);
216
+ const compensation = Math.round(actualDeduct * 0.5 * 100) / 100;
217
+ const protocolShare = Math.round((actualDeduct - compensation) * 100) / 100;
218
+ if (compensation > 0)
219
+ db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compensation, buyerId);
220
+ if (protocolShare > 0)
221
+ db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(protocolShare, sysUserId);
222
+ return actualDeduct;
223
+ };
209
224
  // P0.1:RFQ 路径的 bid_stake_held — fault 时由各分支按规则处理
210
225
  const bidStakeHeld = Number(order.bid_stake_held || 0);
211
226
  if (faultState === 'fault_seller') {
@@ -222,31 +237,9 @@ function settleFault(db, orderId, faultState) {
222
237
  if (compToSys > 0)
223
238
  db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compToSys, sysUserId);
224
239
  }
225
- // 2. stake 50/50 扣罚(严格资金守恒:实扣多少就分多少,不允许协议印钱)
226
- if (stakeAmount > 0) {
227
- // 先确定实际可扣金额
228
- let actualDeduct = 0;
229
- if (stakeLocked) {
230
- // 已锁 → stake 全额扣(必然可扣,因 stake 已 lock 不可挪用)
231
- db.prepare('UPDATE wallets SET staked = staked - ? WHERE user_id = ?').run(stakeAmount, sellerId);
232
- actualDeduct = stakeAmount;
233
- }
234
- else {
235
- // 未锁 → 从 seller.balance 扣(不足则只扣到 0,差额不发放,不印钱)
236
- const w = db.prepare('SELECT balance FROM wallets WHERE user_id = ?').get(sellerId);
237
- actualDeduct = Math.min(stakeAmount, Number(w?.balance ?? 0));
238
- if (actualDeduct > 0) {
239
- db.prepare('UPDATE wallets SET balance = balance - ? WHERE user_id = ?').run(actualDeduct, sellerId);
240
- }
241
- }
242
- // 按 actualDeduct 比例分配(buyer 50% + sys_protocol 50%),保证 入 = 出
243
- const compensation = Math.round(actualDeduct * 0.5 * 100) / 100;
244
- const protocolShare = Math.round((actualDeduct - compensation) * 100) / 100;
245
- if (compensation > 0)
246
- db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compensation, buyerId);
247
- if (protocolShare > 0)
248
- db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(protocolShare, sysUserId);
249
- }
240
+ // 2. 没收(封顶 stake_backing,绝不印钱)→ buyer 50% / sys_protocol 50%
241
+ if (stakeAmount > 0)
242
+ forfeitBackedStake(stakeAmount);
250
243
  // 3. 库存回退(非二手)
251
244
  if (!isSecondhand)
252
245
  db.prepare('UPDATE products SET stock = stock + 1 WHERE id = ?').run(order.product_id);
@@ -278,27 +271,9 @@ function settleFault(db, orderId, faultState) {
278
271
  if (compToSys > 0)
279
272
  db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compToSys, sysUserId);
280
273
  }
281
- // 3. stake 50/50 扣罚(seller 违约:自选 self-fulfill 但没送达)
282
- if (stakeAmount > 0) {
283
- let actualDeduct = 0;
284
- if (stakeLocked) {
285
- db.prepare('UPDATE wallets SET staked = staked - ? WHERE user_id = ?').run(stakeAmount, sellerId);
286
- actualDeduct = stakeAmount;
287
- }
288
- else {
289
- const w = db.prepare('SELECT balance FROM wallets WHERE user_id = ?').get(sellerId);
290
- actualDeduct = Math.min(stakeAmount, Number(w?.balance ?? 0));
291
- if (actualDeduct > 0) {
292
- db.prepare('UPDATE wallets SET balance = balance - ? WHERE user_id = ?').run(actualDeduct, sellerId);
293
- }
294
- }
295
- const compensation = Math.round(actualDeduct * 0.5 * 100) / 100;
296
- const protocolShare = Math.round((actualDeduct - compensation) * 100) / 100;
297
- if (compensation > 0)
298
- db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compensation, buyerId);
299
- if (protocolShare > 0)
300
- db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(protocolShare, sysUserId);
301
- }
274
+ // 3. 没收(self-fulfill seller 违约;封顶 stake_backing,绝不印钱)→ buyer 50% / sys_protocol 50%
275
+ if (stakeAmount > 0)
276
+ forfeitBackedStake(stakeAmount);
302
277
  // 4. 库存回退
303
278
  if (!isSecondhand)
304
279
  db.prepare('UPDATE products SET stock = stock + 1 WHERE id = ?').run(order.product_id);
@@ -318,9 +293,10 @@ function settleFault(db, orderId, faultState) {
318
293
  if (bidStakeHeld > 0) {
319
294
  db.prepare('UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?').run(bidStakeHeld, bidStakeHeld, sellerId);
320
295
  }
321
- if (stakeAmount > 0 && stakeLocked) {
296
+ // seller 无责 退还其【该单实际背书的 stake】(= stake_backing;起步阶段=0,无可退)
297
+ if (orderStakeBacking > 0) {
322
298
  db.prepare('UPDATE wallets SET staked = staked - ?, balance = balance + ? WHERE user_id = ?')
323
- .run(stakeAmount, stakeAmount, sellerId);
299
+ .run(orderStakeBacking, orderStakeBacking, sellerId);
324
300
  }
325
301
  // 3. 库存回退
326
302
  if (!isSecondhand)
@@ -1359,7 +1359,7 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
1359
1359
 
1360
1360
  Actions:
1361
1361
  - list_open (default): open tasks (opt. area filter)
1362
- - claim: take an open task; provenance=human|ai_assisted|ai_authored (self-declared, not detected); auto-expires ~7d if not submitted
1362
+ - claim: take an open task; provenance=human|ai_assisted|ai_authored (self-declared, not detected); auto-expires ~7d if not submitted. Returns a **handoff** (repo + AGENTS.md + PR flow) — point a coding agent at it to actually do the work; the human needn't know git.
1363
1363
  - submit: mark in_review with pr_ref
1364
1364
  - status: tasks you hold (claimed/in_review)
1365
1365
  - profile: your build dashboard — KPI/tier/restrictions+appeal, self-view (private, no public leaderboard)
@@ -1439,9 +1439,22 @@ async function handleContribute(args) {
1439
1439
  const tid = args.task_id;
1440
1440
  if (!tid)
1441
1441
  return { error: 'task_id required for action=claim' };
1442
- return apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/claim', {
1442
+ const r = await apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/claim', {
1443
1443
  method: 'POST', apiKey, body: { provenance: args.provenance },
1444
1444
  });
1445
+ // RFC-006 断点1(b)交接:认领成功后直接下发"怎么真正动手",让贡献者的【编码 agent】接手 git/PR。
1446
+ // 关键:人不必会 git——人的编码 agent(如 Claude Code)做;人(Passkey 真人)担责。
1447
+ if (!r.error) {
1448
+ r.handoff = {
1449
+ repo: 'https://github.com/seasonsagents-art/webaz',
1450
+ start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
1451
+ do_the_work: 'Point a coding agent (e.g. Claude Code) at the repo; work on a single-topic branch. The buyer/shopping agent is not the coding agent — hand off to one.',
1452
+ pr_flow: 'Commit with DCO sign-off (git commit -s). If AI-authored, add 🤖🤖🤖 to the PR title + a meta-rule trace. Humans merge — no auto-merge.',
1453
+ then: `When the PR is open, report it back: webaz_contribute action=submit task_id=${tid} pr_ref=#<N>.`,
1454
+ human_note: "You don't need to know git — your coding agent does it; you (the Passkey-bound human) stay accountable.",
1455
+ };
1456
+ }
1457
+ return r;
1445
1458
  }
1446
1459
  if (action === 'submit') {
1447
1460
  const tid = args.task_id;
@@ -2,6 +2,8 @@ import { generateId } from '../../layer0-foundation/L0-1-database/schema.js';
2
2
  // RFC-006 不变量 1:建设贡献记入【独立】build_reputation 池,不再写交易 reputation_scores
3
3
  //(旧:recordRepEvent('feedback_accepted') 会污染 verifier/arbitrator 准入,已隔离)。
4
4
  import { creditBuildReputation, BUILD_POINTS } from '../L2-9-contribution/build-reputation-engine.js';
5
+ // RFC-006 桥(use→build 漏斗补全):采纳的 proposal → 自动建 build_task + 邀请提案人来认领。
6
+ import { createBuildTask } from '../L2-9-contribution/build-tasks-engine.js';
5
7
  export const FB_TYPES = new Set(['ux_issue', 'bug', 'proposal']);
6
8
  export const FB_SEVERITY = new Set(['low', 'annoying', 'blocking']);
7
9
  export const FB_STATUS = new Set(['received', 'triaged', 'in_progress', 'resolved', 'declined', 'duplicate']);
@@ -38,6 +40,8 @@ export function initBuildFeedbackSchema(db) {
38
40
  'ALTER TABLE build_feedback ADD COLUMN ai_summary TEXT', // 一句话摘要(给 maintainer 扫)
39
41
  'ALTER TABLE build_feedback ADD COLUMN ai_models TEXT', // 参与的模型 + 是否一致
40
42
  'ALTER TABLE build_feedback ADD COLUMN ai_triaged_at TEXT',
43
+ // RFC-006 桥:采纳的 proposal 被 promote 成 build_task 时,记其 task id(use→build 漏斗:反馈→协调)
44
+ 'ALTER TABLE build_feedback ADD COLUMN promoted_task_id TEXT',
41
45
  ]) {
42
46
  try {
43
47
  db.exec(stmt);
@@ -133,7 +137,7 @@ function parse(row) {
133
137
  return { ...rest, scene };
134
138
  }
135
139
  export function listMyBuildFeedback(db, userId) {
136
- const rows = db.prepare(`SELECT id, type, area, severity, subject, body, status, dedup_of, resolution, credited_points, created_at, updated_at
140
+ const rows = db.prepare(`SELECT id, type, area, severity, subject, body, status, dedup_of, resolution, credited_points, promoted_task_id, created_at, updated_at
137
141
  FROM build_feedback WHERE user_id = ? ORDER BY created_at DESC LIMIT 100`).all(userId);
138
142
  return rows;
139
143
  }
@@ -179,7 +183,32 @@ export function adminUpdateBuildFeedback(db, u) {
179
183
  WHERE id = ?`).run(newStatus, u.resolution ?? null, u.rfcDraft ?? null, credited, u.adminId, u.id);
180
184
  if (newStatus !== fromStatus)
181
185
  logEvent(db, u.id, u.adminId, fromStatus, newStatus, u.resolution ?? null);
182
- return { ok: true, credited, ...(credit_skipped_no_anchor ? { credit_skipped_no_anchor: true } : {}) };
186
+ // RFC-006 (use→build 漏斗补全):maintainer 采纳提案时可 promote 成可认领的 build_task,
187
+ // 并【邀请提案人】来认领——把"反馈被采纳"接到"来一起建设",补上漏斗最大断点。
188
+ // 幂等:已 promote 过(promoted_task_id 非空)不重复建。
189
+ let promoted_task_id;
190
+ const already = row.promoted_task_id || null;
191
+ if (u.promoteToTask && !already && row.type === 'proposal') {
192
+ const title = (row.subject || row.body || 'contributor proposal').slice(0, 200);
193
+ const created = createBuildTask(db, {
194
+ creatorId: u.adminId,
195
+ title,
196
+ area: row.area || undefined,
197
+ description: `From accepted proposal ${u.id} (by ${row.user_id}).\n\n${row.body || ''}`.slice(0, 4000),
198
+ rfcRef: row.rfc_draft || undefined,
199
+ });
200
+ if ('id' in created) {
201
+ promoted_task_id = created.id;
202
+ db.prepare('UPDATE build_feedback SET promoted_task_id = ? WHERE id = ?').run(promoted_task_id, u.id);
203
+ // 邀请提案人:通知 + 反馈闭环里会显示 promoted_task_id
204
+ try {
205
+ db.prepare(`INSERT INTO notifications (id, user_id, type, title, body) VALUES (?,?,?,?,?)`).run(generateId('ntf'), row.user_id, 'build_invite', '你的提案被采纳了 — 来一起建设?', `提案「${title}」已被采纳并建成可认领任务 ${promoted_task_id}。在「我的共建」用 webaz_contribute 认领即可参与实现。`);
206
+ }
207
+ catch { /* notifications 可选,不阻断 */ }
208
+ logEvent(db, u.id, u.adminId, newStatus, newStatus, `promoted → task ${promoted_task_id}`);
209
+ }
210
+ }
211
+ return { ok: true, credited, ...(credit_skipped_no_anchor ? { credit_skipped_no_anchor: true } : {}), ...(promoted_task_id ? { promoted_task_id } : {}) };
183
212
  }
184
213
  // ─── RFC-005 Phase 2:AI triage(advisory)─────────────────────────
185
214
  // 给"内部反馈"打标,不碰代码、不 resolve、不记功(那是人类的)。无 key 也能跑(只做确定性去重 + 置 triaged)。
@@ -57,6 +57,10 @@ export function creditBuildReputation(db, userId, source, points, refId, note) {
57
57
  }
58
58
  return { credited: points };
59
59
  }
60
+ // RFC-006 stage 4(恶意管理)= 复用现有问责中间件,**无需新代码**:
61
+ // build_tasks 是 api_key 写端点,被 strike 至 suspend_7d/permanent 的贡献者已被 isApiKeyBlocked
62
+ // (server.ts)挡在所有写之外,包括建设。strike/blocklist/outlier 对建设贡献自动生效。
63
+ // 看板这里只【展示】当事人的活跃 strike + 申诉入口(透明先于强制);真人申诉走现成 strikes/:id/appeal。
60
64
  // 贡献者【自查】档案 —— KPI + 等级 + 来源拆分 + provenance + 限制/惩罚 + 申诉入口。
61
65
  // 不变量 3:仅本人可调(路由层 auth);不做公开榜。
62
66
  export function getBuildProfile(db, userId) {
@@ -71,9 +71,10 @@ export function registerBuildFeedbackRoutes(app, deps) {
71
71
  const admin = requireSupportAdmin(req, res);
72
72
  if (!admin)
73
73
  return;
74
- const { status, resolution, rfc_draft, credit } = req.body ?? {};
74
+ const { status, resolution, rfc_draft, credit, promote_to_task } = req.body ?? {};
75
75
  const result = adminUpdateBuildFeedback(db, {
76
- id: String(req.params.id), status, resolution, rfcDraft: rfc_draft, credit: !!credit, adminId: admin.id,
76
+ id: String(req.params.id), status, resolution, rfcDraft: rfc_draft, credit: !!credit,
77
+ promoteToTask: !!promote_to_task, adminId: admin.id,
77
78
  });
78
79
  if ('error' in result)
79
80
  return void res.status(404).json(result);
@@ -223,6 +223,11 @@ export function registerOrdersCreateRoutes(app, deps) {
223
223
  const buyerRegionSnapshot = buyer?.region || 'global';
224
224
  // P2P:若为 P2P 商品,下单时快照 content_hash(争议时凭买家所见 hash 判定)
225
225
  const contentHashSnapshot = (Number(product.p2p_mode) === 1 && product.content_hash) ? String(product.content_hash) : null;
226
+ // RFC-008 stage 1:赔付背书快照。起步免赔付阶段(require_seller_stake=0)= 0 → 违约只退款不没收、不印钱、零门槛。
227
+ // ⚠️ "要求质押"档(=1,下单锁 stake、backing=total×stake_rate(信誉))属后续【收紧】阶段,届时在此计算并锁定。
228
+ const stakeBacking = Number(getProtocolParam('require_seller_stake', 0)) === 1
229
+ ? 0 // TODO(tighten stage): backing = total × stakeRate(seller reputation) + 在此 lock balance→staked
230
+ : 0;
226
231
  db.prepare(`INSERT INTO orders (
227
232
  id, product_id, buyer_id, seller_id, quantity, unit_price, total_amount, escrow_amount,
228
233
  status, shipping_address, notes, pay_deadline, accept_deadline, ship_deadline,
@@ -230,8 +235,8 @@ export function registerOrdersCreateRoutes(app, deps) {
230
235
  l1_uid, l2_uid, l3_uid, snapshot_commission_rate, buyer_region, content_hash_at_order,
231
236
  delivery_window, variant_id, variant_options_snapshot,
232
237
  gift_recipient_name, gift_recipient_phone, gift_message, insurance_premium,
233
- anonymous_recipient, recipient_code, donation_amount
234
- ) VALUES (?,?,?,?,?,?,?,?,'created',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(orderId, product.id, user.id, product.seller_uid, reqQty, basePrice, totalAmount, totalAmount, shipping_address, notes || null, addHours(now, 24), addHours(now, 48), addHours(now, 120), addHours(now, 168), addHours(now, 336), addHours(now, 408), l1, l2, l3, snapshotRate, buyerRegionSnapshot, contentHashSnapshot, deliveryWindowJson, variant ? variant.id : null, variant ? variant.options_json : null, giftRecipientName, giftRecipientPhone, giftMessage, insurancePremium, anonymousFlag, recipientCode, donationAmount);
238
+ anonymous_recipient, recipient_code, donation_amount, stake_backing
239
+ ) VALUES (?,?,?,?,?,?,?,?,'created',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(orderId, product.id, user.id, product.seller_uid, reqQty, basePrice, totalAmount, totalAmount, shipping_address, notes || null, addHours(now, 24), addHours(now, 48), addHours(now, 120), addHours(now, 168), addHours(now, 336), addHours(now, 408), l1, l2, l3, snapshotRate, buyerRegionSnapshot, contentHashSnapshot, deliveryWindowJson, variant ? variant.id : null, variant ? variant.options_json : null, giftRecipientName, giftRecipientPhone, giftMessage, insurancePremium, anonymousFlag, recipientCode, donationAmount, stakeBacking);
235
240
  // 协议层:写 genesis 事件 — order 创建(必然是 buyer 自己)
236
241
  try {
237
242
  appendOrderEvent(db, {
@@ -531,6 +531,9 @@ for (const stmt of [
531
531
  'ALTER TABLE orders ADD COLUMN gift_message TEXT',
532
532
  // C-3: 订单保险 — 已支付保费(默认 1%,争议时若卖家余额不足由保险池补足)
533
533
  'ALTER TABLE orders ADD COLUMN insurance_premium REAL DEFAULT 0',
534
+ // RFC-008 stage 1:每单【赔付背书】快照 = 该单实际背书的卖家质押额。
535
+ // 起步免赔付阶段(require_seller_stake=0)= 0;违约结算只按此数没收,绝不扣未背书的钱 → 根治印钱 bug。
536
+ 'ALTER TABLE orders ADD COLUMN stake_backing REAL DEFAULT 0',
534
537
  ]) {
535
538
  try {
536
539
  db.exec(stmt);
@@ -767,10 +770,14 @@ try {
767
770
  catch { }
768
771
  // 已注册默认参数(首次启动 seed) — P0-2 加 min/max 边界
769
772
  const DEFAULT_PARAMS = [
770
- { key: 'protocol_fee_rate_shop', value: '0.02', type: 'number', description: '商家订单平台费率', category: 'fee', min: 0, max: 0.20 },
771
- { key: 'protocol_fee_rate_secondhand', value: '0.01', type: 'number', description: '二手订单平台费率', category: 'fee', min: 0, max: 0.20 },
773
+ // RFC-008:平台费硬帽 2%(=当前稳态 治理只能在 0–2% 减免、永不涨)。合计封顶 = 平台费2% + fund_base1% = 3%。宪法级合法性见 CHARTER 修订(单独治理步)。
774
+ { key: 'protocol_fee_rate_shop', value: '0.02', type: 'number', description: '商家订单平台费率(RFC-008 硬帽 2%,只减不涨;前期可减免)', category: 'fee', min: 0, max: 0.02 },
775
+ { key: 'protocol_fee_rate_secondhand', value: '0.01', type: 'number', description: '二手订单平台费率(RFC-008 硬帽 2%,只减不涨)', category: 'fee', min: 0, max: 0.02 },
772
776
  { key: 'default_commission_rate', value: '0.05', type: 'number', description: '新商品默认分享佣金(对齐小红书 5-10%)', category: 'fee', min: 0, max: 0.50 },
773
- { key: 'fund_base_rate', value: '0.01', type: 'number', description: '协议基金池基础费率(每订单固定入池)', category: 'fee', min: 0, max: 0.10 },
777
+ // RFC-008:fund_base 硬帽 1%;pre-launch 减免到 0(社区基金按真实 GMV 注入,0 GMV 时是无回报的税)。有真实 GMV 再由治理开启(≤1%)。
778
+ { key: 'fund_base_rate', value: '0', type: 'number', description: '协议基金池基础费率(RFC-008 硬帽 1%;pre-launch 减免=0,有真实 GMV 再由治理开启 ≤1%)', category: 'fee', min: 0, max: 0.01 },
779
+ // RFC-008:起步免赔付门槛。0 = bootstrap(新商家零质押、违约免赔付只退款+掉信誉,降进入门槛);1 = 要求卖家质押(下单锁 stake、违约真没收)。上轨道后由治理开启。
780
+ { key: 'require_seller_stake', value: '0', type: 'number', description: 'RFC-008 是否要求卖家质押(0=起步免赔付/零门槛,1=要求质押/真没收)', category: 'fee', min: 0, max: 1 },
774
781
  { key: 'checkin_base_reward', value: '0.5', type: 'number', description: '每日签到基础奖励 WAZ', category: 'reward', min: 0, max: 10 },
775
782
  { key: 'streak_bonus_7', value: '5', type: 'number', description: '7 天里程碑额外奖励', category: 'reward', min: 0, max: 100 },
776
783
  { key: 'streak_bonus_30', value: '20', type: 'number', description: '30 天里程碑额外奖励', category: 'reward', min: 0, max: 500 },
@@ -901,6 +908,31 @@ try {
901
908
  catch (e) {
902
909
  console.error('[migration #1006]', e);
903
910
  }
911
+ // RFC-008 迁移:费帽收紧 + fund_base pre-launch 减免。bounds 是协议护栏 → 无条件强制收窄(幂等)。
912
+ // 平台费帽 → 2%(=稳态,只减不涨);fund_base 帽 → 1%;合计封顶 3%。fund_base 值减免到 0(仅原始 0.01、未被治理改过)。
913
+ try {
914
+ const feeCap = db.prepare(`UPDATE protocol_params SET max_value = 0.02, updated_at = datetime('now')
915
+ WHERE key IN ('protocol_fee_rate_shop','protocol_fee_rate_secondhand') AND max_value > 0.02`).run();
916
+ if (feeCap.changes > 0)
917
+ console.log(`[migration RFC-008] 平台费硬帽收紧 ${feeCap.changes} 项 → max 2%`);
918
+ const fbCap = db.prepare(`UPDATE protocol_params SET max_value = 0.01, updated_at = datetime('now')
919
+ WHERE key = 'fund_base_rate' AND max_value > 0.01`).run();
920
+ if (fbCap.changes > 0)
921
+ console.log(`[migration RFC-008] fund_base 硬帽收紧 → max 1%`);
922
+ const fb = db.prepare(`UPDATE protocol_params SET value = '0', default_value = '0', updated_at = datetime('now')
923
+ WHERE key = 'fund_base_rate' AND value = '0.01' AND updated_by IS NULL`).run();
924
+ if (fb.changes > 0) {
925
+ console.log(`[migration RFC-008] fund_base_rate 0.01 → 0 (pre-launch 减免)`);
926
+ try {
927
+ db.prepare(`INSERT INTO protocol_params_log (id, key, old_value, new_value, changed_by, action)
928
+ VALUES (?,?,?,?,?,'migrate')`).run(generateId('ppl'), 'fund_base_rate', '0.01', '0', 'migration_RFC-008');
929
+ }
930
+ catch { }
931
+ }
932
+ }
933
+ catch (e) {
934
+ console.error('[migration RFC-008]', e);
935
+ }
904
936
  // Wave G-2: USDC ↔ WAZ 转换助手
905
937
  function usdcToWaz(usdc) {
906
938
  const rate = getProtocolParam('waz_usdc_rate', 1.0);
@@ -8946,8 +8978,11 @@ function settleOrder(orderId) {
8946
8978
  // 用单语句 UPDATE ... WHERE stake_locked_at IS NULL 原子拿"是否首次"判定,
8947
8979
  // 避免读后写竞态(旧实现两条语句,迁 PG / 多 worker 时可能双锁)
8948
8980
  // M8: 二手商品无 stake 概念,跳过
8981
+ // RFC-008 stage 1:起步免赔付阶段(require_seller_stake=0)不锁 stake —— 与 settleFault 按 stake_backing(=0)结算一致,
8982
+ // 消除"成功时锁 product.stake_amount 但违约按 backing=0 不没收"的旧三口径不一致。收紧档(=1)由后续阶段统一在下单锁。
8983
+ const requireSellerStake = Number(getProtocolParam('require_seller_stake', 0)) === 1;
8949
8984
  let stakeToLock = 0;
8950
- if (!isSecondhand) {
8985
+ if (!isSecondhand && requireSellerStake) {
8951
8986
  const lockResult = db.prepare(`UPDATE products SET stake_locked_at = datetime('now') WHERE id = ? AND stake_locked_at IS NULL`).run(order.product_id);
8952
8987
  if (lockResult.changes === 1) {
8953
8988
  const sellerTrust = db.prepare(`SELECT level FROM reputation_scores WHERE user_id = ?`).get(order.seller_id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seasonkoh/webaz",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "[PRE-LAUNCH] Agent-native decentralized commerce protocol. Humans and AI agents trade on the same protocol via MCP tools. ⚠️ Repository currently private until W8 public launch — GitHub links may return 404. See https://webaz.xyz for status.",
5
5
  "main": "dist/mcp.js",
6
6
  "bin": {