@seasonkoh/webaz 0.1.24 → 0.1.26

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 (195) hide show
  1. package/README.md +5 -1
  2. package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
  3. package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
  4. package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
  5. package/dist/layer0-foundation/L0-1-database/db.js +65 -0
  6. package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
  7. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
  8. package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
  9. package/dist/layer1-agent/L1-1-mcp-server/server.js +288 -208
  10. package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
  11. package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
  12. package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
  13. package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
  14. package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
  15. package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +182 -0
  16. package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
  17. package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
  18. package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +11 -3
  19. package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
  20. package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
  21. package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
  22. package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
  23. package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
  24. package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
  25. package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
  26. package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
  27. package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
  28. package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
  29. package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
  30. package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
  31. package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
  32. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
  33. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
  34. package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
  35. package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
  36. package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
  37. package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
  38. package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
  39. package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
  40. package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +191 -0
  41. package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
  42. package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
  43. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
  44. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
  45. package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
  46. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
  47. package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
  48. package/dist/pwa/acp-feed.js +13 -1
  49. package/dist/pwa/admin-bearer-auth.js +21 -0
  50. package/dist/pwa/contract-fingerprint.js +2 -0
  51. package/dist/pwa/email-delivery.js +127 -0
  52. package/dist/pwa/endpoint-actions.js +5 -1
  53. package/dist/pwa/goal-index.js +8 -8
  54. package/dist/pwa/human-presence.js +62 -0
  55. package/dist/pwa/public/app.js +1485 -283
  56. package/dist/pwa/public/i18n.js +297 -59
  57. package/dist/pwa/public/index.html +1 -0
  58. package/dist/pwa/public/openapi.json +5 -5
  59. package/dist/pwa/public/whitepaper/en/index.html +153 -0
  60. package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
  61. package/dist/pwa/rate-limit.js +22 -0
  62. package/dist/pwa/routes/account-deletion.js +15 -13
  63. package/dist/pwa/routes/addresses.js +10 -9
  64. package/dist/pwa/routes/admin-admins.js +13 -14
  65. package/dist/pwa/routes/admin-analytics.js +109 -69
  66. package/dist/pwa/routes/admin-atomic.js +10 -4
  67. package/dist/pwa/routes/admin-catalog.js +13 -11
  68. package/dist/pwa/routes/admin-editor-picks.js +15 -10
  69. package/dist/pwa/routes/admin-events.js +5 -3
  70. package/dist/pwa/routes/admin-health.js +2 -1
  71. package/dist/pwa/routes/admin-moderation.js +50 -29
  72. package/dist/pwa/routes/admin-ops.js +35 -23
  73. package/dist/pwa/routes/admin-protocol-params.js +16 -19
  74. package/dist/pwa/routes/admin-reports.js +23 -21
  75. package/dist/pwa/routes/admin-tokenomics.js +26 -25
  76. package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
  77. package/dist/pwa/routes/admin-users-query.js +65 -53
  78. package/dist/pwa/routes/admin-verifier-flow.js +82 -41
  79. package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
  80. package/dist/pwa/routes/admin-wallet-ops.js +32 -7
  81. package/dist/pwa/routes/agent-buy.js +46 -22
  82. package/dist/pwa/routes/agent-governance.js +52 -56
  83. package/dist/pwa/routes/ai.js +7 -5
  84. package/dist/pwa/routes/analytics.js +43 -41
  85. package/dist/pwa/routes/anchors.js +19 -20
  86. package/dist/pwa/routes/announcements.js +13 -13
  87. package/dist/pwa/routes/arbitrator.js +97 -31
  88. package/dist/pwa/routes/auction.js +157 -116
  89. package/dist/pwa/routes/auth-login.js +6 -4
  90. package/dist/pwa/routes/auth-read.js +21 -10
  91. package/dist/pwa/routes/auth-register.js +111 -26
  92. package/dist/pwa/routes/auth-sessions.js +12 -11
  93. package/dist/pwa/routes/blocklist.js +16 -15
  94. package/dist/pwa/routes/build-feedback.js +10 -9
  95. package/dist/pwa/routes/build-reputation.js +6 -2
  96. package/dist/pwa/routes/build-tasks.js +45 -13
  97. package/dist/pwa/routes/buyer-feeds.js +27 -25
  98. package/dist/pwa/routes/cart.js +16 -15
  99. package/dist/pwa/routes/charity.js +212 -150
  100. package/dist/pwa/routes/chat.js +42 -43
  101. package/dist/pwa/routes/checkin-tasks.js +10 -9
  102. package/dist/pwa/routes/checkout-helpers.js +12 -10
  103. package/dist/pwa/routes/claim-initiators.js +34 -14
  104. package/dist/pwa/routes/claim-verify.js +86 -53
  105. package/dist/pwa/routes/claim-voting.js +43 -18
  106. package/dist/pwa/routes/contribution-identity.js +164 -0
  107. package/dist/pwa/routes/contribution-score.js +19 -0
  108. package/dist/pwa/routes/coupons.js +19 -16
  109. package/dist/pwa/routes/dashboards.js +18 -16
  110. package/dist/pwa/routes/dispute-cases.js +25 -24
  111. package/dist/pwa/routes/disputes-read.js +45 -51
  112. package/dist/pwa/routes/disputes-write.js +124 -61
  113. package/dist/pwa/routes/evidence.js +9 -9
  114. package/dist/pwa/routes/external-anchors.js +13 -12
  115. package/dist/pwa/routes/feedback.js +29 -33
  116. package/dist/pwa/routes/flash-sales.js +18 -16
  117. package/dist/pwa/routes/follows.js +25 -24
  118. package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
  119. package/dist/pwa/routes/governance-onboarding.js +70 -59
  120. package/dist/pwa/routes/group-buys.js +22 -22
  121. package/dist/pwa/routes/growth.js +34 -31
  122. package/dist/pwa/routes/import-product.js +12 -10
  123. package/dist/pwa/routes/kyc.js +9 -8
  124. package/dist/pwa/routes/leaderboard.js +20 -18
  125. package/dist/pwa/routes/listings.js +23 -22
  126. package/dist/pwa/routes/logistics.js +10 -8
  127. package/dist/pwa/routes/manifests.js +27 -27
  128. package/dist/pwa/routes/me-data.js +23 -21
  129. package/dist/pwa/routes/notifications.js +7 -6
  130. package/dist/pwa/routes/offers.js +30 -12
  131. package/dist/pwa/routes/orders-action.js +51 -29
  132. package/dist/pwa/routes/orders-create.js +75 -20
  133. package/dist/pwa/routes/orders-read.js +21 -20
  134. package/dist/pwa/routes/p2p-products.js +30 -18
  135. package/dist/pwa/routes/payments-governance.js +61 -56
  136. package/dist/pwa/routes/peers.js +9 -8
  137. package/dist/pwa/routes/pin-receipts.js +13 -13
  138. package/dist/pwa/routes/products-aliases.js +12 -10
  139. package/dist/pwa/routes/products-claims.js +36 -17
  140. package/dist/pwa/routes/products-create.js +53 -38
  141. package/dist/pwa/routes/products-crud.js +17 -16
  142. package/dist/pwa/routes/products-links.js +49 -26
  143. package/dist/pwa/routes/products-list.js +6 -4
  144. package/dist/pwa/routes/products-meta.js +40 -39
  145. package/dist/pwa/routes/products-update.js +19 -5
  146. package/dist/pwa/routes/profile-credentials.js +20 -19
  147. package/dist/pwa/routes/profile-identity.js +14 -13
  148. package/dist/pwa/routes/profile-location.js +7 -6
  149. package/dist/pwa/routes/profile-placement.js +20 -19
  150. package/dist/pwa/routes/profile-prefs.js +11 -11
  151. package/dist/pwa/routes/promoter.js +58 -66
  152. package/dist/pwa/routes/public-build-tasks.js +19 -0
  153. package/dist/pwa/routes/public-utils.js +108 -46
  154. package/dist/pwa/routes/push.js +16 -15
  155. package/dist/pwa/routes/ratings.js +92 -32
  156. package/dist/pwa/routes/recover-key.js +66 -26
  157. package/dist/pwa/routes/referral.js +37 -52
  158. package/dist/pwa/routes/reputation.js +3 -2
  159. package/dist/pwa/routes/returns.js +76 -73
  160. package/dist/pwa/routes/reviews.js +41 -18
  161. package/dist/pwa/routes/rewards-apply.js +16 -15
  162. package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
  163. package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
  164. package/dist/pwa/routes/rfqs.js +163 -85
  165. package/dist/pwa/routes/search.js +16 -14
  166. package/dist/pwa/routes/secondhand.js +25 -22
  167. package/dist/pwa/routes/seller-quota.js +24 -26
  168. package/dist/pwa/routes/share-redirects.js +60 -55
  169. package/dist/pwa/routes/shareables-interactions.js +34 -35
  170. package/dist/pwa/routes/shareables.js +55 -51
  171. package/dist/pwa/routes/shop-referral.js +58 -0
  172. package/dist/pwa/routes/shops.js +25 -20
  173. package/dist/pwa/routes/signaling.js +10 -9
  174. package/dist/pwa/routes/skill-market.js +16 -16
  175. package/dist/pwa/routes/skills.js +15 -14
  176. package/dist/pwa/routes/snf.js +14 -13
  177. package/dist/pwa/routes/tags.js +10 -9
  178. package/dist/pwa/routes/task-proposals.js +121 -0
  179. package/dist/pwa/routes/trial.js +72 -52
  180. package/dist/pwa/routes/trusted-kpi.js +20 -18
  181. package/dist/pwa/routes/url-claim.js +67 -28
  182. package/dist/pwa/routes/users-public.js +62 -70
  183. package/dist/pwa/routes/variants.js +12 -13
  184. package/dist/pwa/routes/verifier-user.js +61 -21
  185. package/dist/pwa/routes/verify-tasks.js +49 -25
  186. package/dist/pwa/routes/waitlist.js +16 -15
  187. package/dist/pwa/routes/wallet-read.js +75 -37
  188. package/dist/pwa/routes/wallet-write.js +12 -9
  189. package/dist/pwa/routes/webauthn.js +25 -26
  190. package/dist/pwa/routes/webhooks.js +26 -26
  191. package/dist/pwa/routes/welcome.js +45 -50
  192. package/dist/pwa/routes/wishlist-qa.js +29 -32
  193. package/dist/pwa/server.js +304 -90
  194. package/dist/version.js +1 -1
  195. package/package.json +76 -3
