@seasonkoh/webaz 0.1.21 → 0.1.23
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.
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +81 -93
- package/dist/layer1-agent/L1-1-mcp-server/server.js +682 -236
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +138 -168
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +6 -4
- package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +12 -8
- package/dist/ledger.js +58 -0
- package/dist/money.js +106 -0
- package/dist/pwa/acp-feed.js +103 -0
- package/dist/pwa/economic-participation.js +1 -1
- package/dist/pwa/integration-contract.js +4 -0
- package/dist/pwa/public/app.js +8 -7
- package/dist/pwa/public/index.html +27 -1
- package/dist/pwa/routes/auction.js +9 -6
- package/dist/pwa/routes/disputes-write.js +12 -11
- package/dist/pwa/routes/orders-create.js +24 -13
- package/dist/pwa/routes/public-utils.js +28 -0
- package/dist/pwa/routes/referral.js +81 -0
- package/dist/pwa/server.js +57 -39
- package/dist/settlement-math.js +27 -0
- package/package.json +1 -1
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
import { generateId } from '../L0-1-database/schema.js';
|
|
13
13
|
import { appendOrderEvent } from './order-chain.js';
|
|
14
14
|
import { VALID_TRANSITIONS, CURRENT_RESPONSIBLE, CURRENT_RESPONSIBLE_SELF_FULFILL } from './transitions.js';
|
|
15
|
+
// RFC-014 PR2 — 资金算术统一走整数 base-units;分配用 allocate 保证精确守恒。
|
|
16
|
+
import { toUnits, mulRate, allocate } from '../../money.js';
|
|
17
|
+
// RFC-014 PR3 — 钱包落库 helper 抽到共享 ledger 模块(原私有于此),防多份漂移。
|
|
18
|
+
import { walletUnits, applyWalletDelta } from '../../ledger.js';
|
|
15
19
|
// ─── 核心函数 ────────────────────────────────────────────────
|
|
16
20
|
/**
|
|
17
21
|
* 执行状态转移
|
|
@@ -236,104 +240,101 @@ export function settleFault(db, orderId, faultState) {
|
|
|
236
240
|
const v = Number(row?.value);
|
|
237
241
|
return Number.isFinite(v) && v >= 0 ? v : 0.30;
|
|
238
242
|
};
|
|
239
|
-
|
|
240
|
-
const
|
|
243
|
+
// RFC-014:金额一律转整数 base-units 后再算/分配。
|
|
244
|
+
const totalU = toUnits(total);
|
|
245
|
+
const penaltyU = isSecondhand ? 0 : mulRate(totalU, faultPenaltyRate());
|
|
246
|
+
const orderStakeBackingU = Math.max(0, toUnits(Number(order.stake_backing || 0)));
|
|
241
247
|
// RFC-007 stage 4:没收后的【守恒 + 不牟利】再分配(取代旧的 buyer 50% / protocol 50%)。
|
|
242
248
|
// 旧分配漏掉推广人(违反 §谁责任谁承担:推广人承担了真实推广成本却零补偿)。
|
|
243
249
|
// 新规则(全部用订单快照,可复算,绝不印钱):
|
|
244
250
|
// 1. 协议只回收【原本该收的平台费】protocolTake = min(F, total × protocol_fee_rate)
|
|
245
251
|
// —— 协议不从违约牟利;fund_base(1%) 排除(无成交=无 GMV,社区基金不应从罚没获利)。
|
|
246
|
-
// 2. R = F − protocolTake;买家补偿 = R × 50%(
|
|
252
|
+
// 2. R = F − protocolTake;买家补偿 = R × 50% 起(受损对手方基础份额)。
|
|
247
253
|
// 3. 推广人 = R 的另一半,按 l1/l2/l3 原始佣金比例分,【封顶各自原始佣金】—— 永不超过其真实损失。
|
|
248
|
-
// 4. 推广半残值(超封顶 / 无推广人)→
|
|
254
|
+
// 4. 推广半残值(超封顶 / 无推广人)→ 【买家】(受损方吸收违约方罚金剩余,故买家可超 50%)。
|
|
255
|
+
// 决策 A(2026-06-07,对齐 RFC-007 Invariant #2):残值是罚金且无成交,归被坑方,不入 commission_reserve。
|
|
249
256
|
// 守恒:protocolTake + buyerComp + promotersPaid + reserveResidual ≡ F(按构造,残值兜底)。
|
|
250
257
|
const FORFEIT_LEVEL_RATES = { 1: 0.70, 2: 0.20, 3: 0.10 };
|
|
251
|
-
const round2 = (n) => Math.round(n * 100) / 100;
|
|
252
|
-
const reserveResidual = (amount, note) => {
|
|
253
|
-
const a = round2(amount);
|
|
254
|
-
if (a <= 0)
|
|
255
|
-
return;
|
|
256
|
-
// 复制 redirectToCommissionReserve 的两笔写入(L0 不依赖 server.ts;kind 用既有 orphan_sponsor 桶)
|
|
257
|
-
db.prepare(`UPDATE commission_reserve SET balance = balance + ?, total_orphan_sponsor = total_orphan_sponsor + ?, updated_at = datetime('now') WHERE id = 'main'`).run(a, a);
|
|
258
|
-
db.prepare(`INSERT INTO commission_reserve_txns (id, kind, from_user_id, amount, related_order_id, note) VALUES (?,?,?,?,?,?)`)
|
|
259
|
-
.run(generateId('crt'), 'redirect_orphan_sponsor', sellerId, a, orderId, note);
|
|
260
|
-
};
|
|
261
258
|
const protocolFeeRate = () => {
|
|
262
259
|
const key = isSecondhand ? 'protocol_fee_rate_secondhand' : 'protocol_fee_rate_shop';
|
|
263
260
|
const row = db.prepare('SELECT value FROM protocol_params WHERE key = ?').get(key);
|
|
264
261
|
const v = Number(row?.value);
|
|
265
262
|
return Number.isFinite(v) && v >= 0 ? v : (isSecondhand ? 0.01 : 0.02);
|
|
266
263
|
};
|
|
267
|
-
// 罚没【收取 + RFC-007
|
|
264
|
+
// 罚没【收取 + RFC-007 守恒分配】(整数 base-units;allocate 保证精确守恒),返回实扣额 units(0=起步免赔付)
|
|
268
265
|
const forfeitAndDistribute = (penalty) => {
|
|
269
266
|
// 起步免赔付:无背书订单(stake_backing=0)绝不没收、绝不碰新商家自由余额
|
|
270
|
-
if (
|
|
267
|
+
if (orderStakeBackingU <= 0)
|
|
271
268
|
return 0;
|
|
272
|
-
//
|
|
273
|
-
const fromStaked = Math.min(penalty,
|
|
274
|
-
const remainder =
|
|
275
|
-
const
|
|
276
|
-
const fromBalance =
|
|
277
|
-
const F =
|
|
269
|
+
// 收取:先扣 staked(封顶背书额),不足再扣卖家自由 balance(责任自负;封顶其真实余额→不转负)
|
|
270
|
+
const fromStaked = Math.min(penalty, orderStakeBackingU);
|
|
271
|
+
const remainder = penalty - fromStaked;
|
|
272
|
+
const sellerBalU = Math.max(0, walletUnits(db, sellerId).balance);
|
|
273
|
+
const fromBalance = Math.min(remainder, sellerBalU);
|
|
274
|
+
const F = fromStaked + fromBalance;
|
|
278
275
|
if (F <= 0)
|
|
279
276
|
return 0;
|
|
280
|
-
|
|
281
|
-
db.prepare('UPDATE wallets SET staked = staked - ? WHERE user_id = ?').run(fromStaked, sellerId);
|
|
282
|
-
if (fromBalance > 0)
|
|
283
|
-
db.prepare('UPDATE wallets SET balance = balance - ? WHERE user_id = ?').run(fromBalance, sellerId);
|
|
277
|
+
applyWalletDelta(db, sellerId, { staked: -fromStaked, balance: -fromBalance });
|
|
284
278
|
// 1. 协议回收原始平台费(封顶 F,不牟利)
|
|
285
|
-
const protocolTake =
|
|
279
|
+
const protocolTake = Math.min(F, mulRate(totalU, protocolFeeRate()));
|
|
286
280
|
if (protocolTake > 0)
|
|
287
|
-
db
|
|
288
|
-
// 2. R 与买家补偿
|
|
289
|
-
const R =
|
|
290
|
-
const buyerComp =
|
|
281
|
+
applyWalletDelta(db, sysUserId, { balance: protocolTake });
|
|
282
|
+
// 2. R 与买家补偿(精确平分:allocate 两桶求和 ≡ R)
|
|
283
|
+
const R = F - protocolTake;
|
|
284
|
+
const [buyerComp, promoterHalf] = allocate(R, [1, 1]);
|
|
291
285
|
if (buyerComp > 0)
|
|
292
|
-
db
|
|
293
|
-
// 3. 推广半 → l1/l2/l3
|
|
294
|
-
const promoterHalf = round2(R - buyerComp);
|
|
286
|
+
applyWalletDelta(db, buyerId, { balance: buyerComp });
|
|
287
|
+
// 3. 推广半 → l1/l2/l3 按原始佣金比例分,封顶原始佣金总额(allocate 求和 ≡ payable)
|
|
295
288
|
const commissionRate = Number(order.snapshot_commission_rate ?? 0);
|
|
296
|
-
const
|
|
289
|
+
const poolU = mulRate(totalU, Number.isFinite(commissionRate) && commissionRate > 0 ? commissionRate : 0);
|
|
297
290
|
const levels = [
|
|
298
|
-
{ uid: order.l1_uid, orig:
|
|
299
|
-
{ uid: order.l2_uid, orig:
|
|
300
|
-
{ uid: order.l3_uid, orig:
|
|
291
|
+
{ uid: order.l1_uid, orig: mulRate(poolU, FORFEIT_LEVEL_RATES[1]) },
|
|
292
|
+
{ uid: order.l2_uid, orig: mulRate(poolU, FORFEIT_LEVEL_RATES[2]) },
|
|
293
|
+
{ uid: order.l3_uid, orig: mulRate(poolU, FORFEIT_LEVEL_RATES[3]) },
|
|
301
294
|
].filter(l => l.uid); // 仅【真实存在的推广人】参与
|
|
302
|
-
const originalCommissionTotal =
|
|
295
|
+
const originalCommissionTotal = levels.reduce((s, l) => s + l.orig, 0);
|
|
303
296
|
let promotersPaid = 0;
|
|
304
297
|
if (promoterHalf > 0 && originalCommissionTotal > 0) {
|
|
305
298
|
const payable = Math.min(promoterHalf, originalCommissionTotal); // 封顶原始佣金总额
|
|
306
|
-
|
|
307
|
-
|
|
299
|
+
const shares = allocate(payable, levels.map(l => l.orig));
|
|
300
|
+
levels.forEach((l, i) => {
|
|
301
|
+
const share = shares[i];
|
|
308
302
|
if (share <= 0)
|
|
309
|
-
|
|
310
|
-
db.
|
|
311
|
-
promotersPaid
|
|
312
|
-
}
|
|
303
|
+
return;
|
|
304
|
+
applyWalletDelta(db, l.uid, { balance: share, earned: share });
|
|
305
|
+
promotersPaid += share;
|
|
306
|
+
});
|
|
313
307
|
}
|
|
314
|
-
// 4. 推广半残值(超封顶 / 无推广人 / 取整余数)→
|
|
315
|
-
|
|
308
|
+
// 4. 推广半残值(超封顶 / 无推广人 / 取整余数)→ 【买家】(受损方吸收违约方罚金剩余,可超 50%)。
|
|
309
|
+
// 决策 A(2026-06-07):对齐 RFC-007 Invariant #2「buyer absorbs the residual」+ 公开 economic.json。
|
|
310
|
+
// 理由:残值是【卖家罚金 + 无成交】,非正常单的未归属销售 margin,故归被坑的对手方,不入 commission_reserve。
|
|
311
|
+
// 守恒:protocolTake + (buyerComp+residual) + promotersPaid = protocolTake + R = F。绝不印钱。
|
|
312
|
+
const residual = promoterHalf - promotersPaid;
|
|
313
|
+
if (residual > 0)
|
|
314
|
+
applyWalletDelta(db, buyerId, { balance: residual });
|
|
316
315
|
return F;
|
|
317
316
|
};
|
|
318
317
|
// P0.1:RFQ 路径的 bid_stake_held — fault 时由各分支按规则处理
|
|
319
|
-
const
|
|
318
|
+
const bidStakeHeldU = toUnits(Number(order.bid_stake_held || 0));
|
|
319
|
+
// bid_stake_held 没收 50/50 的公用逻辑(中标后弃单的额外惩罚)
|
|
320
|
+
const forfeitBidStake5050 = () => {
|
|
321
|
+
if (bidStakeHeldU <= 0)
|
|
322
|
+
return;
|
|
323
|
+
applyWalletDelta(db, sellerId, { staked: -bidStakeHeldU });
|
|
324
|
+
const [compBuyer, compSys] = allocate(bidStakeHeldU, [1, 1]);
|
|
325
|
+
if (compBuyer > 0)
|
|
326
|
+
applyWalletDelta(db, buyerId, { balance: compBuyer });
|
|
327
|
+
if (compSys > 0)
|
|
328
|
+
applyWalletDelta(db, sysUserId, { balance: compSys });
|
|
329
|
+
};
|
|
320
330
|
if (faultState === 'fault_seller') {
|
|
321
331
|
// 1. buyer escrow 全额退回
|
|
322
|
-
db
|
|
323
|
-
.run(total, total, buyerId);
|
|
332
|
+
applyWalletDelta(db, buyerId, { escrowed: -totalU, balance: totalU });
|
|
324
333
|
// P0.1:bid_stake_held 没收 50/50(中标后弃单的额外惩罚)
|
|
325
|
-
|
|
326
|
-
db.prepare('UPDATE wallets SET staked = staked - ? WHERE user_id = ?').run(bidStakeHeld, sellerId);
|
|
327
|
-
const compToBuyer = Math.round(bidStakeHeld * 0.5 * 100) / 100;
|
|
328
|
-
const compToSys = Math.round((bidStakeHeld - compToBuyer) * 100) / 100;
|
|
329
|
-
if (compToBuyer > 0)
|
|
330
|
-
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compToBuyer, buyerId);
|
|
331
|
-
if (compToSys > 0)
|
|
332
|
-
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compToSys, sysUserId);
|
|
333
|
-
}
|
|
334
|
+
forfeitBidStake5050();
|
|
334
335
|
// 2. 罚没(fault_penalty_rate×total,staked 不足扣自由 balance,绝不印钱)→ RFC-007 守恒分配
|
|
335
|
-
if (
|
|
336
|
-
forfeitAndDistribute(
|
|
336
|
+
if (penaltyU > 0)
|
|
337
|
+
forfeitAndDistribute(penaltyU);
|
|
337
338
|
// 3. 库存回退(非二手)
|
|
338
339
|
if (!isSecondhand)
|
|
339
340
|
db.prepare('UPDATE products SET stock = stock + 1 WHERE id = ?').run(order.product_id);
|
|
@@ -353,21 +354,12 @@ export function settleFault(db, orderId, faultState) {
|
|
|
353
354
|
const isSelfFulfill = !order.logistics_id;
|
|
354
355
|
if (isSelfFulfill) {
|
|
355
356
|
// 1. buyer escrow 全额退回
|
|
356
|
-
db
|
|
357
|
-
.run(total, total, buyerId);
|
|
357
|
+
applyWalletDelta(db, buyerId, { escrowed: -totalU, balance: totalU });
|
|
358
358
|
// 2. bid_stake_held 没收 50/50(同 fault_seller 逻辑)
|
|
359
|
-
|
|
360
|
-
db.prepare('UPDATE wallets SET staked = staked - ? WHERE user_id = ?').run(bidStakeHeld, sellerId);
|
|
361
|
-
const compToBuyer = Math.round(bidStakeHeld * 0.5 * 100) / 100;
|
|
362
|
-
const compToSys = Math.round((bidStakeHeld - compToBuyer) * 100) / 100;
|
|
363
|
-
if (compToBuyer > 0)
|
|
364
|
-
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compToBuyer, buyerId);
|
|
365
|
-
if (compToSys > 0)
|
|
366
|
-
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compToSys, sysUserId);
|
|
367
|
-
}
|
|
359
|
+
forfeitBidStake5050();
|
|
368
360
|
// 3. 罚没(self-fulfill seller 违约;fault_penalty_rate×total,staked 不足扣自由 balance)→ RFC-007 守恒分配
|
|
369
|
-
if (
|
|
370
|
-
forfeitAndDistribute(
|
|
361
|
+
if (penaltyU > 0)
|
|
362
|
+
forfeitAndDistribute(penaltyU);
|
|
371
363
|
// 4. 库存回退
|
|
372
364
|
if (!isSecondhand)
|
|
373
365
|
db.prepare('UPDATE products SET stock = stock + 1 WHERE id = ?').run(order.product_id);
|
|
@@ -381,17 +373,13 @@ export function settleFault(db, orderId, faultState) {
|
|
|
381
373
|
else {
|
|
382
374
|
// Phase 2 logistics 市场:真正的 logistics 接单后违约
|
|
383
375
|
// 1. buyer escrow 全额退回
|
|
384
|
-
db
|
|
385
|
-
.run(total, total, buyerId);
|
|
376
|
+
applyWalletDelta(db, buyerId, { escrowed: -totalU, balance: totalU });
|
|
386
377
|
// 2. seller 无责 → bid_stake_held / stake 全额返还
|
|
387
|
-
if (
|
|
388
|
-
db
|
|
389
|
-
}
|
|
378
|
+
if (bidStakeHeldU > 0)
|
|
379
|
+
applyWalletDelta(db, sellerId, { balance: bidStakeHeldU, staked: -bidStakeHeldU });
|
|
390
380
|
// seller 无责 → 退还其【该单实际背书的 stake】(= stake_backing;起步阶段=0,无可退)
|
|
391
|
-
if (
|
|
392
|
-
db
|
|
393
|
-
.run(orderStakeBacking, orderStakeBacking, sellerId);
|
|
394
|
-
}
|
|
381
|
+
if (orderStakeBackingU > 0)
|
|
382
|
+
applyWalletDelta(db, sellerId, { staked: -orderStakeBackingU, balance: orderStakeBackingU });
|
|
395
383
|
// 3. 库存回退
|
|
396
384
|
if (!isSecondhand)
|
|
397
385
|
db.prepare('UPDATE products SET stock = stock + 1 WHERE id = ?').run(order.product_id);
|
|
@@ -415,9 +403,8 @@ export function settleFault(db, orderId, faultState) {
|
|
|
415
403
|
catch { }
|
|
416
404
|
}
|
|
417
405
|
// P0.1:买家违约 → bid_stake_held 全额返还卖家(卖家未失责)
|
|
418
|
-
if (
|
|
419
|
-
db
|
|
420
|
-
}
|
|
406
|
+
if (bidStakeHeldU > 0)
|
|
407
|
+
applyWalletDelta(db, sellerId, { balance: bidStakeHeldU, staked: -bidStakeHeldU });
|
|
421
408
|
}
|
|
422
409
|
// 标记已结算(防止重复处置)
|
|
423
410
|
db.prepare("UPDATE orders SET settled_fault_at = datetime('now') WHERE id = ?").run(orderId);
|
|
@@ -434,19 +421,20 @@ export function settleDeclinedNoFault(db, orderId) {
|
|
|
434
421
|
return;
|
|
435
422
|
if (order.settled_fault_at)
|
|
436
423
|
return; // 幂等(复用 settled_fault_at 标记)
|
|
437
|
-
const total = Number(order.total_amount);
|
|
438
424
|
const buyerId = order.buyer_id;
|
|
439
425
|
const sellerId = order.seller_id;
|
|
440
426
|
const isSecondhand = order.source === 'secondhand';
|
|
441
|
-
|
|
442
|
-
const
|
|
427
|
+
// RFC-014:整数 base-units
|
|
428
|
+
const totalU = toUnits(Number(order.total_amount));
|
|
429
|
+
const orderStakeBackingU = Math.max(0, toUnits(Number(order.stake_backing || 0)));
|
|
430
|
+
const bidStakeHeldU = toUnits(Number(order.bid_stake_held || 0));
|
|
443
431
|
// 1. 买家 escrow 全额退回
|
|
444
|
-
db
|
|
432
|
+
applyWalletDelta(db, buyerId, { escrowed: -totalU, balance: totalU });
|
|
445
433
|
// 2. 卖家质押全退(封顶其实际 staked,绝不转负)—— 无责零成本
|
|
446
|
-
const
|
|
447
|
-
const returnStake = Math.min(
|
|
434
|
+
const sellerStakedU = Math.max(0, walletUnits(db, sellerId).staked);
|
|
435
|
+
const returnStake = Math.min(orderStakeBackingU + bidStakeHeldU, sellerStakedU);
|
|
448
436
|
if (returnStake > 0)
|
|
449
|
-
db
|
|
437
|
+
applyWalletDelta(db, sellerId, { staked: -returnStake, balance: returnStake });
|
|
450
438
|
// 3. 库存 / 二手状态恢复
|
|
451
439
|
if (!isSecondhand)
|
|
452
440
|
db.prepare('UPDATE products SET stock = stock + 1 WHERE id = ?').run(order.product_id);
|