@seasonkoh/webaz 0.1.29 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/bond-refund-blockers.js +50 -0
  2. package/dist/bond-slash.js +100 -0
  3. package/dist/bond-terms.js +31 -0
  4. package/dist/direct-pay-bond-rail-clearance.js +10 -5
  5. package/dist/direct-pay-cancel-refund.js +160 -0
  6. package/dist/direct-pay-create.js +78 -9
  7. package/dist/direct-pay-disclosures.js +17 -11
  8. package/dist/direct-pay-fee-ar.js +2 -2
  9. package/dist/direct-pay-fee-prepay-request.js +80 -0
  10. package/dist/direct-pay-returns.js +104 -0
  11. package/dist/direct-pay-stock.js +9 -0
  12. package/dist/direct-receive-deferral.js +30 -1
  13. package/dist/direct-receive-deposits.js +122 -17
  14. package/dist/free-shipping.js +37 -0
  15. package/dist/fx-rates.js +7 -0
  16. package/dist/layer0-foundation/L0-1-database/schema.js +246 -0
  17. package/dist/layer0-foundation/L0-2-state-machine/engine.js +1 -0
  18. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +73 -0
  19. package/dist/layer1-agent/L1-1-mcp-server/server.js +81 -16
  20. package/dist/layer2-business/L2-6-notifications/notification-engine.js +110 -41
  21. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +133 -31
  22. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +16 -4
  23. package/dist/layer3-trust/L3-1-dispute-engine/mutual-cancel.js +156 -0
  24. package/dist/platform-receive-accounts.js +94 -0
  25. package/dist/pwa/arbitration-read-admin.js +37 -0
  26. package/dist/pwa/arbitrator-lifecycle.js +100 -0
  27. package/dist/pwa/contract-fingerprint.js +15 -0
  28. package/dist/pwa/direct-pay-order-redaction.js +20 -2
  29. package/dist/pwa/human-presence.js +2 -6
  30. package/dist/pwa/public/app-account.js +9 -9
  31. package/dist/pwa/public/app-admin-disputes.js +55 -0
  32. package/dist/pwa/public/app-agent-appeal.js +90 -0
  33. package/dist/pwa/public/app-agent-approvals.js +93 -0
  34. package/dist/pwa/public/app-agent-pair.js +127 -0
  35. package/dist/pwa/public/app-arbitrator-admin.js +87 -0
  36. package/dist/pwa/public/app-arbitrator-entry.js +9 -0
  37. package/dist/pwa/public/app-bond-deferral-ui.js +9 -0
  38. package/dist/pwa/public/app-bond-refund-ui.js +66 -0
  39. package/dist/pwa/public/app-bond-slash-ui.js +74 -0
  40. package/dist/pwa/public/app-bond-terms-ui.js +23 -0
  41. package/dist/pwa/public/app-bond-ui.js +108 -0
  42. package/dist/pwa/public/app-chat-poll.js +6 -6
  43. package/dist/pwa/public/app-contribution-hub.js +23 -0
  44. package/dist/pwa/public/app-direct-pay-accounts.js +1 -1
  45. package/dist/pwa/public/app-direct-pay-buyer.js +1 -1
  46. package/dist/pwa/public/app-direct-pay-cancel-refund.js +72 -0
  47. package/dist/pwa/public/app-direct-pay-copy.js +12 -0
  48. package/dist/pwa/public/app-direct-pay-fee-history.js +39 -0
  49. package/dist/pwa/public/app-direct-pay-fee-ops.js +1 -1
  50. package/dist/pwa/public/app-direct-pay-fee-request.js +81 -0
  51. package/dist/pwa/public/app-direct-pay-fee-requests-admin.js +70 -0
  52. package/dist/pwa/public/app-direct-pay-memo.js +14 -0
  53. package/dist/pwa/public/app-direct-pay-negotiation.js +35 -0
  54. package/dist/pwa/public/app-direct-pay-pay.js +15 -0
  55. package/dist/pwa/public/app-direct-pay-paymodal.js +32 -0
  56. package/dist/pwa/public/app-direct-pay-reconcile.js +19 -0
  57. package/dist/pwa/public/app-direct-pay-returns.js +56 -0
  58. package/dist/pwa/public/app-direct-pay-reveal.js +82 -0
  59. package/dist/pwa/public/app-direct-pay-sales-report.js +70 -0
  60. package/dist/pwa/public/app-direct-pay.js +36 -37
  61. package/dist/pwa/public/app-dispute-close-ui.js +38 -0
  62. package/dist/pwa/public/app-free-shipping-ui.js +29 -0
  63. package/dist/pwa/public/app-gmv-rail-split.js +12 -0
  64. package/dist/pwa/public/app-listing-commerce-ui.js +72 -0
  65. package/dist/pwa/public/app-mutual-cancel.js +54 -0
  66. package/dist/pwa/public/app-notif-templates-orders.js +43 -0
  67. package/dist/pwa/public/app-notif-templates.js +22 -0
  68. package/dist/pwa/public/app-order-accept-ui.js +158 -0
  69. package/dist/pwa/public/app-order-errors.js +50 -0
  70. package/dist/pwa/public/app-order-labels.js +19 -0
  71. package/dist/pwa/public/app-order-rail-filter.js +26 -0
  72. package/dist/pwa/public/app-platform-receive-accounts.js +140 -0
  73. package/dist/pwa/public/app-poll-governor.js +22 -0
  74. package/dist/pwa/public/app-profile.js +4 -4
  75. package/dist/pwa/public/app-purchase-terms-ui.js +68 -0
  76. package/dist/pwa/public/app-sale-regions-ui.js +38 -0
  77. package/dist/pwa/public/app-seller.js +1 -1
  78. package/dist/pwa/public/app-trade-tax-ui.js +33 -0
  79. package/dist/pwa/public/app.js +133 -160
  80. package/dist/pwa/public/i18n.js +675 -5
  81. package/dist/pwa/public/index.html +41 -0
  82. package/dist/pwa/public/openapi.json +709 -11
  83. package/dist/pwa/public/style.css +3 -0
  84. package/dist/pwa/routes/admin-direct-receive-deposits.js +215 -3
  85. package/dist/pwa/routes/admin-protocol-params.js +16 -0
  86. package/dist/pwa/routes/admin-reports.js +31 -5
  87. package/dist/pwa/routes/agent-governance.js +1 -1
  88. package/dist/pwa/routes/agent-grants.js +253 -32
  89. package/dist/pwa/routes/analytics.js +2 -0
  90. package/dist/pwa/routes/arbitrator.js +67 -14
  91. package/dist/pwa/routes/bond-seller.js +162 -0
  92. package/dist/pwa/routes/direct-pay-cancel-refund.js +111 -0
  93. package/dist/pwa/routes/direct-pay-disclosure-acks.js +9 -4
  94. package/dist/pwa/routes/direct-pay-pending-accept.js +222 -0
  95. package/dist/pwa/routes/direct-pay-returns.js +74 -0
  96. package/dist/pwa/routes/direct-pay-timeouts.js +186 -9
  97. package/dist/pwa/routes/disputes-read.js +20 -6
  98. package/dist/pwa/routes/disputes-write.js +91 -33
  99. package/dist/pwa/routes/external-anchors.js +4 -2
  100. package/dist/pwa/routes/fee-prepay-requests.js +66 -0
  101. package/dist/pwa/routes/governance-onboarding.js +46 -8
  102. package/dist/pwa/routes/logistics.js +4 -4
  103. package/dist/pwa/routes/me-data.js +2 -2
  104. package/dist/pwa/routes/mutual-cancel.js +62 -0
  105. package/dist/pwa/routes/orders-action.js +182 -9
  106. package/dist/pwa/routes/orders-create.js +18 -7
  107. package/dist/pwa/routes/orders-read.js +76 -14
  108. package/dist/pwa/routes/platform-receive-accounts.js +111 -0
  109. package/dist/pwa/routes/products-create.js +13 -4
  110. package/dist/pwa/routes/products-update.js +15 -1
  111. package/dist/pwa/routes/profile-identity.js +4 -2
  112. package/dist/pwa/routes/returns.js +39 -8
  113. package/dist/pwa/routes/seller-directpay-report.js +110 -0
  114. package/dist/pwa/routes/seller-quota.js +5 -1
  115. package/dist/pwa/routes/shipping-templates.js +219 -0
  116. package/dist/pwa/routes/snf.js +4 -1
  117. package/dist/pwa/routes/webauthn.js +3 -3
  118. package/dist/pwa/server.js +59 -36
  119. package/dist/runtime/agent-grant-scopes.js +69 -1
  120. package/dist/runtime/webaz-schema-helpers.js +57 -3
  121. package/dist/sale-regions.js +116 -0
  122. package/dist/shipping-templates.js +119 -0
  123. package/dist/trade-tax.js +99 -0
  124. package/dist/trade-terms.js +76 -0
  125. package/dist/version.js +1 -1
  126. package/package.json +59 -2
