@seasonkoh/webaz 0.1.28 → 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 (198) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +7 -6
  3. package/dist/bond-refund-blockers.js +50 -0
  4. package/dist/bond-slash.js +100 -0
  5. package/dist/bond-terms.js +31 -0
  6. package/dist/currency.js +16 -0
  7. package/dist/deposit-rails.js +52 -0
  8. package/dist/direct-pay-aml-monitor.js +67 -0
  9. package/dist/direct-pay-aml-review.js +40 -0
  10. package/dist/direct-pay-base-bond-entry.js +5 -0
  11. package/dist/direct-pay-bond-rail-clearance.js +99 -0
  12. package/dist/direct-pay-cancel-refund.js +160 -0
  13. package/dist/direct-pay-compliance-ingress.js +145 -0
  14. package/dist/direct-pay-controls.js +136 -0
  15. package/dist/direct-pay-create.js +231 -0
  16. package/dist/direct-pay-deferral-quota.js +43 -0
  17. package/dist/direct-pay-disclosures.js +50 -0
  18. package/dist/direct-pay-eligibility.js +46 -0
  19. package/dist/direct-pay-fee-ar.js +241 -0
  20. package/dist/direct-pay-fee-prepay-request.js +80 -0
  21. package/dist/direct-pay-launch-readiness.js +108 -0
  22. package/dist/direct-pay-launch-summary.js +68 -0
  23. package/dist/direct-pay-ledger.js +94 -0
  24. package/dist/direct-pay-returns.js +104 -0
  25. package/dist/direct-pay-stock.js +9 -0
  26. package/dist/direct-receive-account-qr.js +77 -0
  27. package/dist/direct-receive-accounts.js +82 -0
  28. package/dist/direct-receive-deferral.js +171 -0
  29. package/dist/direct-receive-deposits.js +365 -0
  30. package/dist/direct-receive-payment-instruction.js +26 -0
  31. package/dist/free-shipping.js +37 -0
  32. package/dist/fx-rates.js +78 -0
  33. package/dist/layer0-foundation/L0-1-database/schema.js +777 -1
  34. package/dist/layer0-foundation/L0-2-state-machine/engine.js +1 -0
  35. package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +15 -5
  36. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +114 -0
  37. package/dist/layer0-foundation/L0-5-manifest/manifest.js +1 -1
  38. package/dist/layer1-agent/L1-1-mcp-server/cli.js +64 -0
  39. package/dist/layer1-agent/L1-1-mcp-server/network-mode.js +11 -0
  40. package/dist/layer1-agent/L1-1-mcp-server/server.js +99 -32
  41. package/dist/layer2-business/L2-6-notifications/notification-engine.js +110 -41
  42. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +133 -31
  43. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +16 -4
  44. package/dist/layer3-trust/L3-1-dispute-engine/mutual-cancel.js +156 -0
  45. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +4 -4
  46. package/dist/ledger.js +12 -3
  47. package/dist/mcp.js +23 -4
  48. package/dist/merchant-bond-domain.js +64 -0
  49. package/dist/merchant-bond-exposure.js +78 -0
  50. package/dist/merchant-bond-watcher.js +26 -0
  51. package/dist/payment-rails.js +29 -0
  52. package/dist/platform-receive-accounts.js +94 -0
  53. package/dist/product-verification.js +93 -0
  54. package/dist/pwa/acp-feed.js +3 -2
  55. package/dist/pwa/arbitration-read-admin.js +37 -0
  56. package/dist/pwa/arbitrator-lifecycle.js +100 -0
  57. package/dist/pwa/contract-fingerprint.js +18 -0
  58. package/dist/pwa/direct-pay-guards.js +20 -0
  59. package/dist/pwa/direct-pay-order-redaction.js +42 -0
  60. package/dist/pwa/endpoint-actions.js +3 -0
  61. package/dist/pwa/human-presence.js +2 -6
  62. package/dist/pwa/public/app-account.js +9 -9
  63. package/dist/pwa/public/app-admin-disputes.js +55 -0
  64. package/dist/pwa/public/app-agent-appeal.js +90 -0
  65. package/dist/pwa/public/app-agent-approvals.js +93 -0
  66. package/dist/pwa/public/app-agent-pair.js +127 -0
  67. package/dist/pwa/public/app-ai.js +10 -10
  68. package/dist/pwa/public/app-arbitrator-admin.js +87 -0
  69. package/dist/pwa/public/app-arbitrator-entry.js +9 -0
  70. package/dist/pwa/public/app-bond-deferral-ui.js +9 -0
  71. package/dist/pwa/public/app-bond-refund-ui.js +66 -0
  72. package/dist/pwa/public/app-bond-slash-ui.js +74 -0
  73. package/dist/pwa/public/app-bond-terms-ui.js +23 -0
  74. package/dist/pwa/public/app-bond-ui.js +108 -0
  75. package/dist/pwa/public/app-chat-poll.js +29 -0
  76. package/dist/pwa/public/app-contribution-hub.js +23 -0
  77. package/dist/pwa/public/app-create-kinds.js +17 -0
  78. package/dist/pwa/public/app-direct-pay-accounts.js +141 -0
  79. package/dist/pwa/public/app-direct-pay-buyer.js +72 -0
  80. package/dist/pwa/public/app-direct-pay-cancel-refund.js +72 -0
  81. package/dist/pwa/public/app-direct-pay-compliance.js +67 -0
  82. package/dist/pwa/public/app-direct-pay-copy.js +12 -0
  83. package/dist/pwa/public/app-direct-pay-deferral-admin.js +72 -0
  84. package/dist/pwa/public/app-direct-pay-deferral.js +61 -0
  85. package/dist/pwa/public/app-direct-pay-fee-center.js +33 -0
  86. package/dist/pwa/public/app-direct-pay-fee-history.js +39 -0
  87. package/dist/pwa/public/app-direct-pay-fee-ops.js +112 -0
  88. package/dist/pwa/public/app-direct-pay-fee-request.js +81 -0
  89. package/dist/pwa/public/app-direct-pay-fee-requests-admin.js +70 -0
  90. package/dist/pwa/public/app-direct-pay-memo.js +14 -0
  91. package/dist/pwa/public/app-direct-pay-negotiation.js +35 -0
  92. package/dist/pwa/public/app-direct-pay-pay.js +15 -0
  93. package/dist/pwa/public/app-direct-pay-paymodal.js +32 -0
  94. package/dist/pwa/public/app-direct-pay-product-verify.js +103 -0
  95. package/dist/pwa/public/app-direct-pay-readiness.js +38 -0
  96. package/dist/pwa/public/app-direct-pay-reconcile.js +19 -0
  97. package/dist/pwa/public/app-direct-pay-returns.js +56 -0
  98. package/dist/pwa/public/app-direct-pay-reveal.js +82 -0
  99. package/dist/pwa/public/app-direct-pay-sales-report.js +70 -0
  100. package/dist/pwa/public/app-direct-pay-store-verify.js +100 -0
  101. package/dist/pwa/public/app-direct-pay.js +226 -0
  102. package/dist/pwa/public/app-discover.js +20 -20
  103. package/dist/pwa/public/app-dispute-close-ui.js +38 -0
  104. package/dist/pwa/public/app-external-links.js +32 -0
  105. package/dist/pwa/public/app-free-shipping-ui.js +29 -0
  106. package/dist/pwa/public/app-gmv-rail-split.js +12 -0
  107. package/dist/pwa/public/app-listing-commerce-ui.js +72 -0
  108. package/dist/pwa/public/app-listings.js +4 -4
  109. package/dist/pwa/public/app-mutual-cancel.js +54 -0
  110. package/dist/pwa/public/app-notif-templates-orders.js +43 -0
  111. package/dist/pwa/public/app-notif-templates.js +22 -0
  112. package/dist/pwa/public/app-order-accept-ui.js +158 -0
  113. package/dist/pwa/public/app-order-errors.js +50 -0
  114. package/dist/pwa/public/app-order-labels.js +19 -0
  115. package/dist/pwa/public/app-order-rail-filter.js +26 -0
  116. package/dist/pwa/public/app-platform-receive-accounts.js +140 -0
  117. package/dist/pwa/public/app-poll-governor.js +22 -0
  118. package/dist/pwa/public/app-prelaunch-waz.js +39 -0
  119. package/dist/pwa/public/app-price.js +55 -0
  120. package/dist/pwa/public/app-product-media.js +15 -0
  121. package/dist/pwa/public/app-profile.js +10 -10
  122. package/dist/pwa/public/app-purchase-terms-ui.js +68 -0
  123. package/dist/pwa/public/app-sale-regions-ui.js +38 -0
  124. package/dist/pwa/public/app-seller.js +5 -5
  125. package/dist/pwa/public/app-shop.js +19 -19
  126. package/dist/pwa/public/app-trade-tax-ui.js +33 -0
  127. package/dist/pwa/public/app.js +264 -295
  128. package/dist/pwa/public/i18n.js +1068 -197
  129. package/dist/pwa/public/index.html +58 -0
  130. package/dist/pwa/public/openapi.json +1845 -653
  131. package/dist/pwa/public/style.css +3 -0
  132. package/dist/pwa/routes/admin-analytics.js +4 -2
  133. package/dist/pwa/routes/admin-direct-receive-deposits.js +533 -0
  134. package/dist/pwa/routes/admin-protocol-params.js +16 -0
  135. package/dist/pwa/routes/admin-reports.js +31 -5
  136. package/dist/pwa/routes/agent-governance.js +1 -1
  137. package/dist/pwa/routes/agent-grants.js +253 -32
  138. package/dist/pwa/routes/analytics.js +2 -0
  139. package/dist/pwa/routes/arbitrator.js +67 -14
  140. package/dist/pwa/routes/bond-seller.js +162 -0
  141. package/dist/pwa/routes/buyer-feeds.js +2 -1
  142. package/dist/pwa/routes/chat.js +6 -1
  143. package/dist/pwa/routes/dashboards.js +2 -1
  144. package/dist/pwa/routes/direct-pay-availability.js +196 -0
  145. package/dist/pwa/routes/direct-pay-cancel-refund.js +111 -0
  146. package/dist/pwa/routes/direct-pay-disclosure-acks.js +79 -0
  147. package/dist/pwa/routes/direct-pay-pending-accept.js +222 -0
  148. package/dist/pwa/routes/direct-pay-returns.js +74 -0
  149. package/dist/pwa/routes/direct-pay-timeouts.js +248 -0
  150. package/dist/pwa/routes/direct-receive-accounts.js +155 -0
  151. package/dist/pwa/routes/direct-receive-payment-instructions.js +45 -0
  152. package/dist/pwa/routes/disputes-read.js +20 -6
  153. package/dist/pwa/routes/disputes-write.js +91 -33
  154. package/dist/pwa/routes/external-anchors.js +4 -2
  155. package/dist/pwa/routes/fee-prepay-requests.js +66 -0
  156. package/dist/pwa/routes/fx.js +12 -0
  157. package/dist/pwa/routes/governance-onboarding.js +46 -8
  158. package/dist/pwa/routes/leaderboard.js +47 -9
  159. package/dist/pwa/routes/listings.js +2 -2
  160. package/dist/pwa/routes/logistics.js +6 -2
  161. package/dist/pwa/routes/manifests.js +38 -0
  162. package/dist/pwa/routes/me-data.js +5 -2
  163. package/dist/pwa/routes/mutual-cancel.js +62 -0
  164. package/dist/pwa/routes/orders-action.js +297 -16
  165. package/dist/pwa/routes/orders-create.js +21 -6
  166. package/dist/pwa/routes/orders-read.js +106 -8
  167. package/dist/pwa/routes/p2p-products.js +2 -2
  168. package/dist/pwa/routes/platform-receive-accounts.js +111 -0
  169. package/dist/pwa/routes/products-create.js +15 -4
  170. package/dist/pwa/routes/products-links.js +34 -0
  171. package/dist/pwa/routes/products-list.js +2 -1
  172. package/dist/pwa/routes/products-update.js +37 -3
  173. package/dist/pwa/routes/profile-identity.js +4 -2
  174. package/dist/pwa/routes/promoter.js +3 -0
  175. package/dist/pwa/routes/referral.js +4 -0
  176. package/dist/pwa/routes/returns.js +60 -4
  177. package/dist/pwa/routes/rewards-apply.js +10 -5
  178. package/dist/pwa/routes/rewards-clearing-mature.js +96 -0
  179. package/dist/pwa/routes/rewards-escrow-expire.js +6 -2
  180. package/dist/pwa/routes/seller-directpay-report.js +110 -0
  181. package/dist/pwa/routes/seller-quota.js +5 -1
  182. package/dist/pwa/routes/shipping-templates.js +219 -0
  183. package/dist/pwa/routes/shops.js +2 -1
  184. package/dist/pwa/routes/snf.js +4 -1
  185. package/dist/pwa/routes/url-claim.js +2 -2
  186. package/dist/pwa/routes/users-public.js +8 -0
  187. package/dist/pwa/routes/wallet-read.js +3 -0
  188. package/dist/pwa/routes/webauthn.js +3 -3
  189. package/dist/pwa/server.js +112 -132
  190. package/dist/runtime/agent-grant-scopes.js +69 -1
  191. package/dist/runtime/webaz-schema-helpers.js +161 -3
  192. package/dist/sale-regions.js +116 -0
  193. package/dist/shipping-templates.js +119 -0
  194. package/dist/store-verification.js +77 -0
  195. package/dist/trade-tax.js +99 -0
  196. package/dist/trade-terms.js +76 -0
  197. package/dist/version.js +1 -1
  198. package/package.json +128 -2