@@ -1,3 +1,4 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  const VALID_CHAT_KINDS = new Set(['order', 'rfq', 'listing_qa']);
2
3
  // 反诈正则(命中即 flag,仍发出)
3
4
  const FRAUD_PATTERNS = [
@@ -21,9 +22,9 @@ export function detectFraud(text) {
21
22
  export function registerChatRoutes(app, deps) {
22
23
  const { db, auth, generateId, rateLimitOk } = deps;
23
24
  // 上下文 + 对方校验:只允许有真实商业关系的两方建会话
24
- function resolveChatParticipants(kind, contextId, requesterId, recipientId) {
25
+ async function resolveChatParticipants(kind, contextId, requesterId, recipientId) {
25
26
  if (kind === 'order') {
26
- const o = db.prepare('SELECT buyer_id, seller_id FROM orders WHERE id = ?').get(contextId);
27
+ const o = await dbOne('SELECT buyer_id, seller_id FROM orders WHERE id = ?', [contextId]);
27
28
  if (!o)
28
29
  return { user_a: '', user_b: '', allowed: false, reason: '订单不存在' };
29
30
  if (requesterId !== o.buyer_id && requesterId !== o.seller_id)
@@ -31,26 +32,26 @@ export function registerChatRoutes(app, deps) {
31
32
  return { user_a: o.buyer_id, user_b: o.seller_id, allowed: true };
32
33
  }
33
34
  if (kind === 'rfq') {
34
- const r = db.prepare('SELECT buyer_id, status FROM rfqs WHERE id = ?').get(contextId);
35
+ const r = await dbOne('SELECT buyer_id, status FROM rfqs WHERE id = ?', [contextId]);
35
36
  if (!r)
36
37
  return { user_a: '', user_b: '', allowed: false, reason: 'RFQ 不存在' };
37
38
  if (requesterId === r.buyer_id) {
38
39
  // buyer 主动找某个 bidder
39
40
  if (!recipientId)
40
41
  return { user_a: '', user_b: '', allowed: false, reason: '需指定 recipient' };
41
- const hasBid = db.prepare('SELECT 1 FROM bids WHERE rfq_id = ? AND seller_id = ?').get(contextId, recipientId);
42
+ const hasBid = await dbOne('SELECT 1 FROM bids WHERE rfq_id = ? AND seller_id = ?', [contextId, recipientId]);
42
43
  if (!hasBid)
43
44
  return { user_a: '', user_b: '', allowed: false, reason: '对方未对此 RFQ 报价' };
44
45
  return { user_a: r.buyer_id, user_b: recipientId, allowed: true };
45
46
  }
46
47
  // seller 找 buyer:必须自己已 bid
47
- const myBid = db.prepare('SELECT 1 FROM bids WHERE rfq_id = ? AND seller_id = ?').get(contextId, requesterId);
48
+ const myBid = await dbOne('SELECT 1 FROM bids WHERE rfq_id = ? AND seller_id = ?', [contextId, requesterId]);
48
49
  if (!myBid)
49
50
  return { user_a: '', user_b: '', allowed: false, reason: '需先报价才能联系买家' };
50
51
  return { user_a: r.buyer_id, user_b: requesterId, allowed: true };
51
52
  }
52
53
  if (kind === 'listing_qa') {
53
- const l = db.prepare('SELECT created_by FROM listings WHERE id = ?').get(contextId);
54
+ const l = await dbOne('SELECT created_by FROM listings WHERE id = ?', [contextId]);
54
55
  if (!l)
55
56
  return { user_a: '', user_b: '', allowed: false, reason: 'listing 不存在' };
56
57
  // 仅非创建者可主动发起(避免 listing 创建者主动 spam)
@@ -59,7 +60,7 @@ export function registerChatRoutes(app, deps) {
59
60
  if (!recipientId)
60
61
  return { user_a: '', user_b: '', allowed: false, reason: '请等买家先发起咨询' };
61
62
  const [a, b] = l.created_by < recipientId ? [l.created_by, recipientId] : [recipientId, l.created_by];
62
- const exists = db.prepare("SELECT 1 FROM conversations WHERE kind = 'listing_qa' AND context_id = ? AND user_a = ? AND user_b = ?").get(contextId, a, b);
63
+ const exists = await dbOne("SELECT 1 FROM conversations WHERE kind = 'listing_qa' AND context_id = ? AND user_a = ? AND user_b = ?", [contextId, a, b]);
63
64
  if (!exists)
64
65
  return { user_a: '', user_b: '', allowed: false, reason: '请等买家先发起咨询' };
65
66
  return { user_a: l.created_by, user_b: recipientId, allowed: true };
@@ -68,22 +69,21 @@ export function registerChatRoutes(app, deps) {
68
69
  }
69
70
  return { user_a: '', user_b: '', allowed: false, reason: 'kind 无效' };
70
71
  }
71
- function findOrCreateConv(kind, contextId, userA, userB) {
72
+ async function findOrCreateConv(kind, contextId, userA, userB) {
72
73
  // 规范化:user_a 字典序较小
73
74
  const [a, b] = userA < userB ? [userA, userB] : [userB, userA];
74
- const selectStmt = db.prepare('SELECT id FROM conversations WHERE kind = ? AND context_id = ? AND user_a = ? AND user_b = ?');
75
- const existing = selectStmt.get(kind, contextId, a, b);
75
+ const SELECT_SQL = 'SELECT id FROM conversations WHERE kind = ? AND context_id = ? AND user_a = ? AND user_b = ?';
76
+ const existing = await dbOne(SELECT_SQL, [kind, contextId, a, b]);
76
77
  if (existing)
77
78
  return existing.id;
78
79
  // 并发场景下 UNIQUE 可能触发;用 INSERT OR IGNORE + 再次 SELECT 兜底
79
80
  const id = generateId('cv');
80
- db.prepare(`INSERT OR IGNORE INTO conversations (id, kind, context_id, user_a, user_b) VALUES (?,?,?,?,?)`)
81
- .run(id, kind, contextId, a, b);
82
- const final = selectStmt.get(kind, contextId, a, b);
81
+ await dbRun(`INSERT OR IGNORE INTO conversations (id, kind, context_id, user_a, user_b) VALUES (?,?,?,?,?)`, [id, kind, contextId, a, b]);
82
+ const final = await dbOne(SELECT_SQL, [kind, contextId, a, b]);
83
83
  return final.id;
84
84
  }
85
85
  // 开会话(idempotent — 已存在则返回 id)
86
- app.post('/api/conversations/start', (req, res) => {
86
+ app.post('/api/conversations/start', async (req, res) => {
87
87
  const user = auth(req, res);
88
88
  if (!user)
89
89
  return;
@@ -92,18 +92,18 @@ export function registerChatRoutes(app, deps) {
92
92
  return void res.json({ error: 'kind 无效' });
93
93
  if (!context_id)
94
94
  return void res.json({ error: 'context_id 必填' });
95
- const r = resolveChatParticipants(String(kind), String(context_id), user.id, recipient_id ? String(recipient_id) : null);
95
+ const r = await resolveChatParticipants(String(kind), String(context_id), user.id, recipient_id ? String(recipient_id) : null);
96
96
  if (!r.allowed)
97
97
  return void res.json({ error: r.reason || '无权开启会话' });
98
- const id = findOrCreateConv(String(kind), String(context_id), r.user_a, r.user_b);
98
+ const id = await findOrCreateConv(String(kind), String(context_id), r.user_a, r.user_b);
99
99
  res.json({ id, kind, context_id });
100
100
  });
101
101
  // 我的会话列表
102
- app.get('/api/conversations', (req, res) => {
102
+ app.get('/api/conversations', async (req, res) => {
103
103
  const user = auth(req, res);
104
104
  if (!user)
105
105
  return;
106
- const rows = db.prepare(`
106
+ const rows = await dbAll(`
107
107
  SELECT c.*,
108
108
  CASE WHEN c.user_a = ? THEN c.unread_a ELSE c.unread_b END as my_unread,
109
109
  CASE WHEN c.user_a = ? THEN c.user_b ELSE c.user_a END as other_id,
@@ -113,15 +113,15 @@ export function registerChatRoutes(app, deps) {
113
113
  WHERE (c.user_a = ? OR c.user_b = ?) AND c.status NOT IN ('blocked','archived')
114
114
  ORDER BY COALESCE(c.last_message_at, c.created_at) DESC
115
115
  LIMIT 100
116
- `).all(user.id, user.id, user.id, user.id, user.id, user.id);
116
+ `, [user.id, user.id, user.id, user.id, user.id, user.id]);
117
117
  res.json({ items: rows });
118
118
  });
119
119
  // 会话详情 + 消息分页
120
- app.get('/api/conversations/:id', (req, res) => {
120
+ app.get('/api/conversations/:id', async (req, res) => {
121
121
  const user = auth(req, res);
122
122
  if (!user)
123
123
  return;
124
- const conv = db.prepare('SELECT * FROM conversations WHERE id = ?').get(req.params.id);
124
+ const conv = await dbOne('SELECT * FROM conversations WHERE id = ?', [req.params.id]);
125
125
  if (!conv)
126
126
  return void res.status(404).json({ error: '会话不存在' });
127
127
  if (conv.user_a !== user.id && conv.user_b !== user.id)
@@ -135,13 +135,13 @@ export function registerChatRoutes(app, deps) {
135
135
  args.push(before);
136
136
  }
137
137
  args.push(limit);
138
- const messages = db.prepare(`
138
+ const messages = await dbAll(`
139
139
  SELECT id, sender_id, body, attachments, flagged, flag_reasons, read_at, kind, meta, created_at
140
140
  FROM messages
141
141
  WHERE conversation_id = ?${whereExtra}
142
142
  ORDER BY created_at DESC
143
143
  LIMIT ?
144
- `).all(...args);
144
+ `, args);
145
145
  messages.reverse(); // 返回时间正序,便于前端 append
146
146
  // QA 轮 11 P1:read 端 flag_reasons 是 JSON string,send 端是 array → agent 双向 parse 易错
147
147
  // 统一为 array (send 端格式)
@@ -170,18 +170,18 @@ export function registerChatRoutes(app, deps) {
170
170
  }
171
171
  }
172
172
  const otherId = conv.user_a === user.id ? conv.user_b : conv.user_a;
173
- const other = db.prepare('SELECT id, handle, name, region FROM users WHERE id = ?').get(otherId);
173
+ const other = await dbOne('SELECT id, handle, name, region FROM users WHERE id = ?', [otherId]);
174
174
  res.json({ conv, messages, other });
175
175
  });
176
176
  // 发消息
177
- app.post('/api/conversations/:id/messages', (req, res) => {
177
+ app.post('/api/conversations/:id/messages', async (req, res) => {
178
178
  const user = auth(req, res);
179
179
  if (!user)
180
180
  return;
181
181
  // P1: 频率限制 — 同用户每分钟 ≤ 60 条(≈ 1/s 持续 + 短时突发)
182
182
  if (!rateLimitOk(`chat_msg:${user.id}`, 60, 60_000))
183
183
  return void res.status(429).json({ error: '发送过于频繁,请稍等' });
184
- const conv = db.prepare('SELECT * FROM conversations WHERE id = ?').get(req.params.id);
184
+ const conv = await dbOne('SELECT * FROM conversations WHERE id = ?', [req.params.id]);
185
185
  if (!conv)
186
186
  return void res.status(404).json({ error: '会话不存在' });
187
187
  if (conv.status === 'blocked')
@@ -238,9 +238,8 @@ export function registerChatRoutes(app, deps) {
238
238
  // 通知接收方
239
239
  try {
240
240
  const recipient = isFromA ? conv.user_b : conv.user_a;
241
- db.prepare(`INSERT INTO notifications (id, user_id, type, title, body, created_at)
242
- VALUES (?,?,'chat_new',?,?,datetime('now'))`)
243
- .run(generateId('ntf'), recipient, `💬 新消息`, preview);
241
+ await dbRun(`INSERT INTO notifications (id, user_id, type, title, body, created_at)
242
+ VALUES (?,?,'chat_new',?,?,datetime('now'))`, [generateId('ntf'), recipient, `💬 新消息`, preview]);
244
243
  }
245
244
  catch (e) {
246
245
  console.error('[chat notify]', e);
@@ -248,11 +247,11 @@ export function registerChatRoutes(app, deps) {
248
247
  res.json({ id, flagged: reasons.length > 0, flag_reasons: reasons });
249
248
  });
250
249
  // 标记已读
251
- app.post('/api/conversations/:id/read', (req, res) => {
250
+ app.post('/api/conversations/:id/read', async (req, res) => {
252
251
  const user = auth(req, res);
253
252
  if (!user)
254
253
  return;
255
- const conv = db.prepare('SELECT user_a, user_b FROM conversations WHERE id = ?').get(req.params.id);
254
+ const conv = await dbOne('SELECT user_a, user_b FROM conversations WHERE id = ?', [req.params.id]);
256
255
  if (!conv)
257
256
  return void res.status(404).json({ error: '会话不存在' });
258
257
  if (conv.user_a !== user.id && conv.user_b !== user.id)
@@ -266,37 +265,37 @@ export function registerChatRoutes(app, deps) {
266
265
  res.json({ success: true });
267
266
  });
268
267
  // 归档(仅自己侧)
269
- app.post('/api/conversations/:id/archive', (req, res) => {
268
+ app.post('/api/conversations/:id/archive', async (req, res) => {
270
269
  const user = auth(req, res);
271
270
  if (!user)
272
271
  return;
273
- const conv = db.prepare('SELECT user_a, user_b, status FROM conversations WHERE id = ?').get(req.params.id);
272
+ const conv = await dbOne('SELECT user_a, user_b, status FROM conversations WHERE id = ?', [req.params.id]);
274
273
  if (!conv)
275
274
  return void res.status(404).json({ error: '会话不存在' });
276
275
  if (conv.user_a !== user.id && conv.user_b !== user.id)
277
276
  return void res.status(403).json({ error: '无权操作' });
278
- db.prepare("UPDATE conversations SET status = 'archived' WHERE id = ?").run(req.params.id);
277
+ await dbRun("UPDATE conversations SET status = 'archived' WHERE id = ?", [req.params.id]);
279
278
  res.json({ success: true });
280
279
  });
281
280
  // 拉黑(双向屏蔽)
282
- app.post('/api/conversations/:id/block', (req, res) => {
281
+ app.post('/api/conversations/:id/block', async (req, res) => {
283
282
  const user = auth(req, res);
284
283
  if (!user)
285
284
  return;
286
- const conv = db.prepare('SELECT user_a, user_b FROM conversations WHERE id = ?').get(req.params.id);
285
+ const conv = await dbOne('SELECT user_a, user_b FROM conversations WHERE id = ?', [req.params.id]);
287
286
  if (!conv)
288
287
  return void res.status(404).json({ error: '会话不存在' });
289
288
  if (conv.user_a !== user.id && conv.user_b !== user.id)
290
289
  return void res.status(403).json({ error: '无权操作' });
291
- db.prepare("UPDATE conversations SET status = 'blocked' WHERE id = ?").run(req.params.id);
290
+ await dbRun("UPDATE conversations SET status = 'blocked' WHERE id = ?", [req.params.id]);
292
291
  res.json({ success: true });
293
292
  });
294
293
  // 举报(人工审核)
295
- app.post('/api/conversations/:id/report', (req, res) => {
294
+ app.post('/api/conversations/:id/report', async (req, res) => {
296
295
  const user = auth(req, res);
297
296
  if (!user)
298
297
  return;
299
- const conv = db.prepare('SELECT user_a, user_b FROM conversations WHERE id = ?').get(req.params.id);
298
+ const conv = await dbOne('SELECT user_a, user_b FROM conversations WHERE id = ?', [req.params.id]);
300
299
  if (!conv)
301
300
  return void res.status(404).json({ error: '会话不存在' });
302
301
  if (conv.user_a !== user.id && conv.user_b !== user.id)
@@ -306,13 +305,13 @@ export function registerChatRoutes(app, deps) {
306
305
  if (!reason)
307
306
  return void res.json({ error: '原因必填' });
308
307
  // P1: 同 (reporter, conversation) 24h 内最多 3 次
309
- const recentRpt = db.prepare(`SELECT COUNT(1) as n FROM chat_reports WHERE conversation_id = ? AND reporter_id = ? AND created_at > datetime('now','-1 day')`).get(req.params.id, user.id).n;
308
+ const recentRpt = (await dbOne(`SELECT COUNT(1) as n FROM chat_reports WHERE conversation_id = ? AND reporter_id = ? AND created_at > datetime('now','-1 day')`, [req.params.id, user.id])).n;
310
309
  if (recentRpt >= 3)
311
310
  return void res.status(429).json({ error: '24 小时内对同一会话最多举报 3 次' });
312
311
  const reportedId = conv.user_a === user.id ? conv.user_b : conv.user_a;
313
- db.prepare(`INSERT INTO chat_reports (id, conversation_id, message_id, reporter_id, reported_id, reason, note)
314
- VALUES (?,?,?,?,?,?,?)`)
315
- .run(generateId('rpt'), req.params.id, body.message_id ? String(body.message_id) : null, user.id, reportedId, reason, body.note ? String(body.note).slice(0, 500) : null);
312
+ await dbRun(`INSERT INTO chat_reports (id, conversation_id, message_id, reporter_id, reported_id, reason, note)
313
+ VALUES (?,?,?,?,?,?,?)`, [generateId('rpt'), req.params.id, body.message_id ? String(body.message_id) : null,
314
+ user.id, reportedId, reason, body.note ? String(body.note).slice(0, 500) : null]);
316
315
  res.json({ success: true });
317
316
  });
318
317
  }
@@ -1,16 +1,17 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerCheckinTasksRoutes(app, deps) {
2
3
  const { db, auth, isTrustedRole, errorRes, generateId, getProtocolParam, resolveCheckinDate, TASK_DEFS, computeTaskProgress, disbursePlatformReward, broadcastSystemEvent } = deps;
3
- app.get('/api/checkin/status', (req, res) => {
4
+ app.get('/api/checkin/status', async (req, res) => {
4
5
  const user = auth(req, res);
5
6
  if (!user)
6
7
  return;
7
8
  if (isTrustedRole(user))
8
9
  return void errorRes(res, 403, 'TRUSTED_ROLE_NO_TRADE', '受信角色无此功能');
9
10
  const today = resolveCheckinDate(req.query.local_date ? String(req.query.local_date) : undefined);
10
- const todayCheckin = db.prepare('SELECT reward, streak FROM daily_checkins WHERE user_id = ? AND checkin_date = ?').get(user.id, today);
11
+ const todayCheckin = await dbOne('SELECT reward, streak FROM daily_checkins WHERE user_id = ? AND checkin_date = ?', [user.id, today]);
11
12
  // streak: 连续签到 — 检查昨日
12
13
  const yesterday = new Date(new Date(today + 'T00:00:00Z').getTime() - 86400000).toISOString().slice(0, 10);
13
- const yesterdayCheckin = db.prepare('SELECT streak FROM daily_checkins WHERE user_id = ? AND checkin_date = ?').get(user.id, yesterday);
14
+ const yesterdayCheckin = await dbOne('SELECT streak FROM daily_checkins WHERE user_id = ? AND checkin_date = ?', [user.id, yesterday]);
14
15
  const currentStreak = todayCheckin?.streak || (yesterdayCheckin ? yesterdayCheckin.streak + 1 : 1);
15
16
  // F-2: 里程碑参数 admin 可调
16
17
  const bonus7 = getProtocolParam('streak_bonus_7', 5);
@@ -22,7 +23,7 @@ export function registerCheckinTasksRoutes(app, deps) {
22
23
  // 任务列表
23
24
  const progress = computeTaskProgress(String(user.id));
24
25
  const claimed = new Map();
25
- for (const row of db.prepare('SELECT task_key, claimed_at FROM task_completions WHERE user_id = ?').all(user.id)) {
26
+ for (const row of await dbAll('SELECT task_key, claimed_at FROM task_completions WHERE user_id = ?', [user.id])) {
26
27
  if (row.claimed_at)
27
28
  claimed.set(row.task_key, row.claimed_at);
28
29
  }
@@ -43,18 +44,18 @@ export function registerCheckinTasksRoutes(app, deps) {
43
44
  tasks,
44
45
  });
45
46
  });
46
- app.post('/api/checkin', (req, res) => {
47
+ app.post('/api/checkin', async (req, res) => {
47
48
  const user = auth(req, res);
48
49
  if (!user)
49
50
  return;
50
51
  if (isTrustedRole(user))
51
52
  return void errorRes(res, 403, 'TRUSTED_ROLE_NO_TRADE', '受信角色无此功能');
52
53
  const today = resolveCheckinDate(req.body?.local_date ? String(req.body.local_date) : undefined);
53
- const existing = db.prepare('SELECT reward FROM daily_checkins WHERE user_id = ? AND checkin_date = ?').get(user.id, today);
54
+ const existing = await dbOne('SELECT reward FROM daily_checkins WHERE user_id = ? AND checkin_date = ?', [user.id, today]);
54
55
  if (existing)
55
56
  return void res.status(400).json({ error: '今日已签到', error_code: 'ALREADY_CHECKED_IN' });
56
57
  const yesterday = new Date(new Date(today + 'T00:00:00Z').getTime() - 86400000).toISOString().slice(0, 10);
57
- const yesterdayCheckin = db.prepare('SELECT streak FROM daily_checkins WHERE user_id = ? AND checkin_date = ?').get(user.id, yesterday);
58
+ const yesterdayCheckin = await dbOne('SELECT streak FROM daily_checkins WHERE user_id = ? AND checkin_date = ?', [user.id, yesterday]);
58
59
  const streak = yesterdayCheckin ? yesterdayCheckin.streak + 1 : 1;
59
60
  // F-2: admin 可调里程碑
60
61
  const bonus7 = getProtocolParam('streak_bonus_7', 5);
@@ -84,7 +85,7 @@ export function registerCheckinTasksRoutes(app, deps) {
84
85
  catch { }
85
86
  res.json({ success: true, reward, streak, milestone_bonus: milestoneBonus });
86
87
  });
87
- app.post('/api/tasks/:key/claim', (req, res) => {
88
+ app.post('/api/tasks/:key/claim', async (req, res) => {
88
89
  const user = auth(req, res);
89
90
  if (!user)
90
91
  return;
@@ -97,7 +98,7 @@ export function registerCheckinTasksRoutes(app, deps) {
97
98
  const progress = computeTaskProgress(String(user.id));
98
99
  if (!progress[taskKey].eligible)
99
100
  return void res.status(400).json({ error: '任务未完成', progress: progress[taskKey] });
100
- const existing = db.prepare('SELECT claimed_at FROM task_completions WHERE user_id = ? AND task_key = ?').get(user.id, taskKey);
101
+ const existing = await dbOne('SELECT claimed_at FROM task_completions WHERE user_id = ? AND task_key = ?', [user.id, taskKey]);
101
102
  if (existing?.claimed_at)
102
103
  return void res.status(400).json({ error: '任务奖励已领取' });
103
104
  db.transaction(() => {
@@ -1,7 +1,9 @@
1
1
  import { buildIntentMandate, signMandate } from './ap2-mandate.js';
2
+ import { dbOne, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
2
3
  export function registerCheckoutHelpersRoutes(app, deps) {
3
- const { db, auth, generateId, formatProductForAgent, signPassport, issuerAddress } = deps;
4
- app.get('/api/checkout/tax-preview', (req, res) => {
4
+ // db 已走 RFC-016 异步 seam(dbOne/dbRun),不再直接用 deps.db
5
+ const { auth, generateId, formatProductForAgent, signPassport, issuerAddress } = deps;
6
+ app.get('/api/checkout/tax-preview', async (req, res) => {
5
7
  const user = auth(req, res);
6
8
  if (!user)
7
9
  return;
@@ -9,9 +11,9 @@ export function registerCheckoutHelpersRoutes(app, deps) {
9
11
  const qtyN = Math.max(1, Math.floor(Number(req.query.quantity) || 1));
10
12
  if (!productId)
11
13
  return void res.status(400).json({ error: 'product_id required' });
12
- const product = db.prepare(`SELECT p.id, p.price, p.title, u.region as seller_region
14
+ const product = await dbOne(`SELECT p.id, p.price, p.title, u.region as seller_region
13
15
  FROM products p JOIN users u ON u.id = p.seller_id
14
- WHERE p.id = ?`).get(productId);
16
+ WHERE p.id = ?`, [productId]);
15
17
  if (!product)
16
18
  return void res.status(404).json({ error: '商品不存在' });
17
19
  const buyerRegion = user.region || 'global';
@@ -24,8 +26,8 @@ export function registerCheckoutHelpersRoutes(app, deps) {
24
26
  disclaimer: '同地区订单,无跨境关税',
25
27
  });
26
28
  }
27
- const cfg = db.prepare(`SELECT est_import_duty_pct, est_import_threshold_waz
28
- FROM region_config WHERE region = ?`).get(buyerRegion);
29
+ const cfg = await dbOne(`SELECT est_import_duty_pct, est_import_threshold_waz
30
+ FROM region_config WHERE region = ?`, [buyerRegion]);
29
31
  const pct = Number(cfg?.est_import_duty_pct || 0);
30
32
  const threshold = Number(cfg?.est_import_threshold_waz || 0);
31
33
  const orderTotal = Number(product.price) * qtyN;
@@ -51,14 +53,14 @@ export function registerCheckoutHelpersRoutes(app, deps) {
51
53
  const { product_id, quantity = 1 } = req.body;
52
54
  if (!product_id)
53
55
  return void res.json({ error: '请提供 product_id' });
54
- const product = db.prepare(`
56
+ const product = await dbOne(`
55
57
  SELECT p.*, u.name as seller_name,
56
58
  COALESCE(rs.level, 'new') as rep_level
57
59
  FROM products p
58
60
  JOIN users u ON p.seller_id = u.id
59
61
  LEFT JOIN reputation_scores rs ON rs.user_id = p.seller_id
60
62
  WHERE p.id = ? AND p.status = 'active'
61
- `).get(product_id);
63
+ `, [product_id]);
62
64
  if (!product)
63
65
  return void res.json({ error: '商品不存在或已下架' });
64
66
  const qty = Number(quantity);
@@ -68,10 +70,10 @@ export function registerCheckoutHelpersRoutes(app, deps) {
68
70
  const now = new Date();
69
71
  const expiresAt = new Date(now.getTime() + 10 * 60_000);
70
72
  const token = generateId('pst');
71
- db.prepare(`
73
+ await dbRun(`
72
74
  INSERT INTO price_sessions (token, product_id, user_id, price, quantity, created_at, expires_at)
73
75
  VALUES (?, ?, ?, ?, ?, ?, ?)
74
- `).run(token, product_id, user.id, product.price, qty, now.toISOString(), expiresAt.toISOString());
76
+ `, [token, product_id, user.id, product.price, qty, now.toISOString(), expiresAt.toISOString()]);
75
77
  // AP2 (B.4 b) — Intent Mandate 并存输出;不破坏现有 session_token
76
78
  let ap2_intent_mandate = null;
77
79
  try {
@@ -1,16 +1,18 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerClaimInitiatorsRoutes(app, deps) {
3
+ // 只读/单写站点走 RFC-016 异步 seam;db 保留:claim 是质押/escrow 资金路径,
4
+ // dup 门 + 钱包扣减 + INSERT 任务必须原子(db.transaction),Phase 3 迁 pg 行锁。
2
5
  const { db, auth, isTrustedRole, errorRes, generateId } = deps;
3
6
  const wire = (cfg) => {
4
7
  const { entityPath, entityTable, entityIdCol, entityPartyCol, taskTable, taskPartyCol, voteTable, targets, stake, deadlineHours, idPrefix, allowedStatuses, notFoundMsg, ownClaimMsg, statusErrMsg, dupErrMsg, taskAlias: a } = cfg;
5
- app.post(`/api/${entityPath}/:id/claim`, (req, res) => {
8
+ app.post(`/api/${entityPath}/:id/claim`, async (req, res) => {
6
9
  const user = auth(req, res);
7
10
  if (!user)
8
11
  return;
9
12
  if (isTrustedRole(user)) {
10
13
  return void errorRes(res, 403, 'TRUSTED_ROLE_NO_CLAIM', '受信角色不可发起声明');
11
14
  }
12
- const entity = db.prepare(`SELECT id, ${entityPartyCol}, status FROM ${entityTable} WHERE id = ?`)
13
- .get(req.params.id);
15
+ const entity = await dbOne(`SELECT id, ${entityPartyCol}, status FROM ${entityTable} WHERE id = ?`, [req.params.id]);
14
16
  if (!entity)
15
17
  return void res.status(404).json({ error: notFoundMsg });
16
18
  const partyId = entity[entityPartyCol];
@@ -25,22 +27,40 @@ export function registerClaimInitiatorsRoutes(app, deps) {
25
27
  if (text.length < 6 || text.length > 500)
26
28
  return void res.status(400).json({ error: 'claim_text 长度需 6-500 字' });
27
29
  const evidence = req.body?.evidence_uri ? String(req.body.evidence_uri).trim().slice(0, 500) : null;
28
- const wallet = db.prepare('SELECT balance FROM wallets WHERE user_id = ?').get(user.id);
30
+ // 友好预检查(读):余额不足直接早退;真正的守恒门在事务内(WHERE balance >= stake)
31
+ const wallet = await dbOne('SELECT balance FROM wallets WHERE user_id = ?', [user.id]);
29
32
  if (!wallet || wallet.balance < stake)
30
33
  return void res.status(400).json({ error: `余额不足:发起需锁 ${stake} WAZ` });
31
- const dup = db.prepare(`SELECT id FROM ${taskTable} WHERE ${entityIdCol} = ? AND claimant_id = ? AND claim_target = ? AND status = 'open'`)
32
- .get(req.params.id, user.id, target);
33
- if (dup)
34
- return void res.status(409).json({ error: dupErrMsg });
35
34
  const id = generateId(idPrefix);
36
35
  const deadline = new Date(Date.now() + deadlineHours * 3600_000).toISOString();
37
- db.prepare(`INSERT INTO ${taskTable} (id, ${entityIdCol}, ${taskPartyCol}, claimant_id, claim_target, claim_text, evidence_uri, stake_claimant, deadline_at, status) VALUES (?,?,?,?,?,?,?,?,?,'open')`)
38
- .run(id, req.params.id, partyId, user.id, target, text, evidence, stake, deadline);
39
- db.prepare('UPDATE wallets SET balance = balance - ?, escrowed = escrowed + ? WHERE user_id = ?')
40
- .run(stake, stake, user.id);
36
+ // 质押/escrow 原子段(同步事务):dup + 钱包扣减(守恒 guard)+ INSERT 任务,
37
+ // 任一失败整段回滚 不会出现"任务已建但钱没锁"或"双重 open 声明"或透支。
38
+ try {
39
+ db.transaction(() => {
40
+ const dup = db.prepare(`SELECT id FROM ${taskTable} WHERE ${entityIdCol} = ? AND claimant_id = ? AND claim_target = ? AND status = 'open'`)
41
+ .get(req.params.id, user.id, target);
42
+ if (dup)
43
+ throw new Error('CLAIM_DUP');
44
+ const debit = db.prepare('UPDATE wallets SET balance = balance - ?, escrowed = escrowed + ? WHERE user_id = ? AND balance >= ?')
45
+ .run(stake, stake, user.id, stake);
46
+ if (debit.changes === 0)
47
+ throw new Error('CLAIM_INSUFFICIENT');
48
+ db.prepare(`INSERT INTO ${taskTable} (id, ${entityIdCol}, ${taskPartyCol}, claimant_id, claim_target, claim_text, evidence_uri, stake_claimant, deadline_at, status) VALUES (?,?,?,?,?,?,?,?,?,'open')`)
49
+ .run(id, req.params.id, partyId, user.id, target, text, evidence, stake, deadline);
50
+ })();
51
+ }
52
+ catch (e) {
53
+ const msg = e.message;
54
+ if (msg === 'CLAIM_DUP')
55
+ return void res.status(409).json({ error: dupErrMsg });
56
+ if (msg === 'CLAIM_INSUFFICIENT')
57
+ return void res.status(400).json({ error: `余额不足:发起需锁 ${stake} WAZ` });
58
+ console.error('[claim-initiators tx]', msg);
59
+ return void res.status(500).json({ error: '发起声明失败,请重试' });
60
+ }
41
61
  res.json({ success: true, claim_id: id, deadline_at: deadline, stake_locked: stake });
42
62
  });
43
- app.get(`/api/${entityPath}/:id/claims`, (req, res) => {
63
+ app.get(`/api/${entityPath}/:id/claims`, async (req, res) => {
44
64
  const sql = `
45
65
  SELECT ${a}.id, ${a}.claim_target, ${a}.claim_text, ${a}.evidence_uri, ${a}.status, ${a}.ruling, ${a}.deadline_at, ${a}.resolved_at, ${a}.created_at,
46
66
  u.name as claimant_name,
@@ -48,7 +68,7 @@ export function registerClaimInitiatorsRoutes(app, deps) {
48
68
  FROM ${taskTable} ${a} JOIN users u ON u.id = ${a}.claimant_id
49
69
  WHERE ${a}.${entityIdCol} = ? ORDER BY ${a}.created_at DESC LIMIT 50
50
70
  `;
51
- const rows = db.prepare(sql).all(req.params.id);
71
+ const rows = await dbAll(sql, [req.params.id]);
52
72
  res.json({ claims: rows, votes_needed: 3 });
53
73
  });
54
74
  };