@@ -253,6 +253,21 @@ export function initDatabase() {
253
253
  updated_at TEXT DEFAULT (datetime('now'))
254
254
  );
255
255
 
256
+ -- 平台(WebAZ)收款方式(admin 管理,可多个 active):卖家申请充值【平台服务费】时看到、据此线下付款。
257
+ -- ⚠️ 这是【平台侧】收款配置,不是卖家账号。改它 = 改平台收款流向 → 端点 root + Passkey 门。
258
+ -- instruction 是平台公开收款明细,给卖家看(非披露门)。qr 内联存(admin 精选、少量;写时 validateQrDataUri 校验 png/webp≤64KB)。
259
+ CREATE TABLE IF NOT EXISTS platform_receive_accounts (
260
+ id TEXT PRIMARY KEY,
261
+ label TEXT, -- 短标签(如 "PayNow-主" / "USDC-Base")
262
+ method TEXT, -- 收款方式(PayNow / Bank / USDC…)
263
+ currency TEXT, -- 币种(卖家据此付)
264
+ instruction TEXT NOT NULL, -- 平台收款明细(账号 / 钱包地址 / 链接),展示给卖家
265
+ qr_data_uri TEXT, -- 可选收款码,内联 data:image/(png|webp);base64(写时校验;≤64KB)
266
+ status TEXT NOT NULL DEFAULT 'active',-- active | inactive
267
+ created_at TEXT DEFAULT (datetime('now')),
268
+ updated_at TEXT DEFAULT (datetime('now'))
269
+ );
270
+
256
271
  -- 直付收款二维码图(Phase C):卖家收款码原始字节。【不可变、按内容寻址(per-seller)】—— ref = sha256(bytes),
257
272
  -- replace = 插新行,旧行永不改/删(触发器强制),这样订单可在建单时快照 (ref, seller_id) 并在 D1/D2 ack 后取回【当时那一版】QR。
