@seasonkoh/webaz 0.1.8 → 0.1.10
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/LICENSE +48 -0
- package/README.md +156 -20
- package/dist/layer0-foundation/L0-1-database/schema.js +5 -4
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +228 -7
- package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +156 -0
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +53 -12
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +14 -1
- package/dist/layer1-agent/L1-1-mcp-server/auth.js +1 -1
- package/dist/layer1-agent/L1-1-mcp-server/server.js +3679 -852
- package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +324 -0
- package/dist/layer1-agent/L1-2-identity/agent-passport.js +100 -0
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +72 -5
- package/dist/layer2-business/L2-7-snf/snf-engine.js +287 -0
- package/dist/layer2-business/L2-anchor-registry/anchor-registry.js +396 -0
- package/dist/layer2-business/L2-notes/note-photo-storage.js +133 -0
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +6 -6
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +246 -0
- package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +95 -1
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +31 -2
- package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +358 -0
- package/dist/pwa/public/app.js +31362 -2320
- package/dist/pwa/public/i18n.js +5308 -111
- package/dist/pwa/public/icon.svg +11 -0
- package/dist/pwa/public/index.html +4 -1
- package/dist/pwa/public/manifest.json +39 -4
- package/dist/pwa/public/openapi.json +5946 -0
- package/dist/pwa/public/style.css +278 -5
- package/dist/pwa/public/sw.js +41 -2
- package/dist/pwa/public/vendor/jsQR.js +10102 -0
- package/dist/pwa/public/webaz-logo.png +0 -0
- package/dist/pwa/routes/account-deletion.js +53 -0
- package/dist/pwa/routes/addresses.js +105 -0
- package/dist/pwa/routes/admin-admins.js +151 -0
- package/dist/pwa/routes/admin-analytics.js +253 -0
- package/dist/pwa/routes/admin-atomic.js +21 -0
- package/dist/pwa/routes/admin-catalog.js +64 -0
- package/dist/pwa/routes/admin-editor-picks.js +45 -0
- package/dist/pwa/routes/admin-events.js +60 -0
- package/dist/pwa/routes/admin-health.js +66 -0
- package/dist/pwa/routes/admin-moderation.js +120 -0
- package/dist/pwa/routes/admin-ops.js +179 -0
- package/dist/pwa/routes/admin-protocol-params.js +79 -0
- package/dist/pwa/routes/admin-reports.js +154 -0
- package/dist/pwa/routes/admin-tokenomics.js +113 -0
- package/dist/pwa/routes/admin-users-lifecycle.js +237 -0
- package/dist/pwa/routes/admin-users-query.js +390 -0
- package/dist/pwa/routes/admin-verifier-flow.js +126 -0
- package/dist/pwa/routes/admin-verifier-whitelist.js +111 -0
- package/dist/pwa/routes/admin-wallet-ops.js +66 -0
- package/dist/pwa/routes/agent-buy.js +215 -0
- package/dist/pwa/routes/agent-governance.js +386 -0
- package/dist/pwa/routes/agent-reputation.js +34 -0
- package/dist/pwa/routes/ai.js +101 -0
- package/dist/pwa/routes/analytics.js +272 -0
- package/dist/pwa/routes/anchors.js +169 -0
- package/dist/pwa/routes/announcements.js +110 -0
- package/dist/pwa/routes/ap2-mandate.js +121 -0
- package/dist/pwa/routes/arbitrator.js +117 -0
- package/dist/pwa/routes/auction.js +436 -0
- package/dist/pwa/routes/auth-login.js +40 -0
- package/dist/pwa/routes/auth-read.js +66 -0
- package/dist/pwa/routes/auth-register.js +161 -0
- package/dist/pwa/routes/auth-sessions.js +62 -0
- package/dist/pwa/routes/blocklist.js +60 -0
- package/dist/pwa/routes/buyer-feeds.js +224 -0
- package/dist/pwa/routes/cart.js +155 -0
- package/dist/pwa/routes/charity.js +816 -0
- package/dist/pwa/routes/chat.js +318 -0
- package/dist/pwa/routes/checkin-tasks.js +122 -0
- package/dist/pwa/routes/checkout-helpers.js +106 -0
- package/dist/pwa/routes/claim-initiators.js +88 -0
- package/dist/pwa/routes/claim-verify.js +615 -0
- package/dist/pwa/routes/claim-voting.js +114 -0
- package/dist/pwa/routes/claim-withdrawals.js +20 -0
- package/dist/pwa/routes/coupons.js +165 -0
- package/dist/pwa/routes/dashboards.js +99 -0
- package/dist/pwa/routes/dispute-cases.js +267 -0
- package/dist/pwa/routes/disputes-read.js +358 -0
- package/dist/pwa/routes/disputes-write.js +475 -0
- package/dist/pwa/routes/evidence.js +86 -0
- package/dist/pwa/routes/external-anchors.js +107 -0
- package/dist/pwa/routes/feedback.js +270 -0
- package/dist/pwa/routes/flash-sales.js +130 -0
- package/dist/pwa/routes/follows.js +103 -0
- package/dist/pwa/routes/group-buys.js +208 -0
- package/dist/pwa/routes/growth.js +199 -0
- package/dist/pwa/routes/import-product.js +153 -0
- package/dist/pwa/routes/kyc.js +40 -0
- package/dist/pwa/routes/leaderboard.js +149 -0
- package/dist/pwa/routes/listings.js +281 -0
- package/dist/pwa/routes/logistics.js +35 -0
- package/dist/pwa/routes/manifests.js +126 -0
- package/dist/pwa/routes/me-data.js +101 -0
- package/dist/pwa/routes/notifications.js +48 -0
- package/dist/pwa/routes/offers.js +96 -0
- package/dist/pwa/routes/orders-action.js +285 -0
- package/dist/pwa/routes/orders-create.js +382 -0
- package/dist/pwa/routes/orders-read.js +180 -0
- package/dist/pwa/routes/p2p-products.js +178 -0
- package/dist/pwa/routes/payments-governance.js +311 -0
- package/dist/pwa/routes/peers.js +34 -0
- package/dist/pwa/routes/pin-receipts.js +39 -0
- package/dist/pwa/routes/products-aliases.js +119 -0
- package/dist/pwa/routes/products-claims.js +60 -0
- package/dist/pwa/routes/products-create.js +206 -0
- package/dist/pwa/routes/products-crud.js +73 -0
- package/dist/pwa/routes/products-links.js +129 -0
- package/dist/pwa/routes/products-list.js +424 -0
- package/dist/pwa/routes/products-meta.js +155 -0
- package/dist/pwa/routes/products-update.js +125 -0
- package/dist/pwa/routes/profile-credentials.js +105 -0
- package/dist/pwa/routes/profile-identity.js +174 -0
- package/dist/pwa/routes/profile-location.js +35 -0
- package/dist/pwa/routes/profile-placement.js +70 -0
- package/dist/pwa/routes/profile-prefs.js +93 -0
- package/dist/pwa/routes/promoter.js +208 -0
- package/dist/pwa/routes/public-utils.js +227 -0
- package/dist/pwa/routes/push.js +54 -0
- package/dist/pwa/routes/ratings.js +220 -0
- package/dist/pwa/routes/recover-key.js +100 -0
- package/dist/pwa/routes/referral.js +58 -0
- package/dist/pwa/routes/reputation.js +34 -0
- package/dist/pwa/routes/returns.js +493 -0
- package/dist/pwa/routes/reviews.js +81 -0
- package/dist/pwa/routes/rfqs.js +443 -0
- package/dist/pwa/routes/search.js +172 -0
- package/dist/pwa/routes/secondhand.js +278 -0
- package/dist/pwa/routes/seller-quota.js +225 -0
- package/dist/pwa/routes/share-redirects.js +164 -0
- package/dist/pwa/routes/shareables-interactions.js +212 -0
- package/dist/pwa/routes/shareables.js +470 -0
- package/dist/pwa/routes/shops.js +98 -0
- package/dist/pwa/routes/signaling.js +43 -0
- package/dist/pwa/routes/skill-market.js +173 -0
- package/dist/pwa/routes/skills.js +174 -0
- package/dist/pwa/routes/snf.js +126 -0
- package/dist/pwa/routes/tags.js +47 -0
- package/dist/pwa/routes/trial.js +333 -0
- package/dist/pwa/routes/trusted-kpi.js +87 -0
- package/dist/pwa/routes/url-claim.js +113 -0
- package/dist/pwa/routes/users-public.js +317 -0
- package/dist/pwa/routes/variants.js +156 -0
- package/dist/pwa/routes/verifier-user.js +107 -0
- package/dist/pwa/routes/verify-tasks.js +120 -0
- package/dist/pwa/routes/waitlist.js +65 -0
- package/dist/pwa/routes/wallet-read.js +218 -0
- package/dist/pwa/routes/wallet-write.js +273 -0
- package/dist/pwa/routes/webauthn.js +188 -0
- package/dist/pwa/routes/webhooks.js +162 -0
- package/dist/pwa/routes/welcome.js +226 -0
- package/dist/pwa/routes/wishlist-qa.js +135 -0
- package/dist/pwa/security/ssrf.js +110 -0
- package/dist/pwa/server.js +9317 -2097
- package/package.json +8 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
Parameters
|
|
5
|
+
|
|
6
|
+
Licensor: seasonsagents-art
|
|
7
|
+
Licensed Work: WebAZ
|
|
8
|
+
Copyright © 2026 seasonsagents-art and contributors
|
|
9
|
+
Additional Use Grant: You may make production use of the Licensed Work,
|
|
10
|
+
provided that your use does not include offering
|
|
11
|
+
the Licensed Work to third parties as a hosted or
|
|
12
|
+
managed service that is substantially similar to,
|
|
13
|
+
or competes with, the WebAZ commerce protocol
|
|
14
|
+
service operated by the Licensor.
|
|
15
|
+
Change Date: 2030-05-18
|
|
16
|
+
Change License: MIT
|
|
17
|
+
|
|
18
|
+
For information about alternative licensing arrangements for the
|
|
19
|
+
Licensed Work, please contact seasons.agents@gmail.com.
|
|
20
|
+
License text copyright © 2024 MariaDB plc, All Rights Reserved. “Business Source License” is a trademark of MariaDB plc.
|
|
21
|
+
|
|
22
|
+
Terms
|
|
23
|
+
The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use.
|
|
24
|
+
|
|
25
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate.
|
|
26
|
+
|
|
27
|
+
If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work.
|
|
28
|
+
|
|
29
|
+
All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor.
|
|
30
|
+
|
|
31
|
+
You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work.
|
|
32
|
+
|
|
33
|
+
Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work.
|
|
34
|
+
|
|
35
|
+
This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License).TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this License’s text to license your works, and to refer to it using the trademark “Business Source License”, as long as you comply with the Covenants of Licensor below.
|
|
36
|
+
|
|
37
|
+
Covenants of Licensor
|
|
38
|
+
In consideration of the right to use this License’s text and the “Business Source License” name and trademark, Licensor covenants to MariaDB, and to all other recipients of the licensed work to be provided by Licensor:
|
|
39
|
+
|
|
40
|
+
To specify as the Change License the GPL Version 2.0 or any later version, or a license that is compatible with GPL Version 2.0 or a later version, where “compatible” means that software provided under the Change License can be included in a program with software provided under GPL Version 2.0 or a later version. Licensor may specify additional Change Licenses without limitation.
|
|
41
|
+
To either: (a) specify an additional grant of rights to use that does not impose any additional restriction on the right granted in this License, as the Additional Use Grant; or (b) insert the text “None” to specify a Change Date. Not to modify this License in any other way.
|
|
42
|
+
|
|
43
|
+
Notice
|
|
44
|
+
The Business Source License (this document, or the “License”) is not an Open Source license. However, the Licensed Work will eventually be made available under an Open Source License, as stated in this License.
|
|
45
|
+
|
|
46
|
+
For more information on the use of the Business Source License for MariaDB products, please visit the MariaDB Business Source License FAQ. For more information on the use of the Business Source License generally, please visit the Adopting and Developing Business Source License FAQ.
|
|
47
|
+
|
|
48
|
+
Projects using BSL
|
package/README.md
CHANGED
|
@@ -1,23 +1,86 @@
|
|
|
1
|
+
> **Code is Rule, Protocol is Trust.**
|
|
2
|
+
> **代码即规则,协议即信任。**
|
|
3
|
+
> — webaz
|
|
4
|
+
|
|
1
5
|
# WebAZ
|
|
2
6
|
|
|
7
|
+
[](https://www.npmjs.com/package/@seasonkoh/webaz)
|
|
8
|
+
[](https://registry.modelcontextprotocol.io/v0/servers?search=webaz)
|
|
9
|
+
[](LICENSE) [](NOTICE)
|
|
10
|
+
|
|
3
11
|
让 AI Agent 成为去中心化商业协议的原生参与者。卖家零额外工作量接入新渠道,买家通过 Agent 自动购物,人类与 AI 在同一协议上平等参与。
|
|
4
12
|
|
|
13
|
+
> ⭐ **运营哲学与远景** → [`docs/VISION-AND-OPERATIONS.md`](docs/VISION-AND-OPERATIONS.md)
|
|
14
|
+
> COP(参与即建设,贡献即收益)· 团队"消失"在协议里 · 最终全 DAO
|
|
15
|
+
|
|
16
|
+
> **试一下**:`npx -y @seasonkoh/webaz`,或在 Claude Desktop 加入 MCP 配置(见下)。
|
|
17
|
+
> **PWA 演示**:[webaz.xyz](https://webaz.xyz)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📜 授权说明
|
|
22
|
+
|
|
23
|
+
自 **2026-05-18** 起,本项目使用 **Business Source License 1.1 (BUSL-1.1)** 协议发布。
|
|
24
|
+
|
|
25
|
+
- ✅ 允许:内部使用、研究、二次开发、非竞争性商业使用
|
|
26
|
+
- ❌ 禁止:运营与 WebAZ 实质相似的去中心化商业协议托管服务
|
|
27
|
+
- ⏰ **2030-05-18** 之后,本协议自动转为 **MIT** 协议
|
|
28
|
+
- 📦 **2026-05-18 之前**发布的所有版本(含历史 git 提交)仍持有 **MIT** 授权,不可撤销
|
|
29
|
+
|
|
30
|
+
详见 [`LICENSE`](LICENSE) 与 [`NOTICE`](NOTICE)。
|
|
31
|
+
|
|
5
32
|
---
|
|
6
33
|
|
|
7
34
|
## 核心特性
|
|
8
35
|
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
36
|
+
### 协议骨架
|
|
37
|
+
- **Agent 原生**:MCP 协议下,Claude 直接搜索、锁价、下单、举证、确认
|
|
38
|
+
- **人类 + Agent 双轨**:PWA 给人类,MCP 给 Agent,共用同一后端
|
|
39
|
+
- **协议级精准镜像**:q= 严格 exact match,"茶" 不会命中"茶具套装";买家从外部平台复制的标识必须字面相等才返回结果(见 [intent-driven-buy](docs/modules/intent-driven-buy.md))
|
|
40
|
+
- **商品 alias 系统**:卖家声明 kouling_token / short_url / title_substring,让用户从外部平台的任何复制形式都能精准命中同一 SKU(见 [product-aliases](docs/modules/product-aliases.md))
|
|
41
|
+
- **角色感知 API**:`/api/products?mode=pwa|agent|raw` 三种调用方分级;agent 模式返回 score_breakdown,raw 模式 HMAC 签名(见 [products-api](docs/modules/products-api.md))
|
|
42
|
+
- **Agent Reputation 体系**:每个 api_key 独立 trust_score(4 layer band),raw mode 需 trust ≥ 30(见 [agent-reputation](docs/modules/agent-reputation.md))
|
|
43
|
+
|
|
44
|
+
### 多元交易场景
|
|
45
|
+
- **🆕 多商家跟卖 (P1)**:listings 1:N offers,4 类目 stake 体系(standard 1.0× / general 1.5× / highvalue 2.0× / restricted 3.0× × base 50 WAZ)
|
|
46
|
+
- **🆕 加权排序 + urgency 路由 (P2)**:trending score 含完成度/信誉/分享/点赞/新鲜度/季节性;urgency 三档 (now/today/flex) 路由不同 ETA 硬限
|
|
47
|
+
- **🆕 RFQ 求购抢单 (P3)**:买家发求购,卖家限时报价;first_match 即时触发 / 提前结算 / 自然语言预填 / auto_bid Skill(见 [rfq-auction-chat](docs/modules/rfq-auction-chat.md))
|
|
48
|
+
- **🆕 加价拍卖 AUC**:English forward auction;反狙击 sniper_extend_min + max_extends cap;卖家担保金 5%
|
|
49
|
+
- **🆕 P2P 原生商店**:无外链商品的去中心化路径;卖家本地节点存详情,WebAZ 锚定 hash + HMAC 签名 + 客户端 sha256 校验
|
|
50
|
+
- **🆕 上下文绑定聊天**:order / RFQ / listing_qa 三类 chat;反诈正则检测微信号/电话/银行卡
|
|
51
|
+
|
|
52
|
+
### 信任与合规
|
|
53
|
+
- **claim 验证 + 条件订单**:买家可对推荐理由发起验证,3 verifier 共识仲裁,4 路径结算(pass / fail / no_fault / timeout)+ outlier 处罚(见 [claim-verification](docs/modules/claim-verification.md))
|
|
54
|
+
- **地区合规分润**:china/us/eu/india = 2 levels;其他 3 levels;UI 按地区动态显示
|
|
55
|
+
- **零门槛上架**:卖家上架免质押,首单成交时从 escrow 自动锁 15% stake(trusted 卖家跳过)
|
|
56
|
+
- **争议系统**:双方举证 → 仲裁员裁定 → 败诉方缴 1% 仲裁费(含超时自动判)
|
|
57
|
+
- **链接认领验证**:卖家关联外部链接需通过众包验证码核验,防止他人冒用商品主权
|
|
58
|
+
|
|
59
|
+
### 决策辅助
|
|
60
|
+
- **🆕 价格历史 + 成交量分布**:3 窗口(30d/90d/lifetime) × 6 统计;价位分布 / sparkline / category_avg / anomaly_flags;防底价倾销
|
|
61
|
+
- **结构化商品 + agent_summary**:规格 / 物流 / 售后字段拼成一句话决策摘要
|
|
62
|
+
- **下单前价格锁定**:`webaz_verify_price` 返回 `session_token`,避免决策与下单之间价格漂移
|
|
63
|
+
|
|
64
|
+
### 社会化 / 公益
|
|
65
|
+
- **🆕 慈善许愿池**:双匿名(HMAC 派生 handle)+ 双签锚定(commit_hash)+ 反自圆梦封锁;许愿/圆梦/还愿/转捐 4 套荣誉细分(见 [charity](docs/modules/charity.md))
|
|
66
|
+
- **🆕 慈善基金**:还愿被谢绝自动转入;任何人可捐款(≤500 WAZ/愿,1 WAZ = 1 honor 上限 50/日);公开账本 + 慈善家排行
|
|
67
|
+
- **🆕 点赞 + 排行榜**:shareable_likes 防 Sybil;商品榜 (sales+推荐+点赞) + 创作者榜
|
|
68
|
+
- **Skill 市场**:catalog_sync / auto_accept / instant_ship / auto_bid 等插件,Agent 订阅自动享用
|
|
69
|
+
- **声誉 5 级**(新手 → 传奇):影响质押折扣 + 搜索排名
|
|
70
|
+
|
|
71
|
+
### 链上 / 安全
|
|
72
|
+
- **链上托管**:USDC on Base Sepolia,充值地址按用户派生,自动扫归集 + 自动执行提现
|
|
73
|
+
- **WebAuthn / Passkey** 大额操作闸门
|
|
74
|
+
- **storage.persist()** 申请 + 持久化状态可视
|
|
75
|
+
- **自动执法**:状态机责任归因,超时自动判责(每 5 分钟 cron 扫)
|
|
16
76
|
|
|
17
77
|
---
|
|
18
78
|
|
|
19
79
|
## 快速开始
|
|
20
80
|
|
|
81
|
+
> **自部署?** → 看 [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md)(30 分钟从零到上线 · systemd + Nginx + Let's Encrypt + 监控 + 备份完整 cookbook)
|
|
82
|
+
> **环境变量?** → 复制 [`.env.example`](.env.example) 为 `.env` 填值
|
|
83
|
+
|
|
21
84
|
### 方式一:Claude MCP 接入(Agent 原生体验)
|
|
22
85
|
|
|
23
86
|
**1. 添加到 Claude Desktop 配置**
|
|
@@ -66,15 +129,28 @@ npm run pwa
|
|
|
66
129
|
|------|------|----------|
|
|
67
130
|
| `webaz_info` | 获取协议概览和实时统计 | — |
|
|
68
131
|
| `webaz_register` | 注册账号,获取 api_key | `name`, `role` |
|
|
69
|
-
| `webaz_search` |
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
132
|
+
| `webaz_search` | 搜索商品(结构化字段 + agent_summary + 声誉加权) | `query`, `category`, `max_price`, `min_return_days`, `max_handling_hours` |
|
|
133
|
+
| `webaz_verify_price` | 下单前锁价 10 分钟,拿到 `session_token` | `product_id`, `quantity`, `api_key` |
|
|
134
|
+
| `webaz_list_product` | 卖家上架商品(含品牌/规格/物流/售后字段) | `title`, `price`, `specs`, `handling_hours`, `return_days`, `api_key` |
|
|
135
|
+
| `webaz_place_order` | 买家下单(可传 `session_token` 防价格漂移) | `product_id`, `shipping_address`, `session_token`, `api_key` |
|
|
72
136
|
| `webaz_update_order` | 更新订单状态(接单/发货/确认/争议) | `order_id`, `action`, `api_key` |
|
|
73
137
|
| `webaz_get_status` | 查询订单/钱包/争议详情 | `order_id` / `wallet` / `dispute_id`, `api_key` |
|
|
74
|
-
| `webaz_wallet` |
|
|
138
|
+
| `webaz_wallet` | 钱包余额 + 链上充值地址 + 提现 | `action`, `api_key` |
|
|
75
139
|
| `webaz_notifications` | 查询未读通知 | `api_key` |
|
|
76
140
|
| `webaz_dispute` | 争议操作(查看/举证/裁定) | `action`, `api_key` |
|
|
77
|
-
| `webaz_skill` | Skill
|
|
141
|
+
| `webaz_skill` | Skill 市场(发布/订阅;含 auto_bid) | `action`, `api_key` |
|
|
142
|
+
| `webaz_profile` | 个人资料 + 多角色管理 | `action`, `api_key` |
|
|
143
|
+
| `webaz_mykey` | api_key 恢复(已注册用户重新获取密钥) | `name`, `recovery_code` |
|
|
144
|
+
| `webaz_rfq` | 🆕 RFQ 求购(发布/浏览/认领/详情) | `action`, `api_key` |
|
|
145
|
+
| `webaz_bid` | 🆕 RFQ 报价(创建/修改/撤销/中标) | `action`, `rfq_id`, `api_key` |
|
|
146
|
+
| `webaz_chat` | 🆕 上下文绑定聊天(order/rfq/listing_qa) | `action`, `context_id`, `api_key` |
|
|
147
|
+
| `webaz_auto_bid` | 🆕 卖家自动报价 Skill 配置 | `action`, `api_key` |
|
|
148
|
+
| `webaz_auction` | 🆕 加价拍卖(发起/浏览/出价/中标) | `action`, `api_key` |
|
|
149
|
+
| `webaz_like` | 🆕 分享点赞(toggle/status) | `action`, `shareable_id`, `api_key` |
|
|
150
|
+
| `webaz_leaderboard` | 🆕 排行榜(products / creators) | `kind`, `limit` |
|
|
151
|
+
| `webaz_p2p_product` | 🆕 P2P 原生商店(卖家节点 hash 锚定) | `action`, `api_key` |
|
|
152
|
+
| `webaz_price_history` | 🆕 商品历史成交价 + 价位分布 | `product_id` |
|
|
153
|
+
| `webaz_charity` | 🆕 慈善许愿池 + 还愿 + 基金 + 捐款(11 sub-action) | `action`, `api_key` |
|
|
78
154
|
|
|
79
155
|
完整协议规范(状态机/经济模型/争议规则)可通过 MCP Resource 读取:
|
|
80
156
|
|
|
@@ -127,7 +203,8 @@ webaz://protocol/manifest
|
|
|
127
203
|
| 协议费 | 2% |
|
|
128
204
|
| 推荐人(如有) | 3% |
|
|
129
205
|
|
|
130
|
-
|
|
206
|
+
卖家上架**零质押**(M7.2.6 起)。首单成交时自动从订单 escrow 锁定 15% 作买家保护,
|
|
207
|
+
信誉 ≥ trusted 的卖家直接跳过 stake 锁定(信任奖励)。
|
|
131
208
|
|
|
132
209
|
---
|
|
133
210
|
|
|
@@ -159,23 +236,82 @@ npm run test-manifest# 测试协议 Manifest
|
|
|
159
236
|
|
|
160
237
|
## 当前阶段
|
|
161
238
|
|
|
162
|
-
**Phase 0 · 概念验证** ✅
|
|
163
|
-
**Phase 1 ·
|
|
239
|
+
**Phase 0 · 概念验证** ✅
|
|
240
|
+
**Phase 1 · 功能完善 + 链上 testnet 闭环** ✅
|
|
241
|
+
- 14 个 MCP 工具 / 全角色 PWA / 通知 / 争议 / 声誉 / Skill 市场 / Manifest
|
|
242
|
+
- USDC on Base Sepolia:派生充值地址、自动监听入账、热钱包扫归集、自动执行提现
|
|
243
|
+
- 链接认领验证(众包验证码 + 主权流转)
|
|
244
|
+
- Agent 决策三件套(结构化字段 / agent_summary / verify_price 锁价)
|
|
245
|
+
- MCP 工具调用遥测(默认开,可关)
|
|
164
246
|
|
|
165
|
-
Phase 2
|
|
247
|
+
**Phase 2 · 主网 + 真正去中心化**(下一步)
|
|
166
248
|
|
|
167
249
|
---
|
|
168
250
|
|
|
169
251
|
## 路线图
|
|
170
252
|
|
|
253
|
+
### Phase 1 完成项
|
|
171
254
|
- [x] 状态机 + 责任归因引擎
|
|
172
|
-
- [x] MCP Server(
|
|
255
|
+
- [x] MCP Server(14+ 个工具)
|
|
173
256
|
- [x] 通知系统(SSE 实时推送)
|
|
174
257
|
- [x] 争议系统(举证 + 超时自动裁定 + 仲裁费)
|
|
175
|
-
- [x]
|
|
258
|
+
- [x] 声誉积分体系(5 级)
|
|
176
259
|
- [x] Skill 市场
|
|
177
260
|
- [x] Protocol Manifest(机器可读协议规范)
|
|
178
261
|
- [x] PWA 前端(全角色覆盖,人类 + Agent 双轨)
|
|
179
|
-
- [
|
|
180
|
-
- [
|
|
262
|
+
- [x] 智能商品导入(贴链接自动提取)
|
|
263
|
+
- [x] 链接认领验证(卖家主权 + 众包核验)
|
|
264
|
+
- [x] 链上 USDC testnet 闭环(Base Sepolia)
|
|
265
|
+
- [x] 下单前价格锁定(verify_price + session_token)
|
|
266
|
+
- [x] 结构化商品规格 + agent_summary 决策摘要
|
|
267
|
+
- [x] 遥测看板(/api/admin/usage)
|
|
268
|
+
|
|
269
|
+
### M1-M7 协议级里程碑(2026-05 完成)
|
|
270
|
+
- [x] **M1** 角色感知 API(mode=pwa/agent/raw)+ cursor 分页
|
|
271
|
+
- [x] **M2** 基础排序公平(jitter / seller cap / 新人 slot)
|
|
272
|
+
- [x] **M3** 反操纵层(click 去重 / 双轨同支审计 / 上架限速 / 注册 IP hash)
|
|
273
|
+
- [x] **M4** Agent Reputation 体系(trust_score + raw mode 门槛 + HMAC 签名)
|
|
274
|
+
- [x] **M5** 新人保护 + 阶梯新鲜度 + sort UI
|
|
275
|
+
- [x] **M6** 商品类型 + 库存稀缺 + 季节性 lifecycle
|
|
276
|
+
- [x] **M7.1** 智能下单 intent-driven UI 重做
|
|
277
|
+
- [x] **M7.1.5** q= exact match 协议契约
|
|
278
|
+
- [x] **M7.2** 商品 alias 系统(kouling_token / title_substring / short_url)
|
|
279
|
+
- [x] **M7.2.5/6/7** 卖家 funnel + 地区合规(2/3 级分润)+ L3 区域感知
|
|
280
|
+
- [x] **代码审计** H-1 / H-2 / M-1 / M-2 / M-5 / 3f254b8 完整批次
|
|
281
|
+
- [x] **M7.3** claim 验证任务系统(4 路径结算 + outlier 处罚)
|
|
282
|
+
- [x] **M7.4** 条件订单(has_pending_claim 暂缓自动判责)
|
|
283
|
+
|
|
284
|
+
### P1-P3 多元交易场景(2026-05-18+)
|
|
285
|
+
- [x] **P1** 多商家跟卖(listings + 4 类目 stake)
|
|
286
|
+
- [x] **P2** 加权排序 + urgency 路由 + cold-start 衰减
|
|
287
|
+
- [x] **P3 RFQ** 求购抢单 + bid_stake + first_match + auto_bid Skill + 买家 NLP Agent
|
|
288
|
+
- [x] **CHAT** 上下文绑定聊天 + 反诈正则
|
|
289
|
+
- [x] **AUC** 加价拍卖(English forward + 反狙击)
|
|
290
|
+
- [x] **LIKE + Leaderboard** 分享点赞 + 商品/创作者双榜
|
|
291
|
+
- [x] **P2P 原生商店** 卖家节点 hash 锚定 + HMAC 签名 + 客户端 sha256
|
|
292
|
+
- [x] **Price History** 历史成交价 + 价位分布 + 异常预警
|
|
293
|
+
|
|
294
|
+
### 慈善 + 社会化(2026-05-18 完成)
|
|
295
|
+
- [x] **慈善许愿池** 双匿名 + 双签锚定 + 4 张表 + 6 前端页 + 反自圆梦封锁
|
|
296
|
+
- [x] **还愿系统** 三态响应(accept / decline_to_fund / 7 天 auto-accept)
|
|
297
|
+
- [x] **慈善基金** 单例池 + 任何人捐款 + 慈善家排行 + 公开账本
|
|
298
|
+
- [x] **5 项荣誉细分**(repay/redirect/grace/donation_total/donation_honor)
|
|
299
|
+
- [x] **16 项审计修复**(4 处竞态 + 6 项业务/隐私 + 6 项工程)
|
|
300
|
+
|
|
301
|
+
### UX 重构(2026-05-19 完成)
|
|
302
|
+
- [x] **导航重组** 角色化 tab bar(买家 5 / 卖家 5)
|
|
303
|
+
- [x] **#me 私人 hub** 三段式(通用 6 / 角色专区 / 账户)
|
|
304
|
+
- [x] **#discover 商务横条** 6 板块快捷入口(拍卖/求购/跟卖/P2P/排行/慈善)
|
|
305
|
+
- [x] **i18n 双语全覆盖** 2700+ 条目;服务端按 Accept-Language 返回
|
|
306
|
+
|
|
307
|
+
### 进行中 / 下一步
|
|
308
|
+
- [ ] **跨模块审计收尾**(alias 限额 TOCTOU / settleOrder 原子化 / 杂项硬化)
|
|
309
|
+
- [ ] **慈善基金 v2**:自动拨款机制(紧急医疗/教育大额匹配)+ 捐物(specific event 定向)
|
|
310
|
+
- [ ] **导航 v2**:4 角色(logistics/verifier/arbitrator/admin)也加 #me 入口完整化
|
|
311
|
+
|
|
312
|
+
### 长期
|
|
313
|
+
- [ ] 链上 USDC 主网(Base)
|
|
314
|
+
- [ ] IPFS 证据存储 + Pin Network(已搭骨架,留 P2)
|
|
315
|
+
- [ ] 评价系统(结构化 1-5 星,反哺声誉)
|
|
316
|
+
- [ ] 证据上传通道(争议附图)
|
|
181
317
|
- [ ] 治理 DAO
|
|
@@ -6,6 +6,7 @@ import Database from 'better-sqlite3';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import os from 'os';
|
|
8
8
|
import fs from 'fs';
|
|
9
|
+
import { randomBytes } from 'crypto';
|
|
9
10
|
const DATA_DIR = path.join(os.homedir(), '.webaz');
|
|
10
11
|
if (!fs.existsSync(DATA_DIR))
|
|
11
12
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
@@ -181,9 +182,9 @@ export function initDatabase() {
|
|
|
181
182
|
console.error('✅ L0-1 数据库初始化完成:', DB_PATH);
|
|
182
183
|
return db;
|
|
183
184
|
}
|
|
184
|
-
// 生成唯一 ID 的工具函数
|
|
185
|
+
// 生成唯一 ID 的工具函数 — crypto-safe,128 bit 熵
|
|
186
|
+
// Why: 旧版用 Date.now() + Math.random() 4 字符,api_key 仅 4 字符随机 → 36⁴≈168 万种秒破;
|
|
187
|
+
// 且 api_key / user_id 共享时间戳前缀泄漏关联(QA agent 抓到)。改用 crypto.randomBytes 后两个 ID 互不相关。
|
|
185
188
|
export function generateId(prefix) {
|
|
186
|
-
|
|
187
|
-
const random = Math.random().toString(36).slice(2, 6);
|
|
188
|
-
return `${prefix}_${timestamp}${random}`;
|
|
189
|
+
return `${prefix}_${randomBytes(16).toString('hex')}`;
|
|
189
190
|
}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* 3. getStatus() — 查询订单当前状态和责任方
|
|
8
8
|
*/
|
|
9
9
|
import { generateId } from '../L0-1-database/schema.js';
|
|
10
|
-
import {
|
|
10
|
+
import { appendOrderEvent } from './order-chain.js';
|
|
11
|
+
import { VALID_TRANSITIONS, CURRENT_RESPONSIBLE, CURRENT_RESPONSIBLE_SELF_FULFILL } from './transitions.js';
|
|
11
12
|
// ─── 核心函数 ────────────────────────────────────────────────
|
|
12
13
|
/**
|
|
13
14
|
* 执行状态转移
|
|
@@ -60,12 +61,28 @@ export function transition(db, orderId, toStatus, actorId, evidenceIds = [], not
|
|
|
60
61
|
SET status = ?, updated_at = datetime('now')
|
|
61
62
|
WHERE id = ?
|
|
62
63
|
`).run(toStatus, orderId);
|
|
63
|
-
//
|
|
64
|
+
// 记录状态历史(人类可读层)
|
|
64
65
|
db.prepare(`
|
|
65
66
|
INSERT INTO order_state_history
|
|
66
67
|
(id, order_id, from_status, to_status, actor_id, actor_role, evidence_ids, notes)
|
|
67
68
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
68
69
|
`).run(historyId, orderId, fromStatus, toStatus, actorId, actor.role, JSON.stringify(evidenceIds), notes);
|
|
70
|
+
// 协议层:append-only 签名链事件(防篡改 + 防伪造)
|
|
71
|
+
try {
|
|
72
|
+
appendOrderEvent(db, {
|
|
73
|
+
orderId,
|
|
74
|
+
eventType: 'transition',
|
|
75
|
+
fromStatus,
|
|
76
|
+
toStatus,
|
|
77
|
+
actorId,
|
|
78
|
+
actorRole: actor.role,
|
|
79
|
+
extra: { evidence_ids: evidenceIds, notes: notes || null, history_id: historyId },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
// 签名链失败不应阻塞主流程(旧数据 / 老 actor 缺 api_key 等场景)
|
|
84
|
+
console.warn('[order-chain] appendEvent failed:', e.message);
|
|
85
|
+
}
|
|
69
86
|
});
|
|
70
87
|
execute();
|
|
71
88
|
return { success: true, newStatus: toStatus, historyId };
|
|
@@ -78,9 +95,11 @@ export function checkTimeouts(db) {
|
|
|
78
95
|
const now = new Date().toISOString();
|
|
79
96
|
const details = [];
|
|
80
97
|
// 找出所有进行中的订单
|
|
98
|
+
// M7.4:跳过 claim 验证进行中的订单(has_pending_claim=1)— auto-confirm / 判责暂缓
|
|
81
99
|
const activeOrders = db.prepare(`
|
|
82
100
|
SELECT * FROM orders
|
|
83
101
|
WHERE status NOT IN ('completed', 'cancelled', 'fault_buyer', 'fault_seller', 'fault_logistics')
|
|
102
|
+
AND COALESCE(has_pending_claim, 0) = 0
|
|
84
103
|
`).all();
|
|
85
104
|
for (const order of activeOrders) {
|
|
86
105
|
const transitionKey = findActiveDeadlineTransition(order, now);
|
|
@@ -95,10 +114,16 @@ export function checkTimeouts(db) {
|
|
|
95
114
|
orderId: order.id,
|
|
96
115
|
action: `${order.status} → ${autoFaultState}(超时自动判责)`
|
|
97
116
|
});
|
|
98
|
-
// 如果判责状态可以自动完成,继续执行
|
|
117
|
+
// 如果判责状态可以自动完成,继续执行 — 资金处置 + 状态转移在一个事务内
|
|
99
118
|
const completionKey = `${autoFaultState}→completed`;
|
|
100
119
|
if (VALID_TRANSITIONS[completionKey]) {
|
|
101
|
-
|
|
120
|
+
try {
|
|
121
|
+
settleFault(db, order.id, autoFaultState);
|
|
122
|
+
transition(db, order.id, 'completed', systemUser.id, [], '系统自动执行处置');
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
console.error(`[settleFault] order=${order.id} state=${autoFaultState}`, e);
|
|
126
|
+
}
|
|
102
127
|
}
|
|
103
128
|
}
|
|
104
129
|
}
|
|
@@ -118,8 +143,11 @@ export function getOrderStatus(db, orderId) {
|
|
|
118
143
|
WHERE h.order_id = ?
|
|
119
144
|
ORDER BY h.created_at ASC
|
|
120
145
|
`).all(orderId);
|
|
121
|
-
|
|
122
|
-
const
|
|
146
|
+
// QA 轮 7 P1:self-fulfill 时 logistics_id 为 null,应该按 seller 表示责任方
|
|
147
|
+
const isSelfFulfill = !order.logistics_id;
|
|
148
|
+
const responsibleTable = isSelfFulfill ? CURRENT_RESPONSIBLE_SELF_FULFILL : CURRENT_RESPONSIBLE;
|
|
149
|
+
const currentResponsible = responsibleTable[order.status] ?? null;
|
|
150
|
+
const activeDeadline = getActiveDeadline(order, db);
|
|
123
151
|
return {
|
|
124
152
|
order,
|
|
125
153
|
history,
|
|
@@ -128,6 +156,180 @@ export function getOrderStatus(db, orderId) {
|
|
|
128
156
|
isOverdue: activeDeadline ? new Date() > new Date(activeDeadline.deadline) : false
|
|
129
157
|
};
|
|
130
158
|
}
|
|
159
|
+
// ─── 超时判责资金处置 ────────────────────────────────────────
|
|
160
|
+
// fault_*→completed 转移时由 checkTimeouts 调用,确保 escrow / stake / 库存 全部正确清算
|
|
161
|
+
// 设计原则:
|
|
162
|
+
// - fault_seller → buyer 全额退款 + stake 50/50 扣罚(与 dispute refund_buyer 对等)
|
|
163
|
+
// - fault_logistics→ buyer 全额退款 + seller stake 全额返还(卖家无责,物流坏账协议吸收)
|
|
164
|
+
// - fault_buyer → 仅库存回退(escrow 未锁,无资金动作)
|
|
165
|
+
// 不发放 commission / PV / 基金池入金 — 无真实成交
|
|
166
|
+
function settleFault(db, orderId, faultState) {
|
|
167
|
+
const sysUserId = 'sys_protocol';
|
|
168
|
+
// 幂等检查 + 资金处置全部包在 transaction 内(防未来迁 PG 时并发 race)
|
|
169
|
+
db.transaction(() => {
|
|
170
|
+
const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(orderId);
|
|
171
|
+
if (!order)
|
|
172
|
+
return;
|
|
173
|
+
if (order.settled_fault_at)
|
|
174
|
+
return; // 幂等:已处置过
|
|
175
|
+
const total = Number(order.total_amount);
|
|
176
|
+
const buyerId = order.buyer_id;
|
|
177
|
+
const sellerId = order.seller_id;
|
|
178
|
+
const isSecondhand = order.source === 'secondhand';
|
|
179
|
+
// QA 轮 7 P0 修复 — 改 per-order stake
|
|
180
|
+
// 旧逻辑读 product.stake_amount + stake_locked_at(per-product 模型残留,已废弃)
|
|
181
|
+
// 新逻辑:stake = order.total_amount × 0.15,下单时已锁,fault 时必然 lock(除非该订单未到 paid)
|
|
182
|
+
const sellerStakeRate = 0.15;
|
|
183
|
+
const stakeAmount = isSecondhand ? 0 : Math.round(total * sellerStakeRate * 100) / 100;
|
|
184
|
+
// 订单到 paid 之后 stake 必然 lock(place_order 已扣,余额不足会 reject)
|
|
185
|
+
const stakeLocked = !isSecondhand && Number(order.total_amount) > 0 && order.status !== 'created';
|
|
186
|
+
// P0.1:RFQ 路径的 bid_stake_held — fault 时由各分支按规则处理
|
|
187
|
+
const bidStakeHeld = Number(order.bid_stake_held || 0);
|
|
188
|
+
if (faultState === 'fault_seller') {
|
|
189
|
+
// 1. buyer escrow 全额退回
|
|
190
|
+
db.prepare('UPDATE wallets SET escrowed = escrowed - ?, balance = balance + ? WHERE user_id = ?')
|
|
191
|
+
.run(total, total, buyerId);
|
|
192
|
+
// P0.1:bid_stake_held 没收 50/50(中标后弃单的额外惩罚)
|
|
193
|
+
if (bidStakeHeld > 0) {
|
|
194
|
+
db.prepare('UPDATE wallets SET staked = staked - ? WHERE user_id = ?').run(bidStakeHeld, sellerId);
|
|
195
|
+
const compToBuyer = Math.round(bidStakeHeld * 0.5 * 100) / 100;
|
|
196
|
+
const compToSys = Math.round((bidStakeHeld - compToBuyer) * 100) / 100;
|
|
197
|
+
if (compToBuyer > 0)
|
|
198
|
+
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compToBuyer, buyerId);
|
|
199
|
+
if (compToSys > 0)
|
|
200
|
+
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compToSys, sysUserId);
|
|
201
|
+
}
|
|
202
|
+
// 2. stake 50/50 扣罚(严格资金守恒:实扣多少就分多少,不允许协议印钱)
|
|
203
|
+
if (stakeAmount > 0) {
|
|
204
|
+
// 先确定实际可扣金额
|
|
205
|
+
let actualDeduct = 0;
|
|
206
|
+
if (stakeLocked) {
|
|
207
|
+
// 已锁 → stake 全额扣(必然可扣,因 stake 已 lock 不可挪用)
|
|
208
|
+
db.prepare('UPDATE wallets SET staked = staked - ? WHERE user_id = ?').run(stakeAmount, sellerId);
|
|
209
|
+
actualDeduct = stakeAmount;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// 未锁 → 从 seller.balance 扣(不足则只扣到 0,差额不发放,不印钱)
|
|
213
|
+
const w = db.prepare('SELECT balance FROM wallets WHERE user_id = ?').get(sellerId);
|
|
214
|
+
actualDeduct = Math.min(stakeAmount, Number(w?.balance ?? 0));
|
|
215
|
+
if (actualDeduct > 0) {
|
|
216
|
+
db.prepare('UPDATE wallets SET balance = balance - ? WHERE user_id = ?').run(actualDeduct, sellerId);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// 按 actualDeduct 比例分配(buyer 50% + sys_protocol 50%),保证 入 = 出
|
|
220
|
+
const compensation = Math.round(actualDeduct * 0.5 * 100) / 100;
|
|
221
|
+
const protocolShare = Math.round((actualDeduct - compensation) * 100) / 100;
|
|
222
|
+
if (compensation > 0)
|
|
223
|
+
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compensation, buyerId);
|
|
224
|
+
if (protocolShare > 0)
|
|
225
|
+
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(protocolShare, sysUserId);
|
|
226
|
+
}
|
|
227
|
+
// 3. 库存回退(非二手)
|
|
228
|
+
if (!isSecondhand)
|
|
229
|
+
db.prepare('UPDATE products SET stock = stock + 1 WHERE id = ?').run(order.product_id);
|
|
230
|
+
// 4. 二手物品:状态恢复 available(保护卖家可重新发布)
|
|
231
|
+
if (isSecondhand) {
|
|
232
|
+
try {
|
|
233
|
+
db.prepare("UPDATE secondhand_items SET status = 'available', updated_at = datetime('now') WHERE id = ?").run(order.product_id);
|
|
234
|
+
}
|
|
235
|
+
catch { }
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else if (faultState === 'fault_logistics') {
|
|
239
|
+
// Phase 1(2026-05-27):logistics_id 为 null 等价"self-fulfill seller"信号
|
|
240
|
+
// → seller 自选自负物流,超时未送达即 seller 违约,按 fault_seller 同样规则处置
|
|
241
|
+
// → 严格守"无责方零成本,谁责任谁承担"原则(详见 docs/LOGISTICS-PHASING.md)
|
|
242
|
+
// Phase 2 logistics 市场启用后,logistics_id 非空走原始 logistics-penalty 逻辑
|
|
243
|
+
const isSelfFulfill = !order.logistics_id;
|
|
244
|
+
if (isSelfFulfill) {
|
|
245
|
+
// 1. buyer escrow 全额退回
|
|
246
|
+
db.prepare('UPDATE wallets SET escrowed = escrowed - ?, balance = balance + ? WHERE user_id = ?')
|
|
247
|
+
.run(total, total, buyerId);
|
|
248
|
+
// 2. bid_stake_held 没收 50/50(同 fault_seller 逻辑)
|
|
249
|
+
if (bidStakeHeld > 0) {
|
|
250
|
+
db.prepare('UPDATE wallets SET staked = staked - ? WHERE user_id = ?').run(bidStakeHeld, sellerId);
|
|
251
|
+
const compToBuyer = Math.round(bidStakeHeld * 0.5 * 100) / 100;
|
|
252
|
+
const compToSys = Math.round((bidStakeHeld - compToBuyer) * 100) / 100;
|
|
253
|
+
if (compToBuyer > 0)
|
|
254
|
+
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compToBuyer, buyerId);
|
|
255
|
+
if (compToSys > 0)
|
|
256
|
+
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compToSys, sysUserId);
|
|
257
|
+
}
|
|
258
|
+
// 3. stake 50/50 扣罚(seller 违约:自选 self-fulfill 但没送达)
|
|
259
|
+
if (stakeAmount > 0) {
|
|
260
|
+
let actualDeduct = 0;
|
|
261
|
+
if (stakeLocked) {
|
|
262
|
+
db.prepare('UPDATE wallets SET staked = staked - ? WHERE user_id = ?').run(stakeAmount, sellerId);
|
|
263
|
+
actualDeduct = stakeAmount;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
const w = db.prepare('SELECT balance FROM wallets WHERE user_id = ?').get(sellerId);
|
|
267
|
+
actualDeduct = Math.min(stakeAmount, Number(w?.balance ?? 0));
|
|
268
|
+
if (actualDeduct > 0) {
|
|
269
|
+
db.prepare('UPDATE wallets SET balance = balance - ? WHERE user_id = ?').run(actualDeduct, sellerId);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const compensation = Math.round(actualDeduct * 0.5 * 100) / 100;
|
|
273
|
+
const protocolShare = Math.round((actualDeduct - compensation) * 100) / 100;
|
|
274
|
+
if (compensation > 0)
|
|
275
|
+
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(compensation, buyerId);
|
|
276
|
+
if (protocolShare > 0)
|
|
277
|
+
db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(protocolShare, sysUserId);
|
|
278
|
+
}
|
|
279
|
+
// 4. 库存回退
|
|
280
|
+
if (!isSecondhand)
|
|
281
|
+
db.prepare('UPDATE products SET stock = stock + 1 WHERE id = ?').run(order.product_id);
|
|
282
|
+
if (isSecondhand) {
|
|
283
|
+
try {
|
|
284
|
+
db.prepare("UPDATE secondhand_items SET status = 'available', updated_at = datetime('now') WHERE id = ?").run(order.product_id);
|
|
285
|
+
}
|
|
286
|
+
catch { }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
// Phase 2 logistics 市场:真正的 logistics 接单后违约
|
|
291
|
+
// 1. buyer escrow 全额退回
|
|
292
|
+
db.prepare('UPDATE wallets SET escrowed = escrowed - ?, balance = balance + ? WHERE user_id = ?')
|
|
293
|
+
.run(total, total, buyerId);
|
|
294
|
+
// 2. seller 无责 → bid_stake_held / stake 全额返还
|
|
295
|
+
if (bidStakeHeld > 0) {
|
|
296
|
+
db.prepare('UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?').run(bidStakeHeld, bidStakeHeld, sellerId);
|
|
297
|
+
}
|
|
298
|
+
if (stakeAmount > 0 && stakeLocked) {
|
|
299
|
+
db.prepare('UPDATE wallets SET staked = staked - ?, balance = balance + ? WHERE user_id = ?')
|
|
300
|
+
.run(stakeAmount, stakeAmount, sellerId);
|
|
301
|
+
}
|
|
302
|
+
// 3. 库存回退
|
|
303
|
+
if (!isSecondhand)
|
|
304
|
+
db.prepare('UPDATE products SET stock = stock + 1 WHERE id = ?').run(order.product_id);
|
|
305
|
+
if (isSecondhand) {
|
|
306
|
+
try {
|
|
307
|
+
db.prepare("UPDATE secondhand_items SET status = 'available', updated_at = datetime('now') WHERE id = ?").run(order.product_id);
|
|
308
|
+
}
|
|
309
|
+
catch { }
|
|
310
|
+
}
|
|
311
|
+
// TODO(Phase 2):logistics 接入 stake/insurance/deposit 后,从 logistics 池扣给 seller 补货款
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else if (faultState === 'fault_buyer') {
|
|
315
|
+
// created→fault_buyer:escrow 未锁,仅库存回退
|
|
316
|
+
if (!isSecondhand)
|
|
317
|
+
db.prepare('UPDATE products SET stock = stock + 1 WHERE id = ?').run(order.product_id);
|
|
318
|
+
if (isSecondhand) {
|
|
319
|
+
try {
|
|
320
|
+
db.prepare("UPDATE secondhand_items SET status = 'available', updated_at = datetime('now') WHERE id = ?").run(order.product_id);
|
|
321
|
+
}
|
|
322
|
+
catch { }
|
|
323
|
+
}
|
|
324
|
+
// P0.1:买家违约 → bid_stake_held 全额返还卖家(卖家未失责)
|
|
325
|
+
if (bidStakeHeld > 0) {
|
|
326
|
+
db.prepare('UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?').run(bidStakeHeld, bidStakeHeld, sellerId);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// 标记已结算(防止重复处置)
|
|
330
|
+
db.prepare("UPDATE orders SET settled_fault_at = datetime('now') WHERE id = ?").run(orderId);
|
|
331
|
+
})();
|
|
332
|
+
}
|
|
131
333
|
// ─── 内部工具函数 ─────────────────────────────────────────────
|
|
132
334
|
/** 找出当前订单超时的转移(如果有) */
|
|
133
335
|
function findActiveDeadlineTransition(order, now) {
|
|
@@ -145,15 +347,34 @@ function findActiveDeadlineTransition(order, now) {
|
|
|
145
347
|
return null;
|
|
146
348
|
}
|
|
147
349
|
/** 获取当前有效的截止时间 */
|
|
148
|
-
function getActiveDeadline(order) {
|
|
350
|
+
function getActiveDeadline(order, db) {
|
|
351
|
+
// QA 轮 7 P1:旧表 picked_up 状态没 deadline → agent 不知道下一步多久前要做完
|
|
352
|
+
// 修:picked_up 状态视为"已揽收,等运输/投递",下一个 deadline 是 delivery_deadline
|
|
353
|
+
// QA 轮 7 P1(另一条):disputed 状态下没读 dispute_cases 的 arbitrate_deadline → agent 不知道仲裁还有多久
|
|
149
354
|
const deadlineMap = {
|
|
150
355
|
created: 'pay_deadline',
|
|
151
356
|
paid: 'accept_deadline',
|
|
152
357
|
accepted: 'ship_deadline',
|
|
153
358
|
shipped: 'pickup_deadline',
|
|
359
|
+
picked_up: 'delivery_deadline',
|
|
154
360
|
in_transit: 'delivery_deadline',
|
|
155
361
|
delivered: 'confirm_deadline',
|
|
156
362
|
};
|
|
363
|
+
// disputed 状态:从 `disputes` 表(active dispute, not the `dispute_cases` archive)查 deadline
|
|
364
|
+
// 优先返 arbitrate_deadline(仲裁总截止);如果还在 respond 窗口(被诉方未回应)返 respond_deadline
|
|
365
|
+
if (order.status === 'disputed' && db) {
|
|
366
|
+
try {
|
|
367
|
+
const dispute = db.prepare(`SELECT respond_deadline, arbitrate_deadline, ruling_type, status FROM disputes WHERE order_id = ? ORDER BY created_at DESC LIMIT 1`).get(order.id);
|
|
368
|
+
if (dispute && !dispute.ruling_type && dispute.status !== 'resolved' && dispute.status !== 'dismissed') {
|
|
369
|
+
if (dispute.arbitrate_deadline)
|
|
370
|
+
return { field: 'arbitrate_deadline', deadline: dispute.arbitrate_deadline };
|
|
371
|
+
if (dispute.respond_deadline)
|
|
372
|
+
return { field: 'respond_deadline', deadline: dispute.respond_deadline };
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch { }
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
157
378
|
const field = deadlineMap[order.status];
|
|
158
379
|
if (!field)
|
|
159
380
|
return null;
|