@@ -4,6 +4,8 @@ import { getCasesForRole, getCasesForMaintainer, validateCaseReviews } from '../
4
4
  // RFC-016 Phase 1 — 申请/题目/案例/申诉的校验读 + 列表读 + 单语句写 → async seam;
5
5
  // activate/resign/resolve-appeal 的 3 个角色态 CAS db.transaction 保持同步(Phase 3 迁 pg)。
6
6
  import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js';
7
+ import { grantArbitratorTx, suspendArbitrator } from '../arbitrator-lifecycle.js'; // PR-B/PR-C.2:激活桥接 active whitelist 同事务;#249-P2:仲裁员卸任真源=whitelist(suspend 可复用,非终态 revoke)
8
+ import { isActiveWhitelistArbitrator } from '../../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
7
9
  // PR #22 review fix P1-2:披露文本版本(server 与 client 同步,变更披露需 bump)
8
10
  // client 在 src/pwa/public/app.js 必须用相同 version
9
11
  export const GOVERNANCE_APPLY_DISCLOSURE_VERSION = 'v1.0-2026-06-02';
@@ -390,10 +392,14 @@ export function registerGovernanceOnboardingRoutes(app, deps) {
390
392
  return void errorRes(res, 401, 'PASSKEY_INVALID', `maintainer Passkey 验证失败: ${result.reason || '未知'}`);
391
393
  }