258
273
  -- ⚠️ 主键 = (ref, seller_id):内容寻址【按卖家隔离】。两个卖家上传【相同】字节时各得一行(否则 INSERT OR IGNORE
@@ -445,6 +460,26 @@ export function initDatabase() {
445
460
  );
446
461
  CREATE INDEX IF NOT EXISTS idx_dp_fee_payments_seller ON direct_pay_fee_payments(seller_id);
447
462
 
463
+ -- 平台服务费【预充值申请】(卖家发起 → admin 核实真实到账后确认入账)。留痕:凭据必填、状态流转、关联入账 payment。
464
+ -- ⚠️ 申请【不动钱】—— 只有 admin 确认(PR3)才调 recordFeePrepay 记入余额。amount_units = base units(1 WAZ=1e6)。
465
+ -- platform_account_id = 付给哪个平台收款方式(见 platform_receive_accounts)。杜绝"场外直接付、无据可查"。
466
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_prepay_requests (
467
+ id TEXT PRIMARY KEY,
468
+ seller_id TEXT NOT NULL REFERENCES users(id),
469
+ amount_units INTEGER NOT NULL, -- 申请充值额(base units)
470
+ currency TEXT, -- 卖家声明的付款币种(展示;以实际到账为准)
471
+ platform_account_id TEXT NOT NULL REFERENCES platform_receive_accounts(id), -- 付给哪个平台收款方式(必选;admin 据此核对到账来源)
472
+ evidence_ref TEXT NOT NULL, -- 付款凭证号/流水(必填 —— 不能无据)
473
+ evidence_note TEXT,
474
+ status TEXT NOT NULL DEFAULT 'pending', -- pending | approved | rejected | cancelled
475
+ created_at TEXT DEFAULT (datetime('now')),
476
+ reviewed_by TEXT REFERENCES users(id), -- admin(PR3 approve/reject 时填,Passkey)
477
+ reviewed_at TEXT,
478
+ review_note TEXT,
479
+ resulting_payment_id TEXT REFERENCES direct_pay_fee_payments(id) -- approve 时关联的入账记录
480
+ );
481
+ CREATE INDEX IF NOT EXISTS idx_dp_fee_prepay_req_seller ON direct_pay_fee_prepay_requests(seller_id, status);
482
+
448
483
  -- ⏸ DORMANT(早先"AR 信用上限"模型预留;当前 = 首单宽限 + 预充值续用,额度即商家实际预付余额,无固定上限)。
449
484
  -- 本表与 ceiling_requests/invoices/invoice_items/invoice_events 当前【不写不读】;保留待将来需要时复用或清理。
450
485
  CREATE TABLE IF NOT EXISTS direct_pay_fee_ar_seller_overrides (
@@ -631,6 +666,14 @@ export function initDatabase() {
631
666
  db.exec(`ALTER TABLE orders ADD COLUMN direct_grace_deadline TEXT`);
632
667
  }
633
668
  catch { /* 已存在 */ } // Rail1 paid-but-timeout 宽限期:系统在此之前绝不关单(买家 →disputed 窗口)
669
+ try {
670
+ db.exec(`ALTER TABLE orders ADD COLUMN payment_query_deadline TEXT`);
671
+ }
672
+ catch { /* 已存在 */ } // Rail1 货款协商:卖家报未收款后买家响应宽限(过期→卖家可请求取消)
673
+ try {
674
+ db.exec(`ALTER TABLE orders ADD COLUMN payment_query_cancel_deadline TEXT`);
675
+ }
676
+ catch { /* 已存在 */ } // Rail1 货款协商:卖家请求取消后买家 7 天申诉窗(过期→cron 关单;窗内买家可升举证仲裁)
634
677
  try {
635
678
  db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_instruction_snapshot TEXT`);
636
679
  }
@@ -668,6 +711,158 @@ export function initDatabase() {
668
711
  db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_decision_code TEXT`);
669
712
  }
670
713
  catch { /* 已存在 */ }
714
+ // 手动接单模式(v16):卖家可选 auto/manual(单品覆盖 ?? 店铺默认 ?? auto);manual 直付单先进 pending_accept。
715
+ try {
716
+ db.exec(`ALTER TABLE products ADD COLUMN accept_mode TEXT`);
717
+ }
718
+ catch { /* 已存在 */ } // 'auto'|'manual'|NULL(=继承店铺默认)
719
+ try {
720
+ db.exec(`ALTER TABLE users ADD COLUMN store_accept_mode TEXT`);
721
+ }
722
+ catch { /* 已存在 */ } // 店铺级默认 'auto'|'manual'|NULL(=auto)
723
+ try {
724
+ db.exec(`ALTER TABLE orders ADD COLUMN accept_mode_snapshot TEXT`);
725
+ }
726
+ catch { /* 已存在 */ } // 下单时快照(卖家事后改不影响在途单)
727
+ try {
728
+ db.exec(`ALTER TABLE orders ADD COLUMN pending_accept_deadline TEXT`);
729
+ }
730
+ catch { /* 已存在 */ } // 接单窗(专属 cron 读;超时无责取消+回补库存)
731
+ // 运费模板(PR-2):按收货地区预设运费/时效;下单命中 → 运费并入总额并快照(卖家改模板不影响在途单)。
732
+ try {
733
+ db.exec(`ALTER TABLE products ADD COLUMN shipping_template TEXT`);
734
+ }
735
+ catch { /* 已存在 */ } // JSON [{region,fee,est_days}];NULL=继承店铺默认
736
+ try {
737
+ db.exec(`ALTER TABLE users ADD COLUMN store_shipping_template TEXT`);
738
+ }
739
+ catch { /* 已存在 */ } // 店铺级默认模板
740
+ try {
741
+ db.exec(`ALTER TABLE orders ADD COLUMN ship_to_region TEXT`);
742
+ }
743
+ catch { /* 已存在 */ } // 买家下单所选收货地区(结构化,非自由文本地址)
744
+ try {
745
+ db.exec(`ALTER TABLE orders ADD COLUMN shipping_fee DECIMAL(18,2)`);
746
+ }
747
+ catch { /* 已存在 */ } // 下单快照运费(已并入 total_amount;NULL=无模板旧单)
748
+ try {
749
+ db.exec(`ALTER TABLE orders ADD COLUMN shipping_est_days TEXT`);
750
+ }
751
+ catch { /* 已存在 */ } // 下单快照预计时效(展示;不接判责钟)
752
+ // 询价握手(PR-3,直付轨):模板外地区先报价后接单。quote_ok=卖家 opt-in(单品??店铺,默认关)。
753
+ try {
754
+ db.exec(`ALTER TABLE products ADD COLUMN shipping_quote_ok INTEGER`);
755
+ }
756
+ catch { /* 已存在 */ } // 1|0|NULL(=继承店铺)
757
+ try {
758
+ db.exec(`ALTER TABLE users ADD COLUMN store_shipping_quote_ok INTEGER`);
759
+ }
760
+ catch { /* 已存在 */ } // 店铺级默认(NULL=关)
761
+ try {
762
+ db.exec(`ALTER TABLE orders ADD COLUMN shipping_quote_required INTEGER`);
763
+ }
764
+ catch { /* 已存在 */ } // 1=本单须先报价(pending_accept 内子流)
765
+ try {
766
+ db.exec(`ALTER TABLE orders ADD COLUMN shipping_quote_fee DECIMAL(18,2)`);
767
+ }
768
+ catch { /* 已存在 */ } // 卖家报价运费(买家确认后并入 total_amount)
769
+ try {
770
+ db.exec(`ALTER TABLE orders ADD COLUMN shipping_quote_est_days TEXT`);
771
+ }
772
+ catch { /* 已存在 */ }
773
+ try {
774
+ db.exec(`ALTER TABLE orders ADD COLUMN shipping_quote_note TEXT`);
775
+ }
776
+ catch { /* 已存在 */ }
777
+ try {
778
+ db.exec(`ALTER TABLE orders ADD COLUMN shipping_quote_at TEXT`);
779
+ }
780
+ catch { /* 已存在 */ }
781
+ // 跨境交易条款骨架(S0):清关/物流证据字段(即时开放,展示+快照证据,零计费逻辑=守 ERP 边界)+
782
+ // 结构化规则列(sale_regions/tax_lines/import_duty_terms 先建列【不开 API】—— 不上假开关:S1 带可售 gate、
783
+ // S3 带税费明细进 total 时才各自开放;既有 ship_regions 是自由文本仅展示,真相源=这些结构化列)。
784
+ try {
785
+ db.exec(`ALTER TABLE products ADD COLUMN weight_kg REAL`);
786
+ }
787
+ catch { /* 已存在 */ } // 重量(S0 起 products-update 也写此列 → 收进 base schema,不再只靠 server.ts 迁移;裸 init 路径一致有列)
788
+ try {
789
+ db.exec(`ALTER TABLE products ADD COLUMN package_size TEXT`);
790
+ }
791
+ catch { /* 已存在 */ } // 长x宽x高 cm 文本(证据/报价参考)
792
+ try {
793
+ db.exec(`ALTER TABLE products ADD COLUMN origin_country TEXT`);
794
+ }
795
+ catch { /* 已存在 */ } // 发货国(ISO 区码)
796
+ try {
797
+ db.exec(`ALTER TABLE products ADD COLUMN country_of_origin TEXT`);
798
+ }
799
+ catch { /* 已存在 */ } // 原产国(清关申报)
800
+ try {
801
+ db.exec(`ALTER TABLE products ADD COLUMN customs_description TEXT`);
802
+ }
803
+ catch { /* 已存在 */ } // 报关品名(英文)
804
+ try {
805
+ db.exec(`ALTER TABLE products ADD COLUMN hs_code TEXT`);
806
+ }
807
+ catch { /* 已存在 */ } // HS 编码(可选)
808
+ try {
809
+ db.exec(`ALTER TABLE products ADD COLUMN sale_regions TEXT`);
810
+ }
811
+ catch { /* 已存在 */ } // S1 gate 消费:{mode,include,exclude} JSON(NULL=继承店铺)
812
+ try {
813
+ db.exec(`ALTER TABLE products ADD COLUMN tax_lines TEXT`);
814
+ }
815
+ catch { /* 已存在 */ } // S3 消费:按目的区税费科目 JSON(NULL=继承店铺)
816
+ try {
817
+ db.exec(`ALTER TABLE products ADD COLUMN import_duty_terms TEXT`);
818
+ }
819
+ catch { /* 已存在 */ } // 'ddu'|'ddp'|NULL(=继承店铺;跨境进口税责声明)
820
+ try {
821
+ db.exec(`ALTER TABLE users ADD COLUMN store_sale_regions TEXT`);
822
+ }
823
+ catch { /* 已存在 */ }
824
+ try {
825
+ db.exec(`ALTER TABLE users ADD COLUMN store_tax_lines TEXT`);
826
+ }
827
+ catch { /* 已存在 */ }
828
+ try {
829
+ db.exec(`ALTER TABLE users ADD COLUMN store_import_duty_terms TEXT`);
830
+ }
831
+ catch { /* 已存在 */ }
832
+ try {
833
+ db.exec(`ALTER TABLE orders ADD COLUMN trade_terms_snapshot TEXT`);
834
+ }
835
+ catch { /* 已存在 */ } // 下单冻结的交易条款 JSON(运费来源/时效/退货/清关字段/税责声明;商家事后改设置不影响旧订单,争议依据)
836
+ // 营销域满额免邮(S2 返工:从运费模板移出 —— 模板=成本结构,免邮=促销;供应商报价期规则不搬家)
837
+ try {
838
+ db.exec(`ALTER TABLE products ADD COLUMN free_shipping_threshold DECIMAL(18,2)`);
839
+ }
840
+ catch { /* 已存在 */ }
841
+ try {
842
+ db.exec(`ALTER TABLE users ADD COLUMN store_free_shipping_threshold DECIMAL(18,2)`);
843
+ }
844
+ catch { /* 已存在 */ }
845
+ // ── 商品【非钱路】扩展列收敛到 base(测试 schema 初始化单一来源;与 weight_kg S4 同法)──
846
+ // products-create/update/read 写这些属性/溯源/物流/退货/i18n/库存/信誉计数列;历史上只在 server.ts 迁移段(或 MCP register-list-search 助手),
847
+ // 裸 initDatabase / bridge / 测试缺列 → 各测试各自手动补 ALTER(band-aid)。收进 base 后每条 init 路径一致有列,band-aid 可删。
848
+ // server.ts 内联迁移【保留不动】(byte-identical guarded ALTER,生产迁移序不变;非 wholesale 抽取,不碰钱/单/状态迁移)。
849
+ // 刻意排除:commission_rate(佣金=钱路,不进);stake_amount / images 已在 CREATE TABLE products。
850
+ for (const col of [
851
+ 'specs TEXT', 'brand TEXT', 'model TEXT', 'source_price REAL', 'ship_regions TEXT DEFAULT "全国"', // 属性 + 溯源参考 + 既有自由文本运费地区
852
+ 'handling_hours INTEGER DEFAULT 24', 'estimated_days TEXT', 'fragile INTEGER DEFAULT 0', // 履约:处理时长/预计时效/易碎
853
+ 'return_days INTEGER DEFAULT 7', 'return_condition TEXT', 'warranty_days INTEGER DEFAULT 0', // 退货/保修承诺
854
+ 'source_url TEXT', 'source_price_at TEXT', // 溯源链接 + 采价时间
855
+ 'commitment_hash TEXT', 'description_hash TEXT', 'price_hash TEXT', 'hashed_at TEXT', // 承诺哈希(内容/描述/价格),非价格数值本身
856
+ 'origin_claims TEXT', 'i18n_titles TEXT', 'i18n_descs TEXT', `product_type TEXT DEFAULT 'retail'`, 'has_variants INTEGER DEFAULT 0', // 声明/多语言/分型
857
+ 'last_sold_at TEXT', 'first_sold_at TEXT', // 销售生命周期时间戳(展示/排序)
858
+ 'completion_count INTEGER DEFAULT 0', 'dispute_loss_count INTEGER DEFAULT 0', 'claim_loss_count INTEGER DEFAULT 0', 'value_badge INTEGER DEFAULT 0', // 信誉/价值计数(纯列,不含状态机/结算逻辑)
859
+ 'low_stock_threshold INTEGER DEFAULT 3', 'auto_delist_on_zero INTEGER DEFAULT 1', 'low_stock_alerted_at TEXT', // 低库存提醒/自动下架(库存运营)
860
+ ]) {
861
+ try {
862
+ db.exec(`ALTER TABLE products ADD COLUMN ${col}`);
863
+ }
864
+ catch { /* 已存在 */ }
865
+ }
671
866
  try {
672
867
  db.exec(`ALTER TABLE wallets ADD COLUMN fee_staked REAL DEFAULT 0`);
673
868
  }
@@ -689,6 +884,57 @@ export function initDatabase() {
689
884
  db.exec(`ALTER TABLE direct_receive_deposits ADD COLUMN production_policy_version TEXT`);
690
885
  }
691
886
  catch { /* 已存在 */ }
887
+ try {
888
+ db.exec(`ALTER TABLE direct_receive_deposits ADD COLUMN reject_note TEXT`);
889
+ }
890
+ catch { /* 已存在 */ } // B1:admin 驳回申报说明(卖家可见)
891
+ try {
892
+ db.exec(`ALTER TABLE direct_receive_deposits ADD COLUMN refund_requested_at TEXT`);
893
+ }
894
+ catch { /* 已存在 */ } // B2:退出申请时间(冷静期锚点)
895
+ try {
896
+ db.exec(`ALTER TABLE direct_receive_deposits ADD COLUMN refund_evidence_ref TEXT`);
897
+ }
898
+ catch { /* 已存在 */ } // B2:场外退还凭据(admin 执行时记录)
899
+ try {
900
+ db.exec(`ALTER TABLE direct_receive_deposits ADD COLUMN terms_version TEXT`);
901
+ }
902
+ catch { /* 已存在 */ } // 条款同意版本快照(缴纳前强制同意;罚没/退还的合同依据)
903
+ try {
904
+ db.exec(`ALTER TABLE direct_receive_deposits ADD COLUMN platform_account_id TEXT`);
905
+ }
906
+ catch { /* 已存在 */ } // 缴到哪个平台收款账户(多币种;币种由账户推导)
907
+ // B3:保证金罚没提案(人工铁律:仲裁裁定卖家责的直付争议 → admin 提案 → 冷静期 → ROOT+Passkey 执行;绝不自动)。
908
+ db.exec(`
909
+ CREATE TABLE IF NOT EXISTS bond_slash_proposals (
910
+ id TEXT PRIMARY KEY,
911
+ deposit_id TEXT NOT NULL,
912
+ seller_id TEXT NOT NULL,
913
+ dispute_id TEXT NOT NULL, -- 依据争议(须 resolved 且 ruling ∈ 卖家责;direct_p2p 单)
914
+ reason TEXT,
915
+ status TEXT NOT NULL DEFAULT 'proposed' CHECK (status IN ('proposed','executed','cancelled')),
916
+ cooling_until TEXT NOT NULL, -- 绝对截止(propose 时按 param 计算;执行须晚于此)
917
+ proposed_by TEXT NOT NULL,
918
+ proposed_at TEXT DEFAULT (datetime('now')),
919
+ executed_at TEXT,
920
+ executed_txn_id TEXT,
921
+ cancelled_at TEXT,
922
+ cancel_note TEXT
923
+ );
924
+ `);
925
+ try {
926
+ db.exec('CREATE INDEX IF NOT EXISTS idx_bond_slash_seller ON bond_slash_proposals(seller_id, status)');
927
+ }
928
+ catch { /* 已存在 */ }
929
+ // B4:缓交收口 —— 到期前提醒去重锚点 + 缴清转正式时间戳(ALTER after CREATE 铁律)。
930
+ try {
931
+ db.exec(`ALTER TABLE direct_receive_deferrals ADD COLUMN reminder_sent_at TEXT`);
932
+ }
933
+ catch { /* 已存在 */ }
934
+ try {
935
+ db.exec(`ALTER TABLE direct_receive_deferrals ADD COLUMN satisfied_at TEXT`);
936
+ }
937
+ catch { /* 已存在 */ }
692
938
  // PR-6A: sanctions 结论有有效期(过期 → fail-closed)。additive nullable;NULL = 无期限(不过期)。
693
939
  try {
694
940
  db.exec(`ALTER TABLE sanctions_screening ADD COLUMN expires_at TEXT`);
@@ -478,6 +478,7 @@ function getActiveDeadline(order, db) {
478
478
  // 修:picked_up 状态视为"已揽收,等运输/投递",下一个 deadline 是 delivery_deadline
479
479
  // QA 轮 7 P1(另一条):disputed 状态下没读 dispute_cases 的 arbitrate_deadline → agent 不知道仲裁还有多久
480
480
  const deadlineMapMarket = {
481
+ pending_accept: 'pending_accept_deadline', // 手动接单(v16):等卖家确认,超时无责取消
481
482
  created: 'pay_deadline',
482
483
  paid: 'accept_deadline',
483
484
  accepted: 'ship_deadline',
@@ -28,6 +28,28 @@ export const VALID_TRANSITIONS = {
28
28
  requiresEvidence: false,
29
29
  description: 'Rail1 直付:卖家已质押平台费、展示收款方式,进入付款窗口'
30
30
  },
31
+ // ── 手动接单模式(accept_mode='manual',v16):付款前先等卖家确认接单 ──────────
32
+ // 直付轨:created→pending_accept(替代 created→direct_pay_window),卖家接单才进付款窗口 ——
33
+ // 非托管无 hold 可言,时序门(接单前不出示收款信息)是唯一正确的付款风控。
34
+ // escrow 轨不用本状态:escrow 本身就是条件中间态(付款进托管,接单才继续,超时自动全额退回)。
35
+ 'created→pending_accept': {
36
+ allowedRoles: ['system'], // 建单路由按 accept_mode 快照以 system 推进(与 created→direct_pay_window 同模式)
37
+ requiresEvidence: false,
38
+ description: '手动接单:订单等待卖家确认接单(付款前,不展示收款信息)'
39
+ },
40
+ 'pending_accept→direct_pay_window': {
41
+ allowedRoles: ['seller', 'system'],
42
+ requiresEvidence: false,
43
+ description: '卖家确认接单 → 进入直付付款窗口(此刻起买家方可见收款信息)'
44
+ },
45
+ 'pending_accept→cancelled': {
46
+ // deadlineField 供专属 cron(direct-pay-timeouts.ts)读取;故意不设 autoFaultState →
47
+ // 通用 engine.checkTimeouts 绝不触发本转移(会漏库存回补等副作用)。超时=无责取消(没人付过钱)。
48
+ allowedRoles: ['seller', 'buyer', 'system'],
49
+ deadlineField: 'pending_accept_deadline',
50
+ requiresEvidence: false,
51
+ description: '接单前取消:卖家谢绝 / 买家撤单 / 超时未接单(system) → 无责取消 + 回补库存'
52
+ },
31
53
  'direct_pay_window→accepted': {
32
54
  allowedRoles: ['buyer'],
33
55
  requiresEvidence: false,
@@ -57,6 +79,53 @@ export const VALID_TRANSITIONS = {
57
79
  requiresEvidence: false,
58
80
  description: 'Rail1 直付:买家确认未付 / 宽限期后系统关单(终态)'
59
81
  },
82
+ // ── Direct Pay (Rail 1) 取消退款握手(审计项 C):买家已付款、卖家未发货 → 三步握手无责关单 ──────────
83
+ // request(买家)→ mark_refunded(卖家声明已场外退款)→ confirm(买家确认收到,Passkey)→ 本边(system 执行)。
84
+ // 仅 system:买家不能单方取消已付款订单,必须经握手(域模块 src/direct-pay-cancel-refund.ts 收口,非托管零资金)。
85
+ 'accepted→cancelled': {
86
+ allowedRoles: ['system'],
87
+ requiresEvidence: false,
88
+ description: 'Rail1 直付:取消退款握手完成(卖家已场外退款+买家确认收到)→ 系统无责关单,恢复库存'
89
+ },
90
+ // ── Direct Pay (Rail 1) 货款协商(争议≠仲裁):卖家报未收款 → 双方先协商,谈不拢才升举证仲裁 ──────────
91
+ 'accepted→payment_query': {
92
+ allowedRoles: ['seller'],
93
+ requiresEvidence: false,
94
+ description: 'Rail1 直付:卖家报告未收到货款 → 进入协商(非仲裁),暂停履约。买家可取消/主张已付,卖家可确认已收/(宽限后)取消'
95
+ },
96
+ 'payment_query→accepted': {
97
+ allowedRoles: ['seller'],
98
+ requiresEvidence: false,
99
+ description: 'Rail1 直付:卖家确认已收到货款 → 恢复原订单继续履约'
100
+ },
101
+ 'payment_query→cancelled': {
102
+ // buyer=主动取消(承认未付);seller=买家静默宽限后请求取消;system=cron 在 7 天买家申诉窗口后终结。时序/窗口由路由+cron 守。
103
+ allowedRoles: ['buyer', 'seller', 'system'],
104
+ requiresEvidence: false,
105
+ description: 'Rail1 直付:协商关单(买家承认未付 / 卖家宽限后取消 / 系统申诉窗口后终结)'
106
+ },
107
+ 'payment_query→disputed': {
108
+ allowedRoles: ['buyer', 'seller'],
109
+ requiresEvidence: true,
110
+ evidenceHint: '上传付款/未收款凭证(链上 tx / 银行回执 / 截图)',
111
+ description: 'Rail1 直付:协商未果(买家主张已付 + 卖家否认)→ 升级举证仲裁(证据制信誉裁决,不涉资金)'
112
+ },
113
+ 'disputed→payment_query': {
114
+ // 裁定前回到协商的两条路径:①买卖任一方【撤回仲裁申请】(pq_withdraw);②仲裁员【驳回仲裁,退回协商】——
115
+ // 协议(sys_protocol)在仲裁员现场 Passkey 授权后代执行,故加 'system'。均仅裁定前有效(裁定后 disputed 已终结),时序/授权由路由守。
116
+ allowedRoles: ['buyer', 'seller', 'system'],
117
+ requiresEvidence: false,
118
+ description: 'Rail1 直付:回到协商(裁定前)—— 买卖双方撤回仲裁,或仲裁员驳回仲裁退回协商(system 代执行)'
119
+ },
120
+ 'disputed→confirmed': {
121
+ // 争议协商收口(买家侧):买家撤诉并确认收货 —— 包裹晚到/放代收点后来找到等场景,不必只剩仲裁一条收口。
122
+ // 限定【delivered 来源的履约争议 + 争议发起人本人 + 裁定前】,来源/发起人/时序由路由权威门守
123
+ // (orders-action dispute_withdraw_confirm,与 disputed→payment_query 的 pq_withdraw 同模式);
124
+ // 同一事务内争议 dismissed + confirmed→completed + settleOrder(与既有 confirm 结算完全同链)。
125
+ allowedRoles: ['buyer'],
126
+ requiresEvidence: false,
127
+ description: '买家撤诉并确认收货(裁定前,限履约争议):争议无责关闭,订单正常完成结算'
128
+ },
60
129
  // ── 卖家接单 ──────────────────────────────────────────────
61
130
  'paid→accepted': {
62
131
  allowedRoles: ['seller'],
@@ -282,6 +351,8 @@ export const CURRENT_RESPONSIBLE = {
282
351
  disputed: 'arbitrator', // 等仲裁处理
283
352
  direct_pay_window: 'buyer', // Rail1: 等买家付款并标记
284
353
  direct_expired_unconfirmed: 'buyer', // Rail1: 等买家确认未付 或 升级争议
354
+ payment_query: 'buyer', // Rail1: 卖家报未收款,等买家协商响应(取消/主张已付)
355
+ pending_accept: 'seller', // 手动接单:等卖家确认接单(超时无责取消)
285
356
  };
286
357
  /** Phase 1 self-fulfill 覆盖表:seller 一人承担 shipped/picked_up/in_transit 全程 */
287
358
  export const CURRENT_RESPONSIBLE_SELF_FULFILL = {
@@ -316,6 +387,8 @@ export const ORDER_STATE_MEANINGS = {
316
387
  expired: { zh: '订单超时自动失败(通用兜底,终态)', en: 'order expired (generic timeout, terminal)' },
317
388
  direct_pay_window: { zh: 'Rail1 直付:已质押平台费,等买家付款(协议不持货款)', en: 'Rail1 direct-pay: fee-staked, awaiting buyer off-protocol payment (protocol holds no funds)' },
318
389
  direct_expired_unconfirmed: { zh: 'Rail1 直付:付款窗口超时未标记(不静默关单,留争议/确认窗口)', en: 'Rail1 direct-pay: payment window expired unmarked (not silently closed; dispute/confirm window open)' },
390
+ payment_query: { zh: 'Rail1 直付:卖家报未收货款,双方协商中(非仲裁;暂停履约)', en: 'Rail1 direct-pay: seller reported non-receipt, buyer↔seller negotiating (not arbitration; fulfillment paused)' },
391
+ pending_accept: { zh: '手动接单:等待卖家确认接单(付款前;直付轨此阶段不展示收款信息,超时无责取消)', en: 'manual accept: awaiting seller acceptance (pre-payment; direct-pay hides payment info at this stage; times out to no-fault cancel)' },
319
392
  };
320
393
  /** 订单/争议生命周期契约 —— 集成方 agent 读它即懂"订单怎么流转 + 每步谁驱动 + 何时 + 含义"。 */
321
394
  export function orderLifecycleContract() {
@@ -31,7 +31,8 @@ import { join as pathJoin } from 'node:path';
31
31
  import { existsSync as fsExists, mkdirSync, writeFileSync, readFileSync, unlinkSync, chmodSync } from 'node:fs';
32
32
  import { execFileSync } from 'node:child_process';
33
33
  import { transition, getOrderStatus, initSystemUser, } from '../../layer0-foundation/L0-2-state-machine/engine.js';
34
- import { initDisputeSchema, createDispute, respondToDispute, getDisputeDetails, getOrderDispute, getOpenDisputes, } from '../../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
34
+ import { initDisputeSchema, createDispute, respondToDispute, getDisputeDetails, getOrderDispute, getOpenDisputes, isActiveWhitelistArbitrator, // 仲裁能力唯一源=active 白名单;legacy user.role==='arbitrator' 旁路已移除
35
+ } from '../../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
35
36
  import { initNotificationSchema, notifyTransition, getNotifications, getUnreadCount, markRead, } from '../../layer2-business/L2-6-notifications/notification-engine.js';
36
37
  import { initSkillSchema, publishSkill, listSkills, getMySkills, subscribeSkill, unsubscribeSkill, getMySubscriptions, formatSkillForAgent, shouldAutoAccept, SKILL_TYPE_META, } from '../../layer4-economics/L4-4-skill-market/skill-engine.js';
37
38
  import { initReputationSchema, recordOrderReputation, getReputation, getSearchBoost, getStakeDiscount, } from '../../layer4-economics/L4-3-reputation/reputation-engine.js';
@@ -325,7 +326,51 @@ export async function handlePair(args) {
325
326
  note: 'Grant verified LIVE by the server (per-call: active/expiry/revoked/subject-suspension/scope + audited). `grant` is authoritative; `local_cache` is advisory. Safe scopes only; no business tool or risk scope consumes this grant.',
326
327
  };
327
328
  }
328
- return { error: `unknown action "${action}" — use "start" | "complete" | "verify"`, error_code: 'BAD_ACTION' };
329
+ if (action === 'request') {
330
+ // The agent asks the human to EXPAND this grant with more SAFE scope / a permission bundle. Uses the
331
+ // stored grant bearer; the server binds the request to (human, grant). Approval is Passkey-gated on the
332
+ // human's side. Only SAFE scopes — risk/never-delegable are structurally rejected.
333
+ const cred = resolveGrantCredential();
334
+ if (!cred)
335
+ return { status: 'not_paired', error_code: 'NO_GRANT_CREDENTIAL', hint: 'No stored grant. Pair first: webaz_pair action="start" → human approves → action="complete".' };
336
+ const bundle = typeof args.bundle === 'string' ? args.bundle : undefined;
337
+ const scopes = Array.isArray(args.scopes) ? args.scopes.map(String).filter(Boolean) : undefined;
338
+ if (!bundle && (!scopes || !scopes.length))
339
+ return { error: 'provide a bundle (e.g. "catalog_agent") OR a scopes[] array of SAFE scopes', error_code: 'NO_SCOPES' };
340
+ const resp = await apiCall('/api/agent-grants/permission-requests', {
341
+ method: 'POST', apiKey: cred.token,
342
+ body: { bundle, scopes, reason: typeof args.reason === 'string' ? args.reason : undefined, duration: typeof args.duration === 'string' ? args.duration : undefined },
343
+ });
344
+ if (resp.error)
345
+ return resp;
346
+ return {
347
+ status: 'requested',
348
+ approval_id: resp.approval_id,
349
+ approval_url: `${WEBAZ_API_URL}${String(resp.approval_url || '')}`,
350
+ requested_scopes: resp.requested_scopes,
351
+ permission_bundle: resp.permission_bundle,
352
+ risk_level: resp.risk_level,
353
+ suggested_duration: resp.suggested_duration,
354
+ next: 'Ask the human to open approval_url (logged in at webaz.xyz) and approve with their Passkey. Then simply RETRY your original call — on approval your grant carries the new scope. Use action="requests" to poll status.',
355
+ note: 'Only SAFE (read/draft) scopes can be requested; risk actions are never delegated to a persistent grant.',
356
+ };
357
+ }
358
+ if (action === 'requests') {
359
+ // Poll THIS grant's own permission requests + their status (pending/approved/rejected) without hitting the
360
+ // target surface. Grant-authed, grant-scoped (you see only your own requests). Server audits every call.
361
+ const cred = resolveGrantCredential();
362
+ if (!cred)
363
+ return { status: 'not_paired', error_code: 'NO_GRANT_CREDENTIAL', hint: 'No stored grant. Pair first: webaz_pair action="start" → human approves → action="complete".' };
364
+ const resp = await apiCall('/api/agent-grants/my-permission-requests', { method: 'GET', apiKey: cred.token });
365
+ if (resp.error)
366
+ return resp;
367
+ return {
368
+ status: 'ok',
369
+ requests: resp.requests,
370
+ note: 'Your grant\'s own permission requests + status. On "approved", just retry your original call — the grant now carries the scope. "pending" → the human has not approved yet.',
371
+ };
372
+ }
373
+ return { error: `unknown action "${action}" — use "start" | "complete" | "verify" | "request" | "requests"`, error_code: 'BAD_ACTION' };
329
374
  }
330
375
  // 启动 banner(stderr)+ status 声明用 —— 让用户/agent 一眼知道现在是真网络还是沙盒
331
376
  function modeBanner() {
@@ -398,8 +443,12 @@ db.exec(`
398
443
  error_msg TEXT
399
444
  )
400
445
  `);
401
- db.exec(`CREATE INDEX IF NOT EXISTS idx_mcp_tool_calls_ts ON mcp_tool_calls(ts)`);
402
- db.exec(`CREATE INDEX IF NOT EXISTS idx_mcp_tool_calls_tool ON mcp_tool_calls(tool_name, ts)`);
446
+ // 索引命名与 runtime bridge(apply-webaz-runtime-schema)统一 —— 两组合根曾各自命名(idx_mcp_tool_calls_* vs idx_mcp_tc_*)
447
+ // → 同库跑过两根就出现同列重复索引,且 pg 产物逐列/索引 parity 因根不同而漂移。老名幂等清除。
448
+ db.exec(`DROP INDEX IF EXISTS idx_mcp_tool_calls_ts`);
449
+ db.exec(`DROP INDEX IF EXISTS idx_mcp_tool_calls_tool`);
450
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_mcp_tc_ts ON mcp_tool_calls(ts)`);
451
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_mcp_tc_tool ON mcp_tool_calls(tool_name, ts)`);
403
452
  // ─── 4 层身份模型 helpers(与 PWA server 同源逻辑)───────────────
404
453
  const PERMA_ALPHABET_MCP = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
405
454
  function mcpGeneratePermanentCode() {
@@ -486,14 +535,21 @@ No auth required, no parameters needed.
486
535
  2. action="complete" → retrieves the credential ONCE (PKCE) and stores it in your OS secret store (macOS Keychain → ~/.webaz/credentials 0600 fallback). Returns only a credential_handle + status — the raw token is never shown.
487
536
  3. action="verify" → uses the stored grant against a safe read endpoint to confirm it is still valid. The server re-checks active/expiry/revoked/suspension/scope and audits the call every time. Returns the grant principal/status; never the raw token.
488
537
 
489
- Scopes: SAFE only (read_public, profile_read, search, list_product_draft, product_publish_request, draft_order). Risk scopes (place_order/wallet/refund/...) and never-delegable actions (withdraw/key-change/vote/...) are hard-rejected.
538
+ Need MORE scope later? If a call returns error_code="PERMISSION_REQUIRED", do NOT re-pair:
539
+ 4. action="request" (bundle="catalog_agent" OR scopes=[...]) → asks the human to expand THIS grant. Returns an approval_id + approval_url; the human approves with a Passkey, then you simply RETRY your original call.
540
+ 5. action="requests" → lists your grant's own requests + status (pending/approved/rejected) so you can poll instead of hitting the target surface.
541
+
542
+ Scopes: SAFE only (read_public, profile_read, search, list_product_draft, product_publish_request, draft_order + seller_*_read / seller_product_draft / seller_pricing_suggestion; bundle "catalog_agent"). Risk scopes (place_order/wallet/refund/...) and never-delegable actions (withdraw/key-change/vote/...) are hard-rejected.
490
543
 
491
544
  NOTE: this consumes the grant only on safe read paths; no business tool and no risk scope is wired to grants. No account is created (registration stays human-only at webaz.xyz).`,
492
545
  inputSchema: {
493
546
  type: 'object',
494
547
  properties: {
495
- action: { type: 'string', enum: ['start', 'complete', 'verify'], description: 'start a pairing, complete it after the human approves, or verify the stored grant (default: start)' },
496
- capabilities: { type: 'array', items: { type: 'string' }, description: 'Requested SAFE scopes (default: read_public, search). Non-safe scopes are rejected.' },
548
+ action: { type: 'string', enum: ['start', 'complete', 'verify', 'request', 'requests'], description: 'start a pairing, complete it after the human approves, verify the stored grant, request MORE scope (bundle/scopes), or list your requests (default: start)' },
549
+ capabilities: { type: 'array', items: { type: 'string' }, description: 'action="start": requested SAFE scopes (default: read_public, search). Non-safe scopes are rejected.' },
550
+ bundle: { type: 'string', description: 'action="request": a permission bundle key (e.g. "catalog_agent") — a named all-safe scope set the human approves as one thing.' },
551
+ scopes: { type: 'array', items: { type: 'string' }, description: 'action="request": individual SAFE scopes to request (alternative to bundle).' },
552
+ duration: { type: 'string', enum: ['once', '1h', '24h', '7d', '30d'], description: 'action="request": how long the expanded grant should last (safe scopes may be long-term; the server caps by risk).' },
497
553
  agent_label: { type: 'string', description: 'Human-friendly name for this agent (shown in the consent screen)' },
498
554
  reason: { type: 'string', description: 'Free-text reason shown to the human (you cannot relabel the scopes)' },
499
555
  },
@@ -810,9 +866,9 @@ Actions:
810
866
  - list_open pending disputes (arbitrators only)
811
867
  - respond respondent rebuttal (before 48h deadline)
812
868
  - add_evidence any participant supplements
813
- - arbitrate ruling + fund disposition (assigned arbitrator only)
869
+ - arbitrate ruling (assigned arbitrator only). ESCROW: fund disposition. DIRECT PAY (payment_rail=direct_p2p, non-custodial): reputation-only ruling — NO refund/release/arbitration-fee, NO amounts.
814
870
 
815
- Ruling options: refund_buyer | release_seller | partial_refund (needs refund_amount; optional liable_party → 3rd-party fault, seller settled full + deducted from liable) | liability_split (needs liability_parties[]={user_id,amount}).
871
+ Ruling options — ESCROW: refund_buyer | release_seller | partial_refund (needs refund_amount; optional liable_party → 3rd-party fault, seller settled full + deducted from liable) | liability_split (needs liability_parties[]={user_id,amount}). DIRECT PAY: refund_buyer=买家胜诉 | release_seller=卖家胜诉 | partial_refund=部分责任 — reputation verdict only, do NOT pass refund_amount/liability_parties (protocol holds no funds). Check \`view\`→payment_rail/ruling_options first.
816
872
 
817
873
  Protocol auto-judges (no human): respondent silent 48h → favor initiator; arbitrator silent 120h → refund_buyer. Executed rulings are instant + irreversible.
818
874
 
@@ -2785,7 +2841,7 @@ async function handleUpdateOrder(args) {
2785
2841
  const isParticipant = order.buyer_id === user.id ||
2786
2842
  order.seller_id === user.id ||
2787
2843
  order.logistics_id === user.id;
2788
- if (!isParticipant && user.role !== 'arbitrator') {
2844
+ if (!isParticipant && !isActiveWhitelistArbitrator(db, user.id)) { // 白名单(非 legacy role):role-only/已吊销不可越权;下游 transition 仍按 actor.role 守边
2789
2845
  return { error: '你不是这笔订单的参与方,无法操作' };
2790
2846
  }
2791
2847
  // action → 状态映射
@@ -3088,10 +3144,13 @@ async function handleDispute(args) {
3088
3144
  WHERE e.order_id = ? AND u.role = ?
3089
3145
  ORDER BY e.created_at ASC
3090
3146
  `).all(orderId, uploaderRole);
3147
+ const dpNonCustodial = dispute.payment_rail === 'direct_p2p'; // 直付=非托管:仅信誉裁决,无退款/放款/仲裁费/金额
3091
3148
  return {
3092
3149
  dispute_id: dispute.id,
3093
3150
  order_id: dispute.order_id,
3094
3151
  status: dispute.status,
3152
+ payment_rail: dispute.payment_rail || 'escrow',
3153
+ non_custodial: dpNonCustodial,
3095
3154
  initiator: `${dispute.initiator_name}(${dispute.initiator_role})`,
3096
3155
  defendant: `${dispute.defendant_name}(${dispute.defendant_role})`,
3097
3156
  reason: dispute.reason,
@@ -3101,21 +3160,27 @@ async function handleDispute(args) {
3101
3160
  defendant_notes: dispute.defendant_notes ?? '(被诉方尚未提交回应)',
3102
3161
  defendant_evidence: JSON.parse(dispute.defendant_evidence_ids || '[]'),
3103
3162
  ruling: dispute.ruling_type
3104
- ? { type: dispute.ruling_type, refund_amount: dispute.refund_amount, reason: dispute.verdict_reason }
3163
+ ? (dpNonCustodial
3164
+ ? { type: dispute.ruling_type, reason: dispute.verdict_reason, note: '非托管信誉裁决:无退款/放款金额' }
3165
+ : { type: dispute.ruling_type, refund_amount: dispute.refund_amount, reason: dispute.verdict_reason })
3105
3166
  : null,
3106
- // QA 轮 15 P1:未裁定时暴露可选 ruling 列表(与 PWA disputes-write.ts validRulings 对齐)
3107
- ruling_options: dispute.ruling_type ? undefined : [
3167
+ // 未裁定时暴露可选 ruling 列表。direct_p2p:仅信誉裁决(胜负/责任,无金额);escrow:含金额/责任分配。
3168
+ ruling_options: dispute.ruling_type ? undefined : (dpNonCustodial ? [
3169
+ { type: 'refund_buyer', desc: '判买家胜诉(信誉裁决;非托管:无退款/放款/仲裁费)' },
3170
+ { type: 'release_seller', desc: '判卖家胜诉(信誉裁决;无退款/放款)' },
3171
+ { type: 'partial_refund', desc: '判部分责任(信誉裁决;无金额)' },
3172
+ ] : [
3108
3173
  { type: 'refund_buyer', desc: '全额退款给买家(卖家败诉)' },
3109
3174
  { type: 'release_seller', desc: '放款给卖家(买家败诉)' },
3110
3175
  { type: 'partial_refund', desc: '部分退款(需传 refund_amount)' },
3111
3176
  { type: 'liability_split', desc: '按责任分配(需传 liability_parties 数组)' },
3112
- ],
3177
+ ]),
3113
3178
  resolved_at: dispute.resolved_at,
3114
3179
  };
3115
3180
  }
3116
3181
  // ── 仲裁员查看所有待处理争议 ───────────────────────────────
3117
3182
  if (action === 'list_open') {
3118
- if (user.role !== 'arbitrator') {
3183
+ if (!isActiveWhitelistArbitrator(db, user.id)) { // 白名单(非 legacy role):whitelist-only 真人可用,role-only/已吊销 403
3119
3184
  return { error: '只有仲裁员可以查看所有待处理争议' };
3120
3185
  }
3121
3186
  const disputes = await getOpenDisputes(db);
@@ -3128,7 +3193,7 @@ async function handleDispute(args) {
3128
3193
  initiator: `${d.initiator_name}(${d.initiator_role})`,
3129
3194
  defendant: `${d.defendant_name}(${d.defendant_role})`,
3130
3195
  reason: d.reason,
3131
- amount: `${d.total_amount} WAZ`,
3196
+ amount: d.payment_rail === 'direct_p2p' ? `${d.total_amount} USDC(非托管·信誉裁决)` : `${d.total_amount} WAZ`,
3132
3197
  respond_deadline: d.respond_deadline,
3133
3198
  arbitrate_deadline: d.arbitrate_deadline,
3134
3199
  created_at: d.created_at,