@seasonkoh/webaz 0.1.19 → 0.1.21
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 +165 -26
- package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +23 -0
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +65 -2
- package/dist/layer1-agent/L1-1-mcp-server/server.js +38 -27
- package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +34 -4
- package/dist/pwa/contract-fingerprint.js +46 -0
- package/dist/pwa/economic-participation.js +122 -0
- package/dist/pwa/endpoint-actions.js +112 -0
- package/dist/pwa/entity-dictionary.js +125 -0
- package/dist/pwa/goal-index.js +60 -0
- package/dist/pwa/integration-contract.js +64 -0
- package/dist/pwa/limits.js +30 -0
- package/dist/pwa/negative-space.js +64 -0
- package/dist/pwa/public/app.js +52 -47
- package/dist/pwa/public/docs/ECONOMIC-MODEL.md +287 -0
- package/dist/pwa/public/docs/INTEGRATOR.md +67 -0
- package/dist/pwa/public/docs/META-RULES-FULL.md +543 -0
- package/dist/pwa/public/i18n.js +44 -41
- package/dist/pwa/routes/disputes-write.js +68 -0
- package/dist/pwa/routes/orders-action.js +93 -1
- package/dist/pwa/routes/orders-read.js +18 -0
- package/dist/pwa/routes/public-utils.js +131 -1
- package/dist/pwa/routes/webauthn.js +9 -1
- package/dist/pwa/server.js +32 -121
- package/dist/pwa/verifiability-index.js +63 -0
- package/dist/version.js +32 -0
- package/package.json +2 -1
package/dist/pwa/public/i18n.js
CHANGED
|
@@ -334,7 +334,7 @@ const _EN = {
|
|
|
334
334
|
'该范围内还没有 ≥ 3 人买过同一商品': 'No product bought by ≥ 3 people in this range yet',
|
|
335
335
|
'当前关键词无匹配': 'No match for current keyword',
|
|
336
336
|
'单': '',
|
|
337
|
-
'
|
|
337
|
+
'积分树挂靠': 'team tree placement',
|
|
338
338
|
'(挂靠位置由系统按弱区自动决定,与邀请人不一定相邻)': '(placement auto-assigned to the weaker leg by the system; not necessarily adjacent to your sponsor)',
|
|
339
339
|
'我的邀请链接(第一触点锁定 30 天)': 'My referral link (first-touch, 30-day cookie)',
|
|
340
340
|
'你的邀请人': 'Your sponsor',
|
|
@@ -350,33 +350,33 @@ const _EN = {
|
|
|
350
350
|
'1%-50%,分享佣金 70/20/10 自动拆分': '1%-50%, auto-split 70/20/10 across tiers',
|
|
351
351
|
'结算后不可冲正,请审慎设定': 'Cannot be clawed back after settlement, set carefully',
|
|
352
352
|
'佣金比例需在 1%-50% 之间': 'Commission rate must be 1%-50%',
|
|
353
|
-
'
|
|
353
|
+
'积分 — 积分匹配': 'points-matching (积分)',
|
|
354
354
|
'左右码(完全对称 / 末端垂直挂靠)': 'Left/Right codes (symmetric / tail-anchored)',
|
|
355
355
|
'左码': 'Left',
|
|
356
356
|
'右码': 'Right',
|
|
357
357
|
'我挂靠位置': 'My placement',
|
|
358
358
|
'直挂': 'Direct',
|
|
359
|
-
'
|
|
359
|
+
'弱侧匹配量': 'Matching volume',
|
|
360
360
|
'距离 tier': 'Toward tier',
|
|
361
361
|
'门槛': 'threshold',
|
|
362
|
-
'
|
|
362
|
+
'匹配档位表': 'Tier table',
|
|
363
363
|
'门槛 PV': 'Threshold PV',
|
|
364
364
|
'Score / 次': 'Score per hit',
|
|
365
|
-
'
|
|
366
|
-
'
|
|
367
|
-
'Tokenomics — 两轨基金': 'Tokenomics —
|
|
365
|
+
'最近匹配': 'Recent matches',
|
|
366
|
+
'暂无匹配记录': 'No matches yet',
|
|
367
|
+
'Tokenomics — 两轨基金': 'Tokenomics — Pooled funds',
|
|
368
368
|
'Tokenomics': 'Tokenomics',
|
|
369
|
-
'
|
|
369
|
+
'积分基金 / Tier 配置 / 高额榜': 'Pooled funds / Tier config / Leaderboard',
|
|
370
370
|
'累计分享分润': 'Total 3-tier commission',
|
|
371
|
-
'
|
|
371
|
+
'累计匹配发放': 'Total distributed',
|
|
372
372
|
'PV 待处理': 'PV pending',
|
|
373
373
|
'池子余额': 'Pool balance',
|
|
374
374
|
'上次 N': 'Last N',
|
|
375
375
|
'上次结算': 'Last settled',
|
|
376
|
-
'来自协议费 50
|
|
376
|
+
'来自协议费 50%,用于大博主匹配团队 10/5/2% 补贴': 'From 50% protocol fee, for top promoter 10/5/2% bonus',
|
|
377
377
|
'Cron 控制 + 紧急操作': 'Cron controls + emergency',
|
|
378
378
|
'处理 PV 流水': 'Process PV ledger',
|
|
379
|
-
'
|
|
379
|
+
'触发匹配结算': 'Run settlement',
|
|
380
380
|
'分发 WAZ(清池)': 'Distribute WAZ (drain pool)',
|
|
381
381
|
'确认分发 WAZ?这将清空池子按 N 比例分配': 'Confirm distribute? This drains the pool by N ratio',
|
|
382
382
|
'PV 待处理流水': 'Pending PV ledger',
|
|
@@ -384,10 +384,10 @@ const _EN = {
|
|
|
384
384
|
'Tier 配置': 'Tier config',
|
|
385
385
|
'(admin 可调)': '(admin adjustable)',
|
|
386
386
|
'调整:POST /api/admin/tokenomics/tier with body {tier, pv_threshold, score_per_hit, active}': 'Adjust via POST /api/admin/tokenomics/tier',
|
|
387
|
-
'Top
|
|
388
|
-
'Top
|
|
387
|
+
'Top 三级佣金': 'Top commission earners',
|
|
388
|
+
'Top 匹配收益': 'Top matching earners',
|
|
389
389
|
'已处理流水': 'Processed',
|
|
390
|
-
'
|
|
390
|
+
'已触发匹配': 'Settled',
|
|
391
391
|
'已分发 WAZ': 'Distributed',
|
|
392
392
|
'推广佣金': 'Promo commission',
|
|
393
393
|
'推广此商品 · 分享赚佣金': 'Promote this · share to earn',
|
|
@@ -402,8 +402,8 @@ const _EN = {
|
|
|
402
402
|
'新人挂靠到你的': 'New user placed in your',
|
|
403
403
|
'商品分享链接(在商品页点「推广此商品」自动生成,含两轨绑定)': 'Product share links (auto-generated via product page Promote button, dual-track)',
|
|
404
404
|
'平台分享 · 仅 PV 条线': 'Platform share · PV only',
|
|
405
|
-
'
|
|
406
|
-
'平台分享:仅建 PV 条线 ·
|
|
405
|
+
'双区链接': 'Dual-zone link',
|
|
406
|
+
'平台分享:仅建 PV 条线 · 双区链接:同时绑三级佣金分享 + 积分 PV': 'Platform share = PV line only · Dual-zone = bind both 3-tier + PV',
|
|
407
407
|
'津贴门控': 'Bonus gating',
|
|
408
408
|
'查看资格用户 + 切换开关': 'View eligible users + toggle',
|
|
409
409
|
'全局开关': 'Global switch',
|
|
@@ -423,7 +423,7 @@ const _EN = {
|
|
|
423
423
|
'分享奖励待解锁': 'Share rewards locked',
|
|
424
424
|
'Admin 强制授予': 'Admin grant override',
|
|
425
425
|
'笔订单 — 可拿分享佣金': 'orders — can earn 3-tier commission',
|
|
426
|
-
'完成至少 1 笔购买订单后,分享商品可拿分享佣金。当前可分享 PV
|
|
426
|
+
'完成至少 1 笔购买订单后,分享商品可拿分享佣金。当前可分享 PV 条线扩展积分树': 'Complete ≥1 purchase to unlock 3-tier. PV-line sharing still works.',
|
|
427
427
|
'L1 分享权限': 'L1 share permission',
|
|
428
428
|
'可拿分享佣金': 'can earn 3-tier',
|
|
429
429
|
'不可': 'no',
|
|
@@ -452,9 +452,9 @@ const _EN = {
|
|
|
452
452
|
'偏好已保存': 'Preference saved',
|
|
453
453
|
'系统检测到邀请链接': 'Invitation link detected',
|
|
454
454
|
'inviter': 'Inviter',
|
|
455
|
-
'
|
|
455
|
+
'是否加入对方的积分配对?': 'Join their points-matching group?',
|
|
456
456
|
'系统将按推荐人偏好自动选择左/右区。一旦加入永久不变。': "Side picked by inviter's preference. Permanent once joined.",
|
|
457
|
-
'
|
|
457
|
+
'已加入积分树': 'Joined team tree',
|
|
458
458
|
'侧': 'Side',
|
|
459
459
|
'注册门控': 'Registration gating',
|
|
460
460
|
'开启后:无邀请码不能注册(admin/物流/仲裁/审核员 与 region=china 豁免)': 'When ON: no invite code → cannot register (admin/logistics/arbitrator/verifier + china region exempted)',
|
|
@@ -1109,7 +1109,7 @@ const _EN = {
|
|
|
1109
1109
|
'拿到第一笔分享佣金': 'Earn first share commission',
|
|
1110
1110
|
'创作首个原生内容': 'Create first native content',
|
|
1111
1111
|
'团队达到 5 人': 'Team reaches 5',
|
|
1112
|
-
'
|
|
1112
|
+
'弱侧匹配 tier 1': 'weak-side tier 1 match',
|
|
1113
1113
|
'月度收益 100 WAZ': 'Monthly income 100 WAZ',
|
|
1114
1114
|
'激活动态推荐': 'Activate dynamic L1',
|
|
1115
1115
|
'团队达到 50 人': 'Team reaches 50',
|
|
@@ -1134,13 +1134,12 @@ const _EN = {
|
|
|
1134
1134
|
'我的邀请链接': 'My referral link',
|
|
1135
1135
|
'第一触点锁定 30 天': 'First-touch locked 30d',
|
|
1136
1136
|
'指定左/右轨': 'Pin to LEFT/RIGHT',
|
|
1137
|
-
'双轨左右区设置': 'Twin-rail L/R Settings',
|
|
1138
1137
|
'左区码': 'LEFT code',
|
|
1139
1138
|
'右区码': 'RIGHT code',
|
|
1140
1139
|
'挂靠': 'Placed at',
|
|
1141
1140
|
'团队分享': 'Team Sharing',
|
|
1142
|
-
'
|
|
1143
|
-
'
|
|
1141
|
+
'积分匹配': 'Matching',
|
|
1142
|
+
'弱侧': 'Weak side',
|
|
1144
1143
|
'累计': 'Total',
|
|
1145
1144
|
'近 7 日': 'Last 7d',
|
|
1146
1145
|
'笔': 'records',
|
|
@@ -1184,7 +1183,7 @@ const _EN = {
|
|
|
1184
1183
|
'例如:思翔教育007': 'e.g. SeasonEducation007',
|
|
1185
1184
|
'在 TikTok / 小红书 口播这个口令,粉丝在 WebAZ 搜它就能找到你': 'Mention this anchor on TikTok/Xiaohongshu — fans can search it on WebAZ to find you',
|
|
1186
1185
|
'在公开动态流显示我的活动': 'Show my activity in public feed',
|
|
1187
|
-
'
|
|
1186
|
+
'关闭后,你的购买/匹配/分润事件不会出现在 发现好物 > 动态': 'When off, your buy/match/commission events won\'t appear in Discover > Feed',
|
|
1188
1187
|
'预览我的主页': 'Preview my profile',
|
|
1189
1188
|
'我的关注/粉丝': 'My follows / fans',
|
|
1190
1189
|
'被拉黑的用户的商品和动态对你不可见': 'Blocked users\' products and activity will be hidden from you',
|
|
@@ -1237,7 +1236,7 @@ const _EN = {
|
|
|
1237
1236
|
// Promoter / Growth
|
|
1238
1237
|
'左区推荐码': 'LEFT referral code',
|
|
1239
1238
|
'右区推荐码': 'RIGHT referral code',
|
|
1240
|
-
'
|
|
1239
|
+
'团队组织图(3 层)': 'team tree (3 levels)',
|
|
1241
1240
|
'空左': 'Empty left',
|
|
1242
1241
|
'空右': 'Empty right',
|
|
1243
1242
|
'每节点显示 L=左累计 PV / R=右累计 PV': 'Each node shows L=left PV / R=right PV',
|
|
@@ -1300,7 +1299,7 @@ const _EN = {
|
|
|
1300
1299
|
'暂无新品': 'No new items',
|
|
1301
1300
|
'等待第一位买家': 'Waiting for first buyer',
|
|
1302
1301
|
'← 发现好物': '← Discover',
|
|
1303
|
-
'卖家最新上架、尚无成交 —
|
|
1302
|
+
'卖家最新上架、尚无成交 — 成为第一位发现者,触发三级佣金 + 积分配对奖励': 'Just listed, no sales yet — be the first discoverer and trigger 3-tier commission + points-matching rewards',
|
|
1304
1303
|
'卖家最新上架、尚无成交 — 成为第一位发现者和传播者': 'Just listed, no sales yet — be the first discoverer and sharer',
|
|
1305
1304
|
|
|
1306
1305
|
// Cart / orders
|
|
@@ -1537,7 +1536,7 @@ const _EN = {
|
|
|
1537
1536
|
'详细 / 充提': 'Details / Top-up',
|
|
1538
1537
|
'累计赚取': 'Total earned',
|
|
1539
1538
|
'待结算点数': 'Pending points',
|
|
1540
|
-
'
|
|
1539
|
+
'积分资产看板': 'points-matching assets dashboard (积分)',
|
|
1541
1540
|
'(设置一句话简介让人记住你)': '(Set a one-line bio to be memorable)',
|
|
1542
1541
|
|
|
1543
1542
|
// Password / Auth
|
|
@@ -1636,7 +1635,7 @@ const _EN = {
|
|
|
1636
1635
|
'永久分享推荐码': 'Permanent share code',
|
|
1637
1636
|
'总资产': 'Total assets',
|
|
1638
1637
|
'收入构成': 'Income breakdown',
|
|
1639
|
-
'
|
|
1638
|
+
'积分已结算': 'Settled',
|
|
1640
1639
|
'销售收入': 'Sales income',
|
|
1641
1640
|
'最近充值': 'Recent deposits',
|
|
1642
1641
|
'最近提现': 'Recent withdrawals',
|
|
@@ -2084,14 +2083,14 @@ const _EN = {
|
|
|
2084
2083
|
'商品推荐': 'Product promo',
|
|
2085
2084
|
'打开': 'Open',
|
|
2086
2085
|
|
|
2087
|
-
//
|
|
2086
|
+
// team tree enhancements
|
|
2088
2087
|
'我挂位置': 'My position',
|
|
2089
2088
|
'左侧': 'left',
|
|
2090
2089
|
'右侧': 'right',
|
|
2091
|
-
'
|
|
2092
|
-
'
|
|
2093
|
-
'
|
|
2094
|
-
'
|
|
2090
|
+
'你还没加入任何上级的积分树(独立根节点)': 'Not yet placed in any upline\'s team tree (independent root)',
|
|
2091
|
+
'本月匹配次数': 'Month hits',
|
|
2092
|
+
'本月匹配 WAZ': 'Month WAZ',
|
|
2093
|
+
'累计匹配次数': 'Total hits',
|
|
2095
2094
|
'点击节点查看 PV': 'click node for PV',
|
|
2096
2095
|
'每节点显示 L=左累计 PV / R=右累计 PV · 点击节点看详情': 'L=left PV / R=right PV · click node for details',
|
|
2097
2096
|
'点击查看 PV KPI': 'Click for PV KPI',
|
|
@@ -2100,7 +2099,7 @@ const _EN = {
|
|
|
2100
2099
|
'深度': 'depth',
|
|
2101
2100
|
'独立根节点(无上级)': 'Independent root (no upline)',
|
|
2102
2101
|
'双腿均衡': 'Leg balance',
|
|
2103
|
-
'
|
|
2102
|
+
'累计匹配': 'Total hits',
|
|
2104
2103
|
'累计获 WAZ': 'Total earned WAZ',
|
|
2105
2104
|
'待结 Score': 'Pending Score',
|
|
2106
2105
|
'查看 TA 的主页': 'View their profile',
|
|
@@ -2119,7 +2118,7 @@ const _EN = {
|
|
|
2119
2118
|
'调用多样': 'API diversity',
|
|
2120
2119
|
'仲裁败诉': 'Dispute loss',
|
|
2121
2120
|
'同 IP 多号': 'Same IP cluster',
|
|
2122
|
-
'
|
|
2121
|
+
'同支': 'Same-branch',
|
|
2123
2122
|
'限速命中': 'Rate limit hits',
|
|
2124
2123
|
'再 +': '+',
|
|
2125
2124
|
'分解锁 raw mode': 'pts to unlock raw mode',
|
|
@@ -2213,7 +2212,7 @@ const _EN = {
|
|
|
2213
2212
|
'这件商品还没在 WebAZ 上架。把它带进来 — 让买家在精准搜索时也能找到你。': 'This SKU is not on WebAZ yet. Bring it in — let buyers find you on precise search.',
|
|
2214
2213
|
'为什么上架到 WebAZ': 'Why list on WebAZ',
|
|
2215
2214
|
'· 协议费仅 2%(vs 淘宝 5%+ 抽佣 + 竞价推广)': '· Protocol fee 2% only (vs Taobao 5%+ commission + bid promotion)',
|
|
2216
|
-
'· 分享成交拿 commission
|
|
2215
|
+
'· 分享成交拿 commission(三级佣金 70/20/10)': '· Earn share commission (3-tier 70/20/10)',
|
|
2217
2216
|
'· 链上稳定币直达,无平台资金截留': '· On-chain stablecoin settlement, no platform float',
|
|
2218
2217
|
'· Agent 自动比价推荐 + alias 精准命中': '· Agent auto-compares + precise alias matching',
|
|
2219
2218
|
'上架只需 3 步': 'List in 3 steps',
|
|
@@ -2412,11 +2411,11 @@ const _EN = {
|
|
|
2412
2411
|
'零上架成本': 'Zero listing cost',
|
|
2413
2412
|
'AI 店长自主上架、自动生成多语种商品矩阵。首单成交仅锁定 15% 作买家保护(Trusted 卖家免除)。':
|
|
2414
2413
|
'AI shopkeeper lists products and generates multilingual catalogs autonomously. Only the first sale locks 15% as buyer protection (Trusted sellers exempt).',
|
|
2415
|
-
'买家 Agent
|
|
2414
|
+
'买家 Agent 意图匹配': 'Buyer Agent intent matching',
|
|
2416
2415
|
'零推广费': 'Zero promotion fees',
|
|
2417
2416
|
'全网买家 Agent 携精准需求(Alias)全天候巡航,与商品语义刚需匹配,订单全自动闭合成交。':
|
|
2418
2417
|
'Buyer Agents across the network roam 24/7 with precise needs (Aliases), matching products by semantic intent and closing orders autonomously.',
|
|
2419
|
-
'
|
|
2418
|
+
'智能体扩散分润': 'Agent-driven viral revenue share',
|
|
2420
2419
|
'秒级自动分账': 'Instant on-chain settlement',
|
|
2421
2420
|
'商品由其他节点或达人 Agent 智能分享成交,下游分润通过智能合约去中心化自动清算,即刻到账。':
|
|
2422
2421
|
'Products surface via other nodes or influencer Agents; downstream revenue share is auto-settled on-chain — instantly credited.',
|
|
@@ -3161,7 +3160,7 @@ const _EN = {
|
|
|
3161
3160
|
'卖家收入速览': 'Seller Income Summary',
|
|
3162
3161
|
'商品销售': 'Product Sales',
|
|
3163
3162
|
'推荐分润': 'Referral Comm.',
|
|
3164
|
-
'
|
|
3163
|
+
'匹配已结': 'Settled',
|
|
3165
3164
|
'待结算': 'Pending',
|
|
3166
3165
|
'escrow 中': 'in escrow',
|
|
3167
3166
|
|
|
@@ -4579,7 +4578,7 @@ const _EN = {
|
|
|
4579
4578
|
'已是共建身份': 'Already opted in',
|
|
4580
4579
|
'你已 opted-in。如需查看状态或退出,前往 ': 'You are opted in. To view status or opt out, go to ',
|
|
4581
4580
|
'本流程与购物无关': 'This flow is not part of shopping',
|
|
4582
|
-
'你可以随时退出,不影响任何已下单或未来订单。本流程涉及经济关系登记(三级佣金 +
|
|
4581
|
+
'你可以随时退出,不影响任何已下单或未来订单。本流程涉及经济关系登记(三级佣金 + 积分配对),请仔细阅读全部条款。': 'You can leave anytime without affecting any past or future orders. This is an economic-relationship registration (3-tier commission + points-matching) — please read all terms carefully.',
|
|
4583
4582
|
'门槛(全部通过才能申请)': 'Eligibility (all must pass to apply)',
|
|
4584
4583
|
'已完成订单': 'Completed orders',
|
|
4585
4584
|
'Passkey 已注册': 'Passkey registered',
|
|
@@ -5569,7 +5568,7 @@ const _EN = {
|
|
|
5569
5568
|
'调用多样性': 'Endpoint diversity',
|
|
5570
5569
|
'争议败诉': 'Dispute losses',
|
|
5571
5570
|
'同 IP 异户': 'Same-IP users',
|
|
5572
|
-
'
|
|
5571
|
+
'匹配命中': 'Matching hits',
|
|
5573
5572
|
'限速命中': 'Rate-limit hits',
|
|
5574
5573
|
'阈值': 'Thresholds',
|
|
5575
5574
|
|
|
@@ -6047,6 +6046,10 @@ const _EN = {
|
|
|
6047
6046
|
'体验问题': 'UX issue',
|
|
6048
6047
|
'提案': 'Proposal',
|
|
6049
6048
|
'共建信誉': 'Co-build reputation',
|
|
6049
|
+
// RFC-004 体验补:无锚点受理 → 引导绑 Passkey 领取(转化,而非困惑)
|
|
6050
|
+
'这条贡献已被采纳 —— 绑定 Passkey 即可领取建设信誉(奖励需锚定可问责真人)': 'This contribution was accepted — bind a Passkey to claim its co-build reputation (rewards must anchor to an accountable human)',
|
|
6051
|
+
'绑定 Passkey 领取': 'Bind Passkey to claim',
|
|
6052
|
+
'已绑定,建设信誉已补发': 'Passkey bound — co-build reputation has been granted',
|
|
6050
6053
|
// RFC-006 Gap 2:贡献者看板
|
|
6051
6054
|
'我的共建': 'My contributions',
|
|
6052
6055
|
'你的建设贡献(独立于交易信誉 — 不影响 verifier/仲裁准入)': 'Your building contributions (separate from trade reputation — does NOT gate verifier/arbitrator)',
|
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
+
// RFC-007 stage 5:客观拒单仲裁翻案直接复用 L0 状态机/结算(纯 db 函数,无副作用)
|
|
3
|
+
import { transition, settleFault, settleDeclinedNoFault } from '../../layer0-foundation/L0-2-state-machine/engine.js';
|
|
2
4
|
export function registerDisputesWriteRoutes(app, deps) {
|
|
3
5
|
const { db, auth, generateId, detectFraud, errorRes, isEligibleArbitrator, requireHumanPresence, getDisputeDetails, respondToDispute, arbitrateDispute, addPartyEvidence, requestEvidence, markEvidenceExpiry, uploadEvidence, EVIDENCE_MAX_BYTES, EVIDENCE_ALLOWED_MIME, appendOrderEvent, FUND_BASE_RATE, settleCommission, depositToFund, calculatePv, recordDisputeReputation, issueAgentStrike, publishDisputeCase, logAdminAction, snfSend, getProtocolParam } = deps;
|
|
6
|
+
// ── RFC-007 stage 5:客观拒单【临时判责】的仲裁翻案 ─────────────────────────────
|
|
7
|
+
// 卖家 contest_decline 后,订单 = fault_seller + decline_objective_pending=1 + decline_contested=1(未结算)。
|
|
8
|
+
// 仲裁员(真实人工 + WebAuthn)裁决:uphold → declined_nofault(免责全退+退质押);reject → 违约结算。
|
|
9
|
+
const SYS = 'sys_protocol';
|
|
10
|
+
// 仲裁员待办:列出所有被举证的临时判责拒单
|
|
11
|
+
app.get('/api/admin/decline-contests', (req, res) => {
|
|
12
|
+
const user = auth(req, res);
|
|
13
|
+
if (!user)
|
|
14
|
+
return;
|
|
15
|
+
const elig = isEligibleArbitrator(user.id);
|
|
16
|
+
if (!elig.ok)
|
|
17
|
+
return void errorRes(res, 403, 'NOT_ARBITRATOR', elig.reason || '仅限仲裁员');
|
|
18
|
+
const rows = db.prepare(`
|
|
19
|
+
SELECT id AS order_id, buyer_id, seller_id, product_id, total_amount, decline_reason_code, declined_at, decline_contest_deadline
|
|
20
|
+
FROM orders
|
|
21
|
+
WHERE status = 'fault_seller' AND COALESCE(decline_objective_pending,0)=1 AND COALESCE(decline_contested,0)=1 AND settled_fault_at IS NULL
|
|
22
|
+
ORDER BY declined_at ASC
|
|
23
|
+
`).all();
|
|
24
|
+
res.json({ contests: rows });
|
|
25
|
+
});
|
|
26
|
+
// 仲裁员裁决
|
|
27
|
+
app.post('/api/admin/decline-contests/:orderId/resolve', (req, res) => {
|
|
28
|
+
const user = auth(req, res);
|
|
29
|
+
if (!user)
|
|
30
|
+
return;
|
|
31
|
+
const elig = isEligibleArbitrator(user.id);
|
|
32
|
+
if (!elig.ok)
|
|
33
|
+
return void errorRes(res, 403, 'NOT_ARBITRATOR', elig.reason || '仅限仲裁员');
|
|
34
|
+
// 铁律:仲裁需真实人工 WebAuthn
|
|
35
|
+
const hp = requireHumanPresence(user.id, 'arbitrate', req.body?.webauthn_token, 'require_human_presence_for_arbitrate');
|
|
36
|
+
if (!hp.ok)
|
|
37
|
+
return void errorRes(res, 412, hp.error_code || 'HUMAN_PRESENCE_REQUIRED', hp.reason || '此操作需真实人工 WebAuthn 验证');
|
|
38
|
+
const { decision, reason } = req.body ?? {};
|
|
39
|
+
if (!['uphold', 'reject'].includes(decision))
|
|
40
|
+
return void errorRes(res, 400, 'BAD_DECISION', "decision 必须为 'uphold'(认定无责) 或 'reject'(驳回,判违约)");
|
|
41
|
+
if (!reason || !String(reason).trim())
|
|
42
|
+
return void errorRes(res, 400, 'REASON_REQUIRED', '请提供裁决理由');
|
|
43
|
+
const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(req.params.orderId);
|
|
44
|
+
if (!order)
|
|
45
|
+
return void res.status(404).json({ error: '订单不存在' });
|
|
46
|
+
if (order.status !== 'fault_seller' || Number(order.decline_objective_pending) !== 1 || Number(order.decline_contested) !== 1 || order.settled_fault_at) {
|
|
47
|
+
return void errorRes(res, 400, 'NOT_CONTESTED_PROVISIONAL', '本订单不是【已举证·待裁决】的临时判责拒单');
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
appendOrderEvent(db, { orderId: req.params.orderId, eventType: 'transition', fromStatus: 'fault_seller', toStatus: 'fault_seller', actorId: user.id, actorRole: 'arbitrator', extra: { action: 'decline_contest_ruling', decision, reason } });
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
console.warn('[order-chain] decline ruling event:', e.message);
|
|
54
|
+
}
|
|
55
|
+
if (decision === 'uphold') {
|
|
56
|
+
// 认定客观无责 → declined_nofault:全退买家 + 退卖家质押,零罚没
|
|
57
|
+
const r1 = transition(db, req.params.orderId, 'declined_nofault', user.id, [], `客观拒单仲裁维持(无责):${reason}`);
|
|
58
|
+
if (!r1.success)
|
|
59
|
+
return void res.json({ error: r1.error });
|
|
60
|
+
settleDeclinedNoFault(db, req.params.orderId);
|
|
61
|
+
transition(db, req.params.orderId, 'completed', SYS, [], '无责拒单结算完成');
|
|
62
|
+
logAdminAction(user.id, 'decline_contest_uphold', 'order', req.params.orderId, { reason });
|
|
63
|
+
return void res.json({ success: true, outcome: 'declined_nofault', note: '裁决:客观无责。买家已全额退款,卖家质押已退回,无罚没。' });
|
|
64
|
+
}
|
|
65
|
+
// reject → 违约结算(终结临时判责)
|
|
66
|
+
settleFault(db, req.params.orderId, 'fault_seller');
|
|
67
|
+
transition(db, req.params.orderId, 'completed', SYS, [], `客观拒单仲裁驳回 → 违约结算:${reason}`);
|
|
68
|
+
db.prepare('UPDATE orders SET decline_objective_pending = 0, decline_contested = 0 WHERE id = ?').run(req.params.orderId);
|
|
69
|
+
logAdminAction(user.id, 'decline_contest_reject', 'order', req.params.orderId, { reason });
|
|
70
|
+
res.json({ success: true, outcome: 'fault_seller', note: '裁决:驳回。按违约结算,买家已全额退款,卖家质押按规则罚没。' });
|
|
71
|
+
});
|
|
4
72
|
// 被诉方反驳
|
|
5
73
|
app.post('/api/disputes/:id/respond', (req, res) => {
|
|
6
74
|
const user = auth(req, res);
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
export function registerOrdersActionRoutes(app, deps) {
|
|
2
|
-
const { db, auth, isTrustedRole, generateId, transition, notifyTransition, settleOrder, detectFraud, createDispute, checkTimeouts, recordViolationReputation, broadcastSystemEvent } = deps;
|
|
2
|
+
const { db, auth, isTrustedRole, generateId, transition, notifyTransition, settleOrder, settleFault, detectFraud, createDispute, checkTimeouts, recordViolationReputation, broadcastSystemEvent } = deps;
|
|
3
|
+
// RFC-007 stage 2:卖家主动拒单 reason_code 白名单。
|
|
4
|
+
// classification(客观无责 vs 主观有责)是 stage 3 auto-verify 的事;stage 2 仅捕获 + 一律走违约结算。
|
|
5
|
+
// objective-claimed(stage 3 将尝试确定性核验):stock_consumed_concurrent / stale_price_snapshot / force_majeure
|
|
6
|
+
// subjective(stage 3 直接判 fault):price_regret / cherry_pick / other
|
|
7
|
+
const DECLINE_REASON_CODES = new Set([
|
|
8
|
+
'stock_consumed_concurrent', 'stale_price_snapshot', 'force_majeure',
|
|
9
|
+
'price_regret', 'cherry_pick', 'other',
|
|
10
|
+
]);
|
|
11
|
+
// 客观-声称理由:链下事实(外部已售/损毁),协议无确定性信号可自动核验 → 临时判责 + 举证窗口(stage 5 仲裁)。
|
|
12
|
+
const OBJECTIVE_DECLINE_REASONS = new Set(['stock_consumed_concurrent', 'stale_price_snapshot', 'force_majeure']);
|
|
3
13
|
// C-4: 卖家批量发货
|
|
4
14
|
app.post('/api/orders/batch-ship', (req, res) => {
|
|
5
15
|
const user = auth(req, res);
|
|
@@ -142,6 +152,88 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
142
152
|
if (action === 'pickup' && !order.logistics_id && user.role === 'logistics') {
|
|
143
153
|
db.prepare('UPDATE orders SET logistics_id = ? WHERE id = ?').run(user.id, req.params.id);
|
|
144
154
|
}
|
|
155
|
+
// ── RFC-007 stage 2/3:卖家【主动拒单】decline ───────────────────────────────
|
|
156
|
+
// 仅卖家、仅 paid(待接单) 状态可拒;记 reason_code + declined_at。
|
|
157
|
+
// stage 3 按理由分流:
|
|
158
|
+
// · 主观理由(price_regret/cherry_pick/other)→ 立即违约结算(paid→fault_seller→completed)。
|
|
159
|
+
// · 客观-声称理由(并发耗尽/陈旧快照/不可抗力)→ 【临时判责】:转 fault_seller 但【不结算】,
|
|
160
|
+
// 置 decline_objective_pending=1 + 举证窗口 deadline。窗口内卖家可开仲裁(stage 5)举证翻案;
|
|
161
|
+
// 到期无人仲裁 → checkTimeouts 自动终结为违约。客观场景本质是链下事实(外部已售/损毁),
|
|
162
|
+
// 协议无确定性信号可自动核验,必须人工仲裁 —— 故不自动免责,只给举证窗口。
|
|
163
|
+
if (action === 'decline') {
|
|
164
|
+
if (uid !== sellerId)
|
|
165
|
+
return void res.status(403).json({ error: '你不是本订单的卖家', error_code: 'NOT_ORDER_SELLER' });
|
|
166
|
+
if (order.status !== 'paid')
|
|
167
|
+
return void res.status(400).json({ error: `仅可在「待接单(paid)」状态拒单,当前 ${order.status}`, error_code: 'DECLINE_WRONG_STATUS' });
|
|
168
|
+
const reasonCode = String((req.body?.decline_reason_code ?? '') || '').trim();
|
|
169
|
+
if (!DECLINE_REASON_CODES.has(reasonCode)) {
|
|
170
|
+
return void res.status(400).json({ error: `decline_reason_code 无效,需为: ${[...DECLINE_REASON_CODES].join(' / ')}`, error_code: 'DECLINE_REASON_INVALID' });
|
|
171
|
+
}
|
|
172
|
+
const isObjectiveClaim = OBJECTIVE_DECLINE_REASONS.has(reasonCode);
|
|
173
|
+
// 客观-声称 + 举证窗口 > 0 → 临时判责(不结算);否则(主观 或 窗口=0)→ 立即违约结算
|
|
174
|
+
const windowHours = Number(db.prepare("SELECT value FROM protocol_params WHERE key = 'decline_contest_window_hours'").get()?.value ?? 24);
|
|
175
|
+
const provisional = isObjectiveClaim && windowHours > 0;
|
|
176
|
+
db.prepare("UPDATE orders SET decline_reason_code = ?, declined_at = datetime('now') WHERE id = ?").run(reasonCode, req.params.id);
|
|
177
|
+
const r1 = transition(db, req.params.id, 'fault_seller', uid, [], `卖家主动拒单 reason=${reasonCode}${provisional ? '(临时判责·待举证)' : ''}${notes ? ':' + notes : ''}`);
|
|
178
|
+
if (!r1.success) {
|
|
179
|
+
db.prepare("UPDATE orders SET decline_reason_code = NULL, declined_at = NULL WHERE id = ?").run(req.params.id);
|
|
180
|
+
return void res.json({ error: r1.error });
|
|
181
|
+
}
|
|
182
|
+
notifyTransition(db, req.params.id, 'paid', 'fault_seller');
|
|
183
|
+
if (provisional) {
|
|
184
|
+
// 临时判责:置 pending + deadline,【不结算】(escrow/stake 暂挂,随终结或翻案一次性结算)
|
|
185
|
+
db.prepare(`UPDATE orders SET decline_objective_pending = 1, decline_contest_deadline = datetime('now', '+' || ? || ' hours') WHERE id = ?`).run(windowHours, req.params.id);
|
|
186
|
+
const deadline = db.prepare("SELECT decline_contest_deadline AS d FROM orders WHERE id = ?").get(req.params.id).d;
|
|
187
|
+
return void res.json({
|
|
188
|
+
success: true, outcome: 'fault_seller_provisional', decline_reason_code: reasonCode, contest_deadline: deadline,
|
|
189
|
+
note: `客观无责拒单为【临时判责】:你声称的客观理由(外部已售/损毁等)是链下事实,协议无法自动核验,需人工仲裁。请在 ${deadline} 前用 webaz_dispute 开仲裁举证;维持则免责全退,逾期未仲裁则自动终结为违约。买家退款随终结/翻案一次性处理。`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// 主观(或窗口=0):立即违约结算(退款买家 + 按 stake_backing 罚没,守恒,绝不印钱)
|
|
193
|
+
const sysUser = db.prepare("SELECT id FROM users WHERE id = 'sys_protocol'").get();
|
|
194
|
+
try {
|
|
195
|
+
settleFault(db, req.params.id, 'fault_seller');
|
|
196
|
+
if (sysUser) {
|
|
197
|
+
transition(db, req.params.id, 'completed', sysUser.id, [], '主动拒单:系统执行违约结算');
|
|
198
|
+
notifyTransition(db, req.params.id, 'fault_seller', 'completed');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
console.error('[decline settleFault]', e);
|
|
203
|
+
}
|
|
204
|
+
return void res.json({
|
|
205
|
+
success: true, outcome: 'fault_seller', decline_reason_code: reasonCode,
|
|
206
|
+
note: '主观理由拒单 → 立即违约结算,买家已全额退款。',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// ── RFC-007 stage 5:卖家就【临时判责】发起仲裁举证 ───────────────────────────
|
|
210
|
+
// 仅卖家、仅 provisional(fault_seller + decline_objective_pending=1 未结算未过期)。置 decline_contested=1
|
|
211
|
+
// → checkTimeouts 暂停自动终结,等人工仲裁裁决(维持→declined_nofault 免责 / 驳回→违约)。
|
|
212
|
+
if (action === 'contest_decline') {
|
|
213
|
+
if (uid !== sellerId)
|
|
214
|
+
return void res.status(403).json({ error: '你不是本订单的卖家', error_code: 'NOT_ORDER_SELLER' });
|
|
215
|
+
if (order.status !== 'fault_seller' || Number(order.decline_objective_pending) !== 1 || order.settled_fault_at) {
|
|
216
|
+
return void res.status(400).json({ error: '本订单不是可举证的【临时判责】状态', error_code: 'NOT_PROVISIONAL_DECLINE' });
|
|
217
|
+
}
|
|
218
|
+
if (Number(order.decline_contested) === 1)
|
|
219
|
+
return void res.status(400).json({ error: '已在仲裁中,无需重复发起', error_code: 'ALREADY_CONTESTED' });
|
|
220
|
+
const overdue = db.prepare("SELECT datetime(decline_contest_deadline) < datetime('now') AS od FROM orders WHERE id = ?").get(req.params.id).od;
|
|
221
|
+
if (overdue)
|
|
222
|
+
return void res.status(400).json({ error: '举证窗口已过期,临时判责已可被终结', error_code: 'CONTEST_WINDOW_CLOSED' });
|
|
223
|
+
const evIds = [];
|
|
224
|
+
if (evidence_description) {
|
|
225
|
+
const eid = generateId('evt');
|
|
226
|
+
const evReasons = detectFraud(String(evidence_description));
|
|
227
|
+
db.prepare(`INSERT INTO evidence (id, order_id, uploader_id, type, description, file_hash, flag_reasons) VALUES (?,?,?,'description',?,?,?)`)
|
|
228
|
+
.run(eid, req.params.id, uid, evidence_description, `hash_${Date.now()}`, evReasons.length ? JSON.stringify(evReasons) : null);
|
|
229
|
+
evIds.push(eid);
|
|
230
|
+
}
|
|
231
|
+
db.prepare("UPDATE orders SET decline_contested = 1 WHERE id = ?").run(req.params.id);
|
|
232
|
+
return void res.json({
|
|
233
|
+
success: true, outcome: 'contested', evidence_ids: evIds,
|
|
234
|
+
note: '已就客观无责拒单发起人工仲裁举证。自动终结已暂停,等待仲裁员裁决:维持→免责全退+退质押,驳回→违约结算。',
|
|
235
|
+
});
|
|
236
|
+
}
|
|
145
237
|
const actionMap = {
|
|
146
238
|
accept: 'accepted', ship: 'shipped', pickup: 'picked_up',
|
|
147
239
|
transit: 'in_transit', deliver: 'delivered', confirm: 'confirmed', dispute: 'disputed'
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// RFC-011 §⑥ 事件游标流(纯 db 函数,party-gated)
|
|
2
|
+
import { listOrderEventsSince } from '../../layer0-foundation/L0-2-state-machine/order-chain.js';
|
|
1
3
|
export function registerOrdersReadRoutes(app, deps) {
|
|
2
4
|
const { db, auth, getOrderStatus, getOrderChain, verifyOrderChain, getOrderDispute } = deps;
|
|
3
5
|
app.get('/api/orders', (req, res) => {
|
|
@@ -112,6 +114,22 @@ export function registerOrdersReadRoutes(app, deps) {
|
|
|
112
114
|
const verification = verifyOrderChain(db, req.params.id);
|
|
113
115
|
res.json({ chain, verification });
|
|
114
116
|
});
|
|
117
|
+
// RFC-011 §⑥:事件游标流 —— 集成方 agent 拉"自 cursor 以来与我相关的订单变化"(agent 拉,非 webhook)。
|
|
118
|
+
// party-gated(只见自己当事的订单事件,= /chain 同口径,不变量 2:活性 ≤ 读边界);
|
|
119
|
+
// 结构性事件 + 哈希链字段(验链防篡改),完整 payload 仍走 party-gated /chain。
|
|
120
|
+
app.get('/api/agent/events', (req, res) => {
|
|
121
|
+
const user = auth(req, res);
|
|
122
|
+
if (!user)
|
|
123
|
+
return;
|
|
124
|
+
const since = typeof req.query.since === 'string' ? req.query.since : undefined;
|
|
125
|
+
const limit = Number(req.query.limit) || 50;
|
|
126
|
+
const r = listOrderEventsSince(db, user.id, since, limit);
|
|
127
|
+
res.setHeader('Cache-Control', 'no-store'); // 事件流不缓存
|
|
128
|
+
res.json({
|
|
129
|
+
...r,
|
|
130
|
+
note: 'Cursor stream of order events you are party to. Pass ?since=<next_cursor> to page. Pull, not push. event_hash+prev_event_hash verify chain integrity; full payload via GET /api/orders/:id/chain.',
|
|
131
|
+
});
|
|
132
|
+
});
|
|
115
133
|
app.get('/api/orders/:id', (req, res) => {
|
|
116
134
|
const user = auth(req, res);
|
|
117
135
|
if (!user)
|