392
394
  }
393
- // 5. 事务:conditional UPDATE 防竞态 + INSERT activate row + 加 users.roles
395
+ // 5. 事务:conditional UPDATE 防竞态 + PR-C.2 仲裁授权 + INSERT activate row + 加 users.roles(仅治理记录,非 eligibility 源)
394
396
  // PR #25 self-review fix P1-1:status check 进 transaction(防 2 maintainer 同时激活 → 2 activate row)
395
397
  // PR #25 self-review fix P1-2:不存 note 到 appeal_resolution(语义错);maintainer note 由 logAdminAction(detail) 记
398
+ // PR-C.2:role='arbitrator' 的 grantArbitratorTx 与状态翻转【同事务】—— 保证"governance active ⟺ whitelist active",
399
+ // grant 失败(revoked/非人类/无 Passkey)→ 抛 sentinel,整个激活回滚。verifier 角色走其自身白名单,不在此桥。
396
400
  const RACE_LOST = 'RACE_LOST_PENDING_TO_ACTIVE';
401
+ const GRANT_ABORT = 'GRANT_ABORT';
402
+ let grantErr = null;
397
403
  try {
398
404
  db.transaction(() => {
399
405
  // 5.1 conditional UPDATE 原 apply row(只在 status='pending_onboarding' 时改 → 防竞态)
@@ -402,6 +408,14 @@ export function registerGovernanceOnboardingRoutes(app, deps) {
402
408
  // 另一 maintainer 已激活 → 抛出 sentinel,整个事务回滚
403
409
  throw new Error(RACE_LOST);
404
410
  }
411
+ // 5.1b 仲裁授权与状态翻转同事务(唯一 runtime 授权源 = active arbitrator_whitelist)
412
+ if (role === 'arbitrator') {
413
+ const g = grantArbitratorTx(db, { userId: app_.user_id, grantedBy: adminId, note: 'governance onboarding 激活' });
414
+ if (!g.ok) {
415
+ grantErr = g.error_code || 'GRANT_FAILED';
416
+ throw new Error(GRANT_ABORT);
417
+ }
418
+ }
405
419
  // 5.2 INSERT 新 row(append-only audit trail,action='activate')
406
420
  const activateId = generateId('gapp');
407
421
  db.prepare(`
@@ -409,7 +423,10 @@ export function registerGovernanceOnboardingRoutes(app, deps) {
409
423
  (id, user_id, role, action, status, passkey_sig, iron_rule_method)
410
424
  VALUES (?, ?, ?, 'activate', 'active', ?, 'passkey')
411
425
  `).run(activateId, app_.user_id, role, webauthn_token || null);
412
- // 5.3 加 role 到 users.roles JSON 数组(去重)
426
+ // 5.3 加 role 到 users.roles JSON 数组(去重)
427
+ // 仲裁员例外:仲裁资格 = active arbitrator_whitelist(5.1b 已同事务 grant,#209/#220),【不是角色】——
428
+ // 写进 roles 会在角色切换 UI 造出可切换的"仲裁员"假身份(与买卖身份互斥感误导用户,梦想者1号被卡案根源之一)。
429
+ // 普通人保留 buyer/seller 身份 + 白名单资格,仲裁台入口随 can_arbitrate 出现,买卖能力不受影响。
413
430
  const user = db.prepare("SELECT roles FROM users WHERE id = ?").get(app_.user_id);
414
431
  let roles = [];
415
432
  try {
@@ -418,7 +435,7 @@ export function registerGovernanceOnboardingRoutes(app, deps) {
418
435
  catch {
419
436
  roles = [];
420
437
  }
421
- if (!roles.includes(role)) {
438
+ if (role !== 'arbitrator' && !roles.includes(role)) {
422
439
  roles.push(role);
423
440
  db.prepare("UPDATE users SET roles = ? WHERE id = ?").run(JSON.stringify(roles), app_.user_id);
424
441
  }
@@ -428,6 +445,10 @@ export function registerGovernanceOnboardingRoutes(app, deps) {
428
445
  if (e.message === RACE_LOST) {
429
446
  return void errorRes(res, 409, 'ALREADY_ACTIVATED', '该 application 已被其他 maintainer 激活(竞态)');
430
447
  }
448
+ if (e.message === GRANT_ABORT) { // PR-C.2:仲裁授权失败 → 整个激活已回滚
449
+ logAdminAction(adminId, 'governance_activate', 'user', app_.user_id, { ok: false, role, error_code: grantErr });
450
+ return void errorRes(res, 409, grantErr || 'GRANT_FAILED', '激活失败:无法授予仲裁员资格(见 error_code),激活已回滚');
451
+ }
431
452
  throw e; // 真实 SQL error → re-throw 给 express error handler
432
453
  }
433
454
  // 6. admin 审计 log(maintainer note 存 detail JSON,非 governance_applications 字段)
@@ -478,7 +499,10 @@ export function registerGovernanceOnboardingRoutes(app, deps) {
478
499
  catch {
479
500
  currentRoles = [];
480
501
  }
481
- if (!currentRoles.includes(role)) {
502
+ // #249-P2:activate 已不再把 arbitrator 写进 users.roles(资格=白名单) 仲裁员的"当前在任"真源
503
+ // = active whitelist(或 legacy roles 残留,两者任一即可卸);verifier 真源仍 = users.roles。
504
+ const wlActiveArb = role === 'arbitrator' && isActiveWhitelistArbitrator(db, userId);
505
+ if (!currentRoles.includes(role) && !wlActiveArb) {
482
506
  return void errorRes(res, 404, 'NOT_ACTIVE', `你当前不是 ${role},无需卸任`);
483
507
  }
484
508
  // 用于日志显示 + 后续 INSERT 关联(取最新的 active 行;若没有也容许,以 user.roles 为准)
@@ -534,7 +558,8 @@ export function registerGovernanceOnboardingRoutes(app, deps) {
534
558
  try {
535
559
  resignId = generateId('gapp');
536
560
  db.transaction(() => {
537
- // 1. 重读 users.roles(进事务后) 验证 role 仍存在再操作
561
+ // 1. 重读真源(进事务后)。仲裁员=active whitelist(#249 activate 不写 roles;suspend=自愿卸任可经
562
+ // admin reinstate 复用,刻意不用终态 revoke);legacy roles 里的 'arbitrator' 存量一并摘除。verifier=users.roles。
538
563
  const u = db.prepare("SELECT roles FROM users WHERE id = ?").get(userId);
539
564
  let roles = [];
540
565
  try {
@@ -543,10 +568,23 @@ export function registerGovernanceOnboardingRoutes(app, deps) {
543
568
  catch {
544
569
  roles = [];
545
570
  }
546
- if (!roles.includes(role))
571
+ const legacyInRoles = roles.includes(role);
572
+ if (role === 'arbitrator') {
573
+ const wlNow = isActiveWhitelistArbitrator(db, userId);
574
+ if (!wlNow && !legacyInRoles)
575
+ throw new Error(RACE_LOST);
576
+ if (wlNow) {
577
+ const m = suspendArbitrator(db, { userId, note: '主动卸任(governance resign)' });
578
+ if (!m.ok)
579
+ throw new Error(RACE_LOST);
580
+ }
581
+ }
582
+ else if (!legacyInRoles)
547
583
  throw new Error(RACE_LOST);
548
- roles = roles.filter(r => r !== role);
549
- db.prepare("UPDATE users SET roles = ? WHERE id = ?").run(JSON.stringify(roles), userId);
584
+ if (legacyInRoles) {
585
+ roles = roles.filter(r => r !== role);
586
+ db.prepare("UPDATE users SET roles = ? WHERE id = ?").run(JSON.stringify(roles), userId);
587
+ }
550
588
  // 2. 把所有该 user+role 的 active 行 → inactive(可能有 apply + activate 两行)
551
589
  db.prepare("UPDATE governance_applications SET status = 'inactive' WHERE user_id = ? AND role = ? AND status = 'active'").run(userId, role);
552
590
  // 3. INSERT resign 行(append-only audit)
@@ -1,4 +1,42 @@
1
1
  import { dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
2
+ // ── Anonymous-projection allowlist (security: this is a NO-AUTH public endpoint) ──────────────
3
+ // Each board's anon payload carries ONLY what the public value proposition needs: a public
4
+ // identity for linking (handle/name) + the reputation/content signal. It must NEVER carry
5
+ // internal canonical user ids (usr_xxx — those are the keyed inputs the #1043 cross-user-read
6
+ // cap rate-limits; emitting them here would be a free enumeration seed-list), account-structure
7
+ // metadata (keys_count), or behavior-fingerprint integers (calls_30d). ALLOWLIST, not denylist,
8
+ // so a future SELECT column can't silently ride to the public surface. Canonical user id (usr_xxx)
9
+ // is the keyed input the #1043 cross-user-read cap rate-limits, so it is DROPPED from every user
10
+ // board (agents/buyers/sellers/arbitrators/verifiers/creators). All user cards navigate via the
11
+ // public handle — #shop/:identifier and #u/:user_id both resolve a handle (users-public.ts
12
+ // resolveUserId: usr_xxx / permanent_code / @handle). Product boards keep `id` — a public PRODUCT
13
+ // id, not a user id.
14
+ export const BOARD_ALLOWLIST = {
15
+ products: ['id', 'title', 'price', 'total_likes', 'completion_count', 'seller_handle', 'seller_name', 'recommend_count', 'rank_score'],
16
+ value_products: ['id', 'title', 'price', 'category', 'value_badge_rank', 'value_badge_pct', 'value_badge_at', 'completion_count', 'total_likes', 'seller_handle', 'seller_name'],
17
+ creators: ['handle', 'name', 'region', 'products_shared', 'shareable_count', 'total_likes', 'total_clicks'], // no id — card navs #u/${handle} (#u/ resolves handle)
18
+ buyers: ['handle', 'name', 'region', 'orders_count'],
19
+ sellers: ['handle', 'name', 'region', 'orders_count', 'avg_rating', 'rating_count'], // no id — card navs via handle (#shop accepts it)
20
+ agents: ['handle', 'name', 'trust_score', 'level', 'activity'], // NO id / keys_count / calls_30d
21
+ arbitrators: ['handle', 'name', 'cases_count', 'total_yes', 'total_no', 'fairness_score'],
22
+ verifiers: ['handle', 'name', 'tasks_done', 'tasks_correct', 'tasks_wrong', 'accuracy', 'tier'],
23
+ };
24
+ // calls_30d (behavior fingerprint) → coarse activity bucket (keeps "busy vs dormant" product signal
25
+ // without leaking a raw per-account integer or the all-zero "network looks dead" aggregate).
26
+ function activityBucket(calls) {
27
+ return calls >= 20 ? 'active' : calls >= 1 ? 'quiet' : 'dormant';
28
+ }
29
+ function projectBoard(kind, rows) {
30
+ const allow = BOARD_ALLOWLIST[kind] || [];
31
+ return rows.map(r => {
32
+ const src = kind === 'agents' ? { ...r, activity: activityBucket(Number(r.calls_30d) || 0) } : r;
33
+ const out = {};
34
+ for (const k of allow)
35
+ if (k in src)
36
+ out[k] = src[k];
37
+ return out;
38
+ });
39
+ }
2
40
  export function registerLeaderboardRoutes(app, deps) {
3
41
  // db 已走 RFC-016 异步 seam(dbAll),不再直接用 deps.db
4
42
  const { internalAuditorId, rateLimitOk } = deps;
@@ -27,7 +65,7 @@ export function registerLeaderboardRoutes(app, deps) {
27
65
  ORDER BY rank_score DESC, p.id DESC
28
66
  LIMIT ?
29
67
  `, [limit]);
30
- return void res.json({ kind, items: rows });
68
+ return void res.json({ kind, items: projectBoard(kind, rows) });
31
69
  }
32
70
  if (kind === 'creators') {
33
71
  // 创作者维度:聚合自己 shareables 的总点赞 + 关联商品总数
@@ -43,7 +81,7 @@ export function registerLeaderboardRoutes(app, deps) {
43
81
  ORDER BY total_likes DESC, shareable_count DESC, u.id DESC
44
82
  LIMIT ?
45
83
  `, [limit]);
46
- return void res.json({ kind, items: rows });
84
+ return void res.json({ kind, items: projectBoard(kind, rows) });
47
85
  }
48
86
  // B-2: 用户排行 — top buyers / sellers / verifiers
49
87
  // 2026-05-23 隐私第一原理:移除 gmv 字段(运营状态私密,防过早 fork)
@@ -55,7 +93,7 @@ export function registerLeaderboardRoutes(app, deps) {
55
93
  WHERE o.status = 'completed' AND u.id NOT IN ('sys_protocol', ?)
56
94
  GROUP BY u.id ORDER BY orders_count DESC, u.id DESC LIMIT ?
57
95
  `, [internalAuditorId, limit]);
58
- return void res.json({ kind, items: rows });
96
+ return void res.json({ kind, items: projectBoard(kind, rows) });
59
97
  }
60
98
  if (kind === 'sellers') {
61
99
  // 排序改为 评分主导(avg_rating × log(1+rating_count)),不再按 GMV
@@ -70,7 +108,7 @@ export function registerLeaderboardRoutes(app, deps) {
70
108
  ORDER BY (avg_rating * (1.0 + log(1.0 + rating_count))) DESC, rating_count DESC, orders_count DESC
71
109
  LIMIT ?
72
110
  `, [limit]);
73
- return void res.json({ kind, items: rows });
111
+ return void res.json({ kind, items: projectBoard(kind, rows) });
74
112
  }
75
113
  if (kind === 'value_products') {
76
114
  // 2026-05-23 S5:极致性价比榜 — 按 value_badge=1 + 同类目 rank 排
@@ -85,7 +123,7 @@ export function registerLeaderboardRoutes(app, deps) {
85
123
  WHERE p.value_badge = 1 AND p.status = 'active' AND p.stock > 0
86
124
  ORDER BY p.value_badge_rank ASC, p.value_badge_pct DESC LIMIT ?
87
125
  `, [limit]);
88
- return void res.json({ kind, items: rows });
126
+ return void res.json({ kind, items: projectBoard(kind, rows) });
89
127
  }
90
128
  if (kind === 'agents') {
91
129
  // 2026-05-22 AG1:Agent 评测竞赛榜单
@@ -101,12 +139,12 @@ export function registerLeaderboardRoutes(app, deps) {
101
139
  AND acl.created_at > datetime('now', '-30 days')) as calls_30d
102
140
  FROM agent_reputation ar
103
141
  JOIN users u ON u.id = ar.user_id
104
- WHERE u.id != 'sys_protocol'
142
+ WHERE u.id != 'sys_protocol' AND u.role != 'admin'
105
143
  GROUP BY u.id
106
144
  HAVING calls_30d > 0 OR trust_score > 0
107
145
  ORDER BY trust_score DESC, calls_30d DESC LIMIT ?
108
146
  `, [limit]);
109
- return void res.json({ kind, items: rows });
147
+ return void res.json({ kind, items: projectBoard(kind, rows) });
110
148
  }
111
149
  if (kind === 'arbitrators') {
112
150
  // 2026-05-22 A3:仲裁员声誉排行
@@ -132,7 +170,7 @@ export function registerLeaderboardRoutes(app, deps) {
132
170
  GROUP BY u.id
133
171
  ORDER BY cases_count DESC, u.id DESC LIMIT ?
134
172
  `, [limit]);
135
- return void res.json({ kind, items: rows });
173
+ return void res.json({ kind, items: projectBoard(kind, rows) });
136
174
  }
137
175
  if (kind === 'verifiers') {
138
176
  // 2026-05-22 V1:移除 tasks_done >= 5 门槛 — 小协议早期阶段会卡死榜单
@@ -152,7 +190,7 @@ export function registerLeaderboardRoutes(app, deps) {
152
190
  WHERE vs.tasks_done >= 1
153
191
  ORDER BY vs.tasks_done DESC, u.id DESC LIMIT ?
154
192
  `, [limit]);
155
- return void res.json({ kind, items: rows });
193
+ return void res.json({ kind, items: projectBoard(kind, rows) });
156
194
  }
157
195
  return void res.json({ error: 'kind 必须是 products / creators / buyers / sellers / verifiers / arbitrators / agents / value_products' });
158
196
  });
@@ -265,8 +265,8 @@ export function registerListingsRoutes(app, deps) {
265
265
  id, seller_id, title, description, price, stock, status, images,
266
266
  ship_regions, handling_hours, commission_rate, category_id, stake_amount,
267
267
  listing_id, fulfillment_type, eta_hours, freshness_ts,
268
- is_clearance, clearance_until, cold_start_remaining, listing_stake_locked
269
- ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,datetime('now'),?,?,?,?)
268
+ is_clearance, clearance_until, cold_start_remaining, listing_stake_locked, currency
269
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,datetime('now'),?,?,?,?,'WAZ')
270
270
  `).run(id, user.id, listing.title, (body.description ? String(body.description) : listing.description || ''), priceNum, stockNum, 'active', imagesJson, shipRegions, 24, 0.10, 'cat_default', 0, req.params.id, fulfillmentType, body.eta_hours != null ? Number(body.eta_hours) : null, body.is_clearance ? 1 : 0, body.clearance_until ? String(body.clearance_until) : null, catCfg.cold_start, stakeRequired);
271
271
  db.prepare(`UPDATE wallets SET balance = balance - ?, staked = staked + ? WHERE user_id = ?`).run(stakeRequired, stakeRequired, user.id);
272
272
  db.prepare(`UPDATE listings SET total_offers = total_offers + 1, updated_at = datetime('now') WHERE id = ?`).run(req.params.id);
@@ -1,7 +1,8 @@
1
1
  import { dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
2
+ import { projectDirectPayTargetForViewer } from '../direct-pay-order-redaction.js'; // 披露门:按查看者投影(物流=第三方 → 剥离收款目标)
2
3
  export function registerLogisticsRoutes(app, deps) {
3
- // db 已走 RFC-016 异步 seam(dbAll),不再直接用 deps.db
4
- const { auth } = deps;
4
+ // 查询走 RFC-016 异步 seam(dbAll);db 仅传给共享投影器(其买家分支查 ack,本路由查看者恒第三方)
5
+ const { auth, db } = deps;
5
6
  app.get('/api/logistics/companies', async (_req, res) => {
6
7
  const companies = await dbAll(`SELECT id, name FROM users WHERE role = 'logistics' ORDER BY name ASC`);
7
8
  res.json(companies);
@@ -32,6 +33,9 @@ export function registerLogisticsRoutes(app, deps) {
32
33
  WHERE o.logistics_id = ? AND o.status IN ('shipped','picked_up','in_transit')
33
34
  ORDER BY o.created_at ASC LIMIT 20
34
35
  `, [user.id]);
36
+ // 披露门:物流是非买家第三方,无条件删除 direct_p2p 收款目标(instruction 快照 + 账号快照);物流只需商品/状态/地址。
37
+ for (const o of [...available, ...mine])
38
+ projectDirectPayTargetForViewer(db, o, user.id);
35
39
  res.json({ available, mine });
36
40
  });
37
41
  }
@@ -2,6 +2,9 @@ import crypto from 'crypto';
2
2
  import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
3
3
  const MANIFEST_DAILY_LIMIT = 20;
4
4
  const THUMB_MAX_BYTES = 12000; // ~12KB base64 ≈ 9KB 原始图
5
+ // Whitelist for stored/served thumbnails: raster data-URI only. Blocks svg/text/html data URIs so a malicious
6
+ // seller can't smuggle scriptable content that the public /thumb endpoint would serve (stored-XSS guard).
7
+ const THUMB_DATA_URI_RE = /^data:image\/(jpeg|png|webp);base64,[A-Za-z0-9+/=]+$/;
5
8
  function verifyManifestSig(hash, ownerId, contentType, byteSize, signedAt, apiKey, signature) {
6
9
  const payload = `${hash}|${ownerId}|${contentType}|${byteSize}|${signedAt}`;
7
10
  const expected = crypto.createHmac('sha256', apiKey).update(payload).digest('hex');
@@ -25,6 +28,8 @@ export function registerManifestsRoutes(app, deps) {
25
28
  return void res.json({ error: '请关联商品或流量口令' });
26
29
  if (thumbnail_data_uri && thumbnail_data_uri.length > THUMB_MAX_BYTES)
27
30
  return void res.json({ error: '缩略图过大(≤ 9KB 原始)' });
31
+ if (thumbnail_data_uri && !THUMB_DATA_URI_RE.test(thumbnail_data_uri))
32
+ return void res.json({ error: '缩略图必须是 data:image/(jpeg|png|webp);base64 格式' });
28
33
  // 验签
29
34
  const apiKey = me.api_key;
30
35
  if (!verifyManifestSig(hash, me.id, content_type, byte_size, signed_at, apiKey, signature)) {
@@ -82,6 +87,39 @@ export function registerManifestsRoutes(app, deps) {
82
87
  `, [req.params.hash]);
83
88
  res.json({ manifest: m, peers });
84
89
  });
90
+ // PUBLIC thumbnail bytes for <img src> (product cards can't send an auth header). Hardened:
91
+ // - :hash must be 64-hex (format guard)
92
+ // - only status='active' manifests
93
+ // - the stored data-URI must match the raster whitelist (jpeg/png/webp) — else 404 (no svg/text/html)
94
+ // - Content-Type is FORCED from the whitelisted subtype (never echoed from the stored value) + nosniff
95
+ // - size guard; SHORT revalidatable cache (max-age=300, must-revalidate) — NOT immutable: the content is
96
+ // hash-fixed but its serve-ability is not (owner/admin takedown flips status), so a long cache would
97
+ // keep serving a taken-down thumbnail. 5-min TTL absorbs list bursts while honoring takedown promptly.
98
+ // Only the low-res thumbnail is exposed (never full-res / metadata / other columns).
99
+ app.get('/api/manifests/:hash/thumb', async (req, res) => {
100
+ const hash = String(req.params.hash || '');
101
+ if (!/^[0-9a-f]{64}$/i.test(hash))
102
+ return void res.status(400).end();
103
+ const m = await dbOne(`SELECT thumbnail_data_uri, status FROM manifest_registry WHERE hash = ?`, [hash]);
104
+ if (!m || m.status !== 'active' || !m.thumbnail_data_uri)
105
+ return void res.status(404).end();
106
+ const parsed = THUMB_DATA_URI_RE.exec(m.thumbnail_data_uri);
107
+ if (!parsed)
108
+ return void res.status(404).end();
109
+ let buf;
110
+ try {
111
+ buf = Buffer.from(m.thumbnail_data_uri.slice(m.thumbnail_data_uri.indexOf(',') + 1), 'base64');
112
+ }
113
+ catch {
114
+ return void res.status(404).end();
115
+ }
116
+ if (buf.length === 0 || buf.length > 64 * 1024)
117
+ return void res.status(404).end();
118
+ res.setHeader('Content-Type', `image/${parsed[1]}`);
119
+ res.setHeader('X-Content-Type-Options', 'nosniff');
120
+ res.setHeader('Cache-Control', 'public, max-age=300, must-revalidate');
121
+ res.send(buf);
122
+ });
85
123
  app.get('/api/manifests/by-product/:pid', async (req, res) => {
86
124
  const user = auth(req, res);
87
125
  if (!user)
@@ -1,7 +1,8 @@
1
1
  import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
2
+ import { projectDirectPayTargetForViewer } from '../direct-pay-order-redaction.js'; // 披露门:自导出的 orders 也按查看者投影,不得旁路
2
3
  export function registerMeDataRoutes(app, deps) {
3
- // db 已全量走 RFC-016 异步 seam(dbOne/dbAll),不再直接用 deps.db
4
- const { auth } = deps;
4
+ // db RFC-016 异步 seam(dbOne/dbAll);deps.db 仅供披露门(requireBothDisclosuresAcked 是同步 gate)
5
+ const { auth, db } = deps;
5
6
  // COP 飞轮: 完成订单 7d 引导发笔记
6
7
  app.get('/api/me/note-prompts', async (req, res) => {
7
8
  const user = auth(req, res);
@@ -56,6 +57,8 @@ export function registerMeDataRoutes(app, deps) {
56
57
  data.profile = await dbOne(`SELECT id, name, handle, role, region, bio, search_anchor, email, phone, permanent_code, created_at, reputation FROM users WHERE id = ?`, [uid]);
57
58
  data.wallet = await dbOne(`SELECT balance, staked, escrowed, earned FROM wallets WHERE user_id = ?`, [uid]);
58
59
  data.orders = await dbAll(`SELECT * FROM orders WHERE buyer_id = ? OR seller_id = ? ORDER BY created_at DESC LIMIT 1000`, [uid, uid]);
60
+ for (const o of data.orders)
61
+ projectDirectPayTargetForViewer(db, o, uid); // 披露门:自导出按查看者投影(买家行=ack 门/卖家行=收款方保留),JSON + CSV 同源
59
62
  data.shareables = await dbAll(`SELECT * FROM shareables WHERE owner_id = ? AND status != 'removed'`, [uid]);
60
63
  data.bookmarks = await dbAll(`SELECT b.*, s.title FROM shareable_bookmarks b LEFT JOIN shareables s ON s.id = b.shareable_id WHERE b.user_id = ?`, [uid]);
61
64
  data.likes = await dbAll(`SELECT l.*, s.title FROM shareable_likes l LEFT JOIN shareables s ON s.id = l.shareable_id WHERE l.user_id = ?`, [uid]);
@@ -0,0 +1,62 @@
1
+ import { proposeMutualCancel, acceptMutualCancel, declineMutualCancel, withdrawMutualCancel, getMutualCancelState } from '../../layer3-trust/L3-1-dispute-engine/mutual-cancel.js';
2
+ export function registerMutualCancelRoutes(app, deps) {
3
+ const { db, auth, generateId, errorRes } = deps;
4
+ // 域返回 error_code → HTTP 状态。未知/校验类 → 409(与当前状态冲突);系统缺失 → 500。
5
+ const httpFor = (code) => code === 'ORDER_NOT_FOUND' ? 404
6
+ : code === 'NOT_A_PARTY' ? 403
7
+ : code === 'SYS_MISSING' || code === 'TRANSITION_FAILED' ? 500
8
+ : 409;
9
+ app.get('/api/orders/:id/mutual-cancel', (req, res) => {
10
+ const user = auth(req, res);
11
+ if (!user)
12
+ return;
13
+ const r = getMutualCancelState(db, req.params.id, user.id);
14
+ if (!r.ok)
15
+ return void errorRes(res, httpFor(r.error_code), r.error_code || 'MUTUAL_CANCEL_STATE_ERROR', r.error || '读取失败');
16
+ res.json({ success: true, proposal: r.proposal ?? null, can_propose: !!r.can_propose, can_accept: !!r.can_accept, can_decline: !!r.can_decline, can_withdraw: !!r.can_withdraw });
17
+ });
18
+ app.post('/api/orders/:id/mutual-cancel/propose', (req, res) => {
19
+ const user = auth(req, res);
20
+ if (!user)
21
+ return;
22
+ const reason = typeof req.body?.reason === 'string' ? req.body.reason : null;
23
+ const r = proposeMutualCancel(db, req.params.id, user.id, reason, generateId('mcp'));
24
+ if (!r.ok)
25
+ return void errorRes(res, httpFor(r.error_code), r.error_code || 'MUTUAL_CANCEL_PROPOSE_ERROR', r.error || '提议失败');
26
+ res.json({ success: true, proposal_id: r.proposal_id, status: r.status });
27
+ });
28
+ app.post('/api/orders/:id/mutual-cancel/accept', (req, res) => {
29
+ const user = auth(req, res);
30
+ if (!user)
31
+ return;
32
+ let r;
33
+ try {
34
+ // 资金 + 状态翻转 + 争议 resolved 必须同一原子边界(RFC-016 钱路铁律);域函数内已做竞态重校验。
35
+ r = db.transaction(() => acceptMutualCancel(db, req.params.id, user.id))();
36
+ }
37
+ catch (e) {
38
+ return void errorRes(res, 500, 'MUTUAL_CANCEL_ACCEPT_FAILED', e.message);
39
+ }
40
+ if (!r.ok)
41
+ return void errorRes(res, httpFor(r.error_code), r.error_code || 'MUTUAL_CANCEL_ACCEPT_ERROR', r.error || '确认失败');
42
+ res.json({ success: true, status: r.status, settlement: r.settlement });
43
+ });
44
+ app.post('/api/orders/:id/mutual-cancel/decline', (req, res) => {
45
+ const user = auth(req, res);
46
+ if (!user)
47
+ return;
48
+ const r = declineMutualCancel(db, req.params.id, user.id);
49
+ if (!r.ok)
50
+ return void errorRes(res, httpFor(r.error_code), r.error_code || 'MUTUAL_CANCEL_DECLINE_ERROR', r.error || '拒绝失败');
51
+ res.json({ success: true, status: r.status });
52
+ });
53
+ app.post('/api/orders/:id/mutual-cancel/withdraw', (req, res) => {
54
+ const user = auth(req, res);
55
+ if (!user)
56
+ return;
57
+ const r = withdrawMutualCancel(db, req.params.id, user.id);
58
+ if (!r.ok)
59
+ return void errorRes(res, httpFor(r.error_code), r.error_code || 'MUTUAL_CANCEL_WITHDRAW_ERROR', r.error || '撤回失败');
60
+ res.json({ success: true, status: r.status });
61
+ });
62
+ }