@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,97 +1,102 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  const PAYMENT_METHOD_KINDS = new Set(['crypto_onchain', 'bank_wire', 'card', 'mobile_wallet', 'p2p']);
2
3
  const PAYMENT_METHOD_STATUSES = new Set(['active', 'preview', 'inactive', 'deprecated']);
3
4
  const RPM_DIRECTIONS = new Set(['deposit', 'withdraw', 'both']);
4
5
  const RPM_STATUSES = new Set(['active', 'paused', 'blocked']);
5
6
  export function registerPaymentsGovernanceRoutes(app, deps) {
6
- const { db, generateId, requireRootAdmin } = deps;
7
+ // db 已全量走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
8
+ const { generateId, requireRootAdmin } = deps;
7
9
  // 写支付变更审计日志
8
- function logPaymentChange(entity_kind, entity_id, action, oldValue, newValue, changed_by, reason) {
9
- db.prepare(`INSERT INTO payment_methods_log (entity_kind, entity_id, action, old_value, new_value, changed_by, reason) VALUES (?,?,?,?,?,?,?)`).run(entity_kind, entity_id, action, oldValue == null ? null : JSON.stringify(oldValue), newValue == null ? null : JSON.stringify(newValue), changed_by, reason ?? null);
10
+ async function logPaymentChange(entity_kind, entity_id, action, oldValue, newValue, changed_by, reason) {
11
+ await dbRun(`INSERT INTO payment_methods_log (entity_kind, entity_id, action, old_value, new_value, changed_by, reason) VALUES (?,?,?,?,?,?,?)`, [entity_kind, entity_id, action,
12
+ oldValue == null ? null : JSON.stringify(oldValue),
13
+ newValue == null ? null : JSON.stringify(newValue),
14
+ changed_by, reason ?? null]);
10
15
  }
11
16
  // ─── 治理参数 ────────────────────────────────────────────────
12
- app.get('/api/governance/params', (_req, res) => {
13
- const params = db.prepare(`
17
+ app.get('/api/governance/params', async (_req, res) => {
18
+ const params = await dbAll(`
14
19
  SELECT key, value, type, description, category, default_value, min_value, max_value, updated_at
15
20
  FROM protocol_params
16
21
  ORDER BY category, key
17
- `).all();
22
+ `);
18
23
  // 每个参数附最近 5 条变更
19
24
  for (const p of params) {
20
- const recent = db.prepare(`
25
+ const recent = await dbAll(`
21
26
  SELECT old_value, new_value, action, created_at
22
27
  FROM protocol_params_log
23
28
  WHERE key = ?
24
29
  ORDER BY id DESC LIMIT 5
25
- `).all(p.key);
30
+ `, [p.key]);
26
31
  p.recent_changes = recent;
27
32
  }
28
33
  res.json({
29
34
  notice: 'WebAZ 协议参数公示 — COP 团队自约束:所有参数变更必须可被任何人查询。',
30
35
  params,
31
- last_change: db.prepare(`SELECT key, old_value, new_value, action, created_at FROM protocol_params_log ORDER BY id DESC LIMIT 1`).get() || null,
36
+ last_change: (await dbOne(`SELECT key, old_value, new_value, action, created_at FROM protocol_params_log ORDER BY id DESC LIMIT 1`)) || null,
32
37
  });
33
38
  });
34
- app.get('/api/governance/params/:key/history', (req, res) => {
35
- const param = db.prepare(`SELECT * FROM protocol_params WHERE key = ?`).get(req.params.key);
39
+ app.get('/api/governance/params/:key/history', async (req, res) => {
40
+ const param = await dbOne(`SELECT * FROM protocol_params WHERE key = ?`, [req.params.key]);
36
41
  if (!param)
37
42
  return void res.status(404).json({ error: 'param not found' });
38
- const history = db.prepare(`
43
+ const history = await dbAll(`
39
44
  SELECT id, old_value, new_value, action, created_at,
40
45
  (SELECT name FROM users WHERE id = protocol_params_log.changed_by) as changed_by_name
41
46
  FROM protocol_params_log
42
47
  WHERE key = ?
43
48
  ORDER BY id DESC LIMIT 100
44
- `).all(req.params.key);
49
+ `, [req.params.key]);
45
50
  res.json({ param, history });
46
51
  });
47
52
  // ─── 公共支付方法 ───────────────────────────────────────────
48
- app.get('/api/payment-methods', (_req, res) => {
49
- const rows = db.prepare(`SELECT id, display_name, display_name_en, kind, asset, chain, contract_address, decimals, icon, status, watcher_status, notes
50
- FROM payment_methods WHERE status IN ('active','preview') ORDER BY status DESC, kind, asset`).all();
53
+ app.get('/api/payment-methods', async (_req, res) => {
54
+ const rows = await dbAll(`SELECT id, display_name, display_name_en, kind, asset, chain, contract_address, decimals, icon, status, watcher_status, notes
55
+ FROM payment_methods WHERE status IN ('active','preview') ORDER BY status DESC, kind, asset`);
51
56
  res.json({ items: rows });
52
57
  });
53
58
  // 某地区可用方法(fallback 到 global)
54
- app.get('/api/payment-methods/for-region', (req, res) => {
59
+ app.get('/api/payment-methods/for-region', async (req, res) => {
55
60
  const region = String(req.query.region || 'global');
56
61
  const direction = String(req.query.direction || ''); // 'deposit' | 'withdraw' | '' (任意)
57
62
  if (direction && !RPM_DIRECTIONS.has(direction)) {
58
63
  return void res.status(400).json({ error: `direction 必须是 ${[...RPM_DIRECTIONS].join(' / ')}` });
59
64
  }
60
- const rowsRegion = db.prepare(`
65
+ const rowsRegion = await dbAll(`
61
66
  SELECT rpm.region, rpm.method_id, rpm.direction, rpm.status, rpm.min_amount, rpm.max_amount, rpm.daily_cap, rpm.notes,
62
67
  pm.display_name, pm.display_name_en, pm.kind, pm.asset, pm.chain, pm.icon, pm.status as method_status, pm.watcher_status
63
68
  FROM region_payment_methods rpm JOIN payment_methods pm ON pm.id = rpm.method_id
64
69
  WHERE rpm.region = ? AND rpm.status = 'active' AND pm.status IN ('active','preview')
65
- `).all(region);
70
+ `, [region]);
66
71
  const useFallback = rowsRegion.length === 0 && region !== 'global';
67
72
  const finalRegion = useFallback ? 'global' : region;
68
- const rows = useFallback ? db.prepare(`
73
+ const rows = useFallback ? await dbAll(`
69
74
  SELECT rpm.region, rpm.method_id, rpm.direction, rpm.status, rpm.min_amount, rpm.max_amount, rpm.daily_cap, rpm.notes,
70
75
  pm.display_name, pm.display_name_en, pm.kind, pm.asset, pm.chain, pm.icon, pm.status as method_status, pm.watcher_status
71
76
  FROM region_payment_methods rpm JOIN payment_methods pm ON pm.id = rpm.method_id
72
77
  WHERE rpm.region = 'global' AND rpm.status = 'active' AND pm.status IN ('active','preview')
73
- `).all() : rowsRegion;
78
+ `) : rowsRegion;
74
79
  const filtered = direction
75
80
  ? rows.filter(r => r.direction === direction || r.direction === 'both')
76
81
  : rows;
77
82
  res.json({ region: finalRegion, fallback_from: useFallback ? region : null, items: filtered });
78
83
  });
79
84
  // 公共变更审计日志(COP transparency)
80
- app.get('/api/payment-methods/log', (req, res) => {
85
+ app.get('/api/payment-methods/log', async (req, res) => {
81
86
  const limit = Math.min(200, Math.max(10, Number(req.query.limit) || 50));
82
- const rows = db.prepare(`SELECT id, entity_kind, entity_id, action, old_value, new_value, changed_by, reason, created_at
83
- FROM payment_methods_log ORDER BY id DESC LIMIT ?`).all(limit);
87
+ const rows = await dbAll(`SELECT id, entity_kind, entity_id, action, old_value, new_value, changed_by, reason, created_at
88
+ FROM payment_methods_log ORDER BY id DESC LIMIT ?`, [limit]);
84
89
  res.json({ items: rows });
85
90
  });
86
91
  // ─── Admin payment_methods CRUD(root admin only · 基础设施变更需根权限)─
87
- app.get('/api/admin/payment-methods', (req, res) => {
92
+ app.get('/api/admin/payment-methods', async (req, res) => {
88
93
  const user = requireRootAdmin(req, res);
89
94
  if (!user)
90
95
  return;
91
- const rows = db.prepare(`SELECT * FROM payment_methods ORDER BY status DESC, kind, asset`).all();
96
+ const rows = await dbAll(`SELECT * FROM payment_methods ORDER BY status DESC, kind, asset`);
92
97
  res.json({ items: rows });
93
98
  });
94
- app.post('/api/admin/payment-methods', (req, res) => {
99
+ app.post('/api/admin/payment-methods', async (req, res) => {
95
100
  const user = requireRootAdmin(req, res);
96
101
  if (!user)
97
102
  return;
@@ -99,7 +104,7 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
99
104
  const id = String(b.id || '').trim().toLowerCase();
100
105
  if (!/^[a-z0-9_]{3,40}$/.test(id))
101
106
  return void res.status(400).json({ error: 'id 必须是 3-40 位 [a-z0-9_](如 usdc_base)' });
102
- if (db.prepare(`SELECT 1 FROM payment_methods WHERE id = ?`).get(id))
107
+ if (await dbOne(`SELECT 1 FROM payment_methods WHERE id = ?`, [id]))
103
108
  return void res.status(409).json({ error: 'id 已存在' });
104
109
  const display_name = String(b.display_name || '').trim();
105
110
  if (display_name.length < 1 || display_name.length > 60)
@@ -122,18 +127,18 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
122
127
  return void res.status(400).json({ error: `status 必须是 ${[...PAYMENT_METHOD_STATUSES].join(' / ')}` });
123
128
  const notes = b.notes ? String(b.notes).slice(0, 200) : null;
124
129
  const newRow = { id, display_name, display_name_en, kind, asset, chain, contract_address, decimals, icon, status, watcher_status: 'unconfigured', notes };
125
- db.prepare(`INSERT INTO payment_methods (
130
+ await dbRun(`INSERT INTO payment_methods (
126
131
  id, display_name, display_name_en, kind, asset, chain, contract_address, decimals, icon, status, watcher_status, notes, updated_by
127
- ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(id, display_name, display_name_en, kind, asset, chain, contract_address, decimals, icon, status, 'unconfigured', notes, user.id);
128
- logPaymentChange('method', id, 'create', null, newRow, user.id, String(b.reason || ''));
132
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`, [id, display_name, display_name_en, kind, asset, chain, contract_address, decimals, icon, status, 'unconfigured', notes, user.id]);
133
+ await logPaymentChange('method', id, 'create', null, newRow, user.id, String(b.reason || ''));
129
134
  res.json({ ok: true, id });
130
135
  });
131
- app.put('/api/admin/payment-methods/:id', (req, res) => {
136
+ app.put('/api/admin/payment-methods/:id', async (req, res) => {
132
137
  const user = requireRootAdmin(req, res);
133
138
  if (!user)
134
139
  return;
135
140
  const id = req.params.id;
136
- const existing = db.prepare(`SELECT * FROM payment_methods WHERE id = ?`).get(id);
141
+ const existing = await dbOne(`SELECT * FROM payment_methods WHERE id = ?`, [id]);
137
142
  if (!existing)
138
143
  return void res.status(404).json({ error: 'not_found' });
139
144
  const b = req.body;
@@ -175,29 +180,29 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
175
180
  return void res.status(400).json({ error: '无更新字段' });
176
181
  const cols = Object.keys(updates).map(k => `${k} = ?`).join(', ');
177
182
  const vals = Object.values(updates);
178
- db.prepare(`UPDATE payment_methods SET ${cols}, updated_at = datetime('now'), updated_by = ? WHERE id = ?`).run(...vals, user.id, id);
179
- logPaymentChange('method', id, 'update', existing, { ...existing, ...updates }, user.id, String(b.reason || ''));
183
+ await dbRun(`UPDATE payment_methods SET ${cols}, updated_at = datetime('now'), updated_by = ? WHERE id = ?`, [...vals, user.id, id]);
184
+ await logPaymentChange('method', id, 'update', existing, { ...existing, ...updates }, user.id, String(b.reason || ''));
180
185
  res.json({ ok: true });
181
186
  });
182
- app.delete('/api/admin/payment-methods/:id', (req, res) => {
187
+ app.delete('/api/admin/payment-methods/:id', async (req, res) => {
183
188
  const user = requireRootAdmin(req, res);
184
189
  if (!user)
185
190
  return;
186
191
  const id = req.params.id;
187
192
  if (id === 'usdc_base')
188
193
  return void res.status(400).json({ error: '默认协议方法 usdc_base 不可删除,可改为 deprecated 状态' });
189
- const existing = db.prepare(`SELECT * FROM payment_methods WHERE id = ?`).get(id);
194
+ const existing = await dbOne(`SELECT * FROM payment_methods WHERE id = ?`, [id]);
190
195
  if (!existing)
191
196
  return void res.status(404).json({ error: 'not_found' });
192
- const refs = db.prepare(`SELECT COUNT(*) as n FROM region_payment_methods WHERE method_id = ? AND status = 'active'`).get(id).n;
197
+ const refs = (await dbOne(`SELECT COUNT(*) as n FROM region_payment_methods WHERE method_id = ? AND status = 'active'`, [id])).n;
193
198
  if (refs > 0)
194
199
  return void res.status(409).json({ error: `还有 ${refs} 条 active 区域映射引用该方法,请先停用` });
195
- db.prepare(`DELETE FROM payment_methods WHERE id = ?`).run(id);
196
- logPaymentChange('method', id, 'delete', existing, null, user.id, String((req.body || {}).reason || ''));
200
+ await dbRun(`DELETE FROM payment_methods WHERE id = ?`, [id]);
201
+ await logPaymentChange('method', id, 'delete', existing, null, user.id, String((req.body || {}).reason || ''));
197
202
  res.json({ ok: true });
198
203
  });
199
204
  // ─── region_payment_methods CRUD ──────────────────────────
200
- app.get('/api/admin/region-payment-methods', (req, res) => {
205
+ app.get('/api/admin/region-payment-methods', async (req, res) => {
201
206
  const user = requireRootAdmin(req, res);
202
207
  if (!user)
203
208
  return;
@@ -212,15 +217,15 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
212
217
  params.push(String(req.query.method_id));
213
218
  }
214
219
  const whereSql = where.length ? `WHERE ${where.join(' AND ')}` : '';
215
- const rows = db.prepare(`
220
+ const rows = await dbAll(`
216
221
  SELECT rpm.*, pm.display_name, pm.display_name_en, pm.icon, pm.asset, pm.chain
217
222
  FROM region_payment_methods rpm JOIN payment_methods pm ON pm.id = rpm.method_id
218
223
  ${whereSql}
219
224
  ORDER BY rpm.region, pm.kind, pm.asset
220
- `).all(...params);
225
+ `, params);
221
226
  res.json({ items: rows });
222
227
  });
223
- app.post('/api/admin/region-payment-methods', (req, res) => {
228
+ app.post('/api/admin/region-payment-methods', async (req, res) => {
224
229
  const user = requireRootAdmin(req, res);
225
230
  if (!user)
226
231
  return;
@@ -229,7 +234,7 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
229
234
  if (!/^[a-z_]{2,20}$/.test(region))
230
235
  return void res.status(400).json({ error: 'region 必须是 2-20 位 [a-z_]' });
231
236
  const method_id = String(b.method_id || '');
232
- if (!db.prepare(`SELECT 1 FROM payment_methods WHERE id = ?`).get(method_id))
237
+ if (!(await dbOne(`SELECT 1 FROM payment_methods WHERE id = ?`, [method_id])))
233
238
  return void res.status(404).json({ error: 'method_id 不存在' });
234
239
  const direction = String(b.direction || 'both');
235
240
  if (!RPM_DIRECTIONS.has(direction))
@@ -245,24 +250,24 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
245
250
  const notes = b.notes ? String(b.notes).slice(0, 200) : null;
246
251
  const id = generateId('rpm');
247
252
  try {
248
- db.prepare(`INSERT INTO region_payment_methods (
253
+ await dbRun(`INSERT INTO region_payment_methods (
249
254
  id, region, method_id, direction, status, min_amount, max_amount, daily_cap, notes, updated_by
250
- ) VALUES (?,?,?,?,?,?,?,?,?,?)`).run(id, region, method_id, direction, status, min_amount, max_amount, daily_cap, notes, user.id);
255
+ ) VALUES (?,?,?,?,?,?,?,?,?,?)`, [id, region, method_id, direction, status, min_amount, max_amount, daily_cap, notes, user.id]);
251
256
  }
252
257
  catch (e) {
253
258
  if (String(e).includes('UNIQUE'))
254
259
  return void res.status(409).json({ error: '同一 region + method + direction 已存在' });
255
260
  throw e;
256
261
  }
257
- logPaymentChange('region_mapping', id, 'create', null, { region, method_id, direction, status, min_amount, max_amount, daily_cap, notes }, user.id, String(b.reason || ''));
262
+ await logPaymentChange('region_mapping', id, 'create', null, { region, method_id, direction, status, min_amount, max_amount, daily_cap, notes }, user.id, String(b.reason || ''));
258
263
  res.json({ ok: true, id });
259
264
  });
260
- app.put('/api/admin/region-payment-methods/:id', (req, res) => {
265
+ app.put('/api/admin/region-payment-methods/:id', async (req, res) => {
261
266
  const user = requireRootAdmin(req, res);
262
267
  if (!user)
263
268
  return;
264
269
  const id = req.params.id;
265
- const existing = db.prepare(`SELECT * FROM region_payment_methods WHERE id = ?`).get(id);
270
+ const existing = await dbOne(`SELECT * FROM region_payment_methods WHERE id = ?`, [id]);
266
271
  if (!existing)
267
272
  return void res.status(404).json({ error: 'not_found' });
268
273
  const b = req.body;
@@ -289,23 +294,23 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
289
294
  return void res.status(400).json({ error: 'min_amount 不能大于 max_amount' });
290
295
  const cols = Object.keys(updates).map(k => `${k} = ?`).join(', ');
291
296
  const vals = Object.values(updates);
292
- db.prepare(`UPDATE region_payment_methods SET ${cols}, updated_at = datetime('now'), updated_by = ? WHERE id = ?`).run(...vals, user.id, id);
293
- logPaymentChange('region_mapping', id, 'update', existing, { ...existing, ...updates }, user.id, String(b.reason || ''));
297
+ await dbRun(`UPDATE region_payment_methods SET ${cols}, updated_at = datetime('now'), updated_by = ? WHERE id = ?`, [...vals, user.id, id]);
298
+ await logPaymentChange('region_mapping', id, 'update', existing, { ...existing, ...updates }, user.id, String(b.reason || ''));
294
299
  res.json({ ok: true });
295
300
  });
296
- app.delete('/api/admin/region-payment-methods/:id', (req, res) => {
301
+ app.delete('/api/admin/region-payment-methods/:id', async (req, res) => {
297
302
  const user = requireRootAdmin(req, res);
298
303
  if (!user)
299
304
  return;
300
305
  const id = req.params.id;
301
- const existing = db.prepare(`SELECT * FROM region_payment_methods WHERE id = ?`).get(id);
306
+ const existing = await dbOne(`SELECT * FROM region_payment_methods WHERE id = ?`, [id]);
302
307
  if (!existing)
303
308
  return void res.status(404).json({ error: 'not_found' });
304
309
  if (existing.region === 'global' && existing.method_id === 'usdc_base') {
305
310
  return void res.status(400).json({ error: '默认协议映射 global × usdc_base 不可删除' });
306
311
  }
307
- db.prepare(`DELETE FROM region_payment_methods WHERE id = ?`).run(id);
308
- logPaymentChange('region_mapping', id, 'delete', existing, null, user.id, String((req.body || {}).reason || ''));
312
+ await dbRun(`DELETE FROM region_payment_methods WHERE id = ?`, [id]);
313
+ await logPaymentChange('region_mapping', id, 'delete', existing, null, user.id, String((req.body || {}).reason || ''));
309
314
  res.json({ ok: true });
310
315
  });
311
316
  }
@@ -1,6 +1,8 @@
1
+ import { dbOne, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerPeersRoutes(app, deps) {
2
- const { db, auth } = deps;
3
- app.post('/api/peers/heartbeat', (req, res) => {
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbRun),不再直接用 deps.db
4
+ const { auth } = deps;
5
+ app.post('/api/peers/heartbeat', async (req, res) => {
4
6
  const me = auth(req, res);
5
7
  if (!me)
6
8
  return;
@@ -11,24 +13,23 @@ export function registerPeersRoutes(app, deps) {
11
13
  for (const h of hashes) {
12
14
  if (typeof h !== 'string' || !/^[a-f0-9]{64}$/.test(h))
13
15
  continue;
14
- const m = db.prepare("SELECT owner_id, status FROM manifest_registry WHERE hash = ?").get(h);
16
+ const m = await dbOne("SELECT owner_id, status FROM manifest_registry WHERE hash = ?", [h]);
15
17
  if (!m || m.status !== 'active')
16
18
  continue;
17
19
  const isOwner = m.owner_id === me.id ? 1 : 0;
18
20
  const pinIntent = pinIntents.has(h) ? 1 : 0;
19
- db.prepare(`INSERT INTO peer_directory (peer_id, manifest_hash, is_owner, pin_intent, last_heartbeat)
21
+ await dbRun(`INSERT INTO peer_directory (peer_id, manifest_hash, is_owner, pin_intent, last_heartbeat)
20
22
  VALUES (?,?,?,?,?)
21
- ON CONFLICT(peer_id, manifest_hash) DO UPDATE SET is_owner=excluded.is_owner, pin_intent=excluded.pin_intent, last_heartbeat=excluded.last_heartbeat`)
22
- .run(me.id, h, isOwner, pinIntent, now);
23
+ ON CONFLICT(peer_id, manifest_hash) DO UPDATE SET is_owner=excluded.is_owner, pin_intent=excluded.pin_intent, last_heartbeat=excluded.last_heartbeat`, [me.id, h, isOwner, pinIntent, now]);
23
24
  registered++;
24
25
  }
25
26
  res.json({ ok: true, registered });
26
27
  });
27
- app.delete('/api/peers/:hash', (req, res) => {
28
+ app.delete('/api/peers/:hash', async (req, res) => {
28
29
  const me = auth(req, res);
29
30
  if (!me)
30
31
  return;
31
- db.prepare("DELETE FROM peer_directory WHERE peer_id = ? AND manifest_hash = ?").run(me.id, req.params.hash);
32
+ await dbRun("DELETE FROM peer_directory WHERE peer_id = ? AND manifest_hash = ?", [me.id, req.params.hash]);
32
33
  res.json({ ok: true });
33
34
  });
34
35
  }
@@ -1,6 +1,8 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerPinReceiptsRoutes(app, deps) {
2
- const { db, auth, generateId } = deps;
3
- app.post('/api/pin-receipts', (req, res) => {
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
4
+ const { auth, generateId } = deps;
5
+ app.post('/api/pin-receipts', async (req, res) => {
4
6
  const me = auth(req, res);
5
7
  if (!me)
6
8
  return;
@@ -13,27 +15,25 @@ export function registerPinReceiptsRoutes(app, deps) {
13
15
  }
14
16
  if (pinner_id === me.id)
15
17
  return void res.json({ error: 'pinner 不能是自己' });
16
- const m = db.prepare("SELECT 1 FROM manifest_registry WHERE hash = ? AND status = 'active'").get(manifest_hash);
18
+ const m = await dbOne("SELECT 1 FROM manifest_registry WHERE hash = ? AND status = 'active'", [manifest_hash]);
17
19
  if (!m)
18
20
  return void res.json({ error: 'manifest 不存在或已下架' });
19
- const dup = db.prepare(`SELECT id FROM pin_receipts WHERE manifest_hash = ? AND pinner_id = ? AND recipient_id = ? AND served_at > datetime('now', '-1 day')`).get(manifest_hash, pinner_id, me.id);
21
+ const dup = await dbOne(`SELECT id FROM pin_receipts WHERE manifest_hash = ? AND pinner_id = ? AND recipient_id = ? AND served_at > datetime('now', '-1 day')`, [manifest_hash, pinner_id, me.id]);
20
22
  if (dup)
21
23
  return void res.json({ error: '24 小时内已有相同 pin 回执' });
22
24
  const id = generateId('pin');
23
- db.prepare(`INSERT INTO pin_receipts (id, manifest_hash, pinner_id, recipient_id, bytes_served, served_at, pinner_sig, recipient_sig)
24
- VALUES (?,?,?,?,?,?,?,?)`)
25
- .run(id, manifest_hash, pinner_id, me.id, bytes_served, served_at, pinner_sig, recipient_sig);
26
- db.prepare(`UPDATE peer_directory SET bytes_served_total = bytes_served_total + ? WHERE peer_id = ? AND manifest_hash = ?`)
27
- .run(bytes_served, pinner_id, manifest_hash);
25
+ await dbRun(`INSERT INTO pin_receipts (id, manifest_hash, pinner_id, recipient_id, bytes_served, served_at, pinner_sig, recipient_sig)
26
+ VALUES (?,?,?,?,?,?,?,?)`, [id, manifest_hash, pinner_id, me.id, bytes_served, served_at, pinner_sig, recipient_sig]);
27
+ await dbRun(`UPDATE peer_directory SET bytes_served_total = bytes_served_total + ? WHERE peer_id = ? AND manifest_hash = ?`, [bytes_served, pinner_id, manifest_hash]);
28
28
  res.json({ ok: true, id });
29
29
  });
30
- app.get('/api/pin-receipts/mine', (req, res) => {
30
+ app.get('/api/pin-receipts/mine', async (req, res) => {
31
31
  const me = auth(req, res);
32
32
  if (!me)
33
33
  return;
34
- const earned = db.prepare(`SELECT COALESCE(SUM(rewarded_waz), 0) as total, COUNT(*) as count FROM pin_receipts WHERE pinner_id = ? AND rewarded_at IS NOT NULL`).get(me.id);
35
- const pending = db.prepare(`SELECT COUNT(*) as n FROM pin_receipts WHERE pinner_id = ? AND rewarded_at IS NULL`).get(me.id);
36
- const recent = db.prepare(`SELECT p.*, m.title as manifest_title FROM pin_receipts p LEFT JOIN manifest_registry m ON m.hash = p.manifest_hash WHERE p.pinner_id = ? ORDER BY p.served_at DESC LIMIT 20`).all(me.id);
34
+ const earned = (await dbOne(`SELECT COALESCE(SUM(rewarded_waz), 0) as total, COUNT(*) as count FROM pin_receipts WHERE pinner_id = ? AND rewarded_at IS NOT NULL`, [me.id]));
35
+ const pending = (await dbOne(`SELECT COUNT(*) as n FROM pin_receipts WHERE pinner_id = ? AND rewarded_at IS NULL`, [me.id]));
36
+ const recent = await dbAll(`SELECT p.*, m.title as manifest_title FROM pin_receipts p LEFT JOIN manifest_registry m ON m.hash = p.manifest_hash WHERE p.pinner_id = ? ORDER BY p.served_at DESC LIMIT 20`, [me.id]);
37
37
  res.json({ earned, pending_count: pending.n, recent });
38
38
  });
39
39
  }
@@ -1,4 +1,7 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerProductsAliasesRoutes(app, deps) {
3
+ // db 仍保留:用于 POST /aliases 的 db.transaction(TOCTOU 防护,better-sqlite3 事务须同步)。
4
+ // 其余只读/单写站点已走 RFC-016 异步 seam(dbOne/dbAll/dbRun)。
2
5
  const { db, auth, generateId, extractCandidateAliases } = deps;
3
6
  // M7.2-5: 从外部原文提取候选 alias
4
7
  app.post('/api/products/extract-aliases', (req, res) => {
@@ -14,24 +17,24 @@ export function registerProductsAliasesRoutes(app, deps) {
14
17
  res.json({ candidates, hint: '勾选要声明为该商品 alias 的项目(≥ 6 字符;过短或过通用的项会被反作弊机制挑战)' });
15
18
  });
16
19
  // M7.2-7: alias CRUD(仅商品 owner)
17
- app.get('/api/products/:id/aliases', (req, res) => {
20
+ app.get('/api/products/:id/aliases', async (req, res) => {
18
21
  const user = auth(req, res);
19
22
  if (!user)
20
23
  return;
21
- const p = db.prepare('SELECT seller_id FROM products WHERE id = ?').get(req.params.id);
24
+ const p = await dbOne('SELECT seller_id FROM products WHERE id = ?', [req.params.id]);
22
25
  if (!p)
23
26
  return void res.status(404).json({ error: '商品不存在' });
24
27
  if (p.seller_id !== user.id)
25
28
  return void res.status(403).json({ error: '仅商品 owner 可查看 alias' });
26
- const rows = db.prepare(`SELECT id, alias_type, alias_value, min_match_chars, status, challenged_at, created_at
27
- FROM product_aliases WHERE product_id = ? ORDER BY created_at DESC`).all(req.params.id);
29
+ const rows = await dbAll(`SELECT id, alias_type, alias_value, min_match_chars, status, challenged_at, created_at
30
+ FROM product_aliases WHERE product_id = ? ORDER BY created_at DESC`, [req.params.id]);
28
31
  res.json({ aliases: rows });
29
32
  });
30
- app.post('/api/products/:id/aliases', (req, res) => {
33
+ app.post('/api/products/:id/aliases', async (req, res) => {
31
34
  const user = auth(req, res);
32
35
  if (!user)
33
36
  return;
34
- const p = db.prepare('SELECT seller_id, title FROM products WHERE id = ?').get(req.params.id);
37
+ const p = await dbOne('SELECT seller_id, title FROM products WHERE id = ?', [req.params.id]);
35
38
  if (!p)
36
39
  return void res.status(404).json({ error: '商品不存在' });
37
40
  if (p.seller_id !== user.id)
@@ -103,17 +106,16 @@ export function registerProductsAliasesRoutes(app, deps) {
103
106
  }
104
107
  res.json({ inserted: inserted.length, skipped });
105
108
  });
106
- app.delete('/api/products/:id/aliases/:aliasId', (req, res) => {
109
+ app.delete('/api/products/:id/aliases/:aliasId', async (req, res) => {
107
110
  const user = auth(req, res);
108
111
  if (!user)
109
112
  return;
110
- const p = db.prepare('SELECT seller_id FROM products WHERE id = ?').get(req.params.id);
113
+ const p = await dbOne('SELECT seller_id FROM products WHERE id = ?', [req.params.id]);
111
114
  if (!p)
112
115
  return void res.status(404).json({ error: '商品不存在' });
113
116
  if (p.seller_id !== user.id)
114
117
  return void res.status(403).json({ error: '仅商品 owner 可删除 alias' });
115
- const r = db.prepare(`UPDATE product_aliases SET status = 'revoked' WHERE id = ? AND product_id = ?`)
116
- .run(req.params.aliasId, req.params.id);
118
+ const r = await dbRun(`UPDATE product_aliases SET status = 'revoked' WHERE id = ? AND product_id = ?`, [req.params.aliasId, req.params.id]);
117
119
  res.json({ success: r.changes > 0 });
118
120
  });
119
121
  }
@@ -1,6 +1,9 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerProductsClaimsRoutes(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, PRODUCT_CLAIM_TARGETS, PRODUCT_CLAIM_STAKE_DEFAULT, PRODUCT_CLAIM_DEADLINE_HOURS, PRODUCT_CLAIM_VERIFIERS_NEEDED } = deps;
3
- app.post('/api/products/:id/claim', (req, res) => {
6
+ app.post('/api/products/:id/claim', async (req, res) => {
4
7
  const user = auth(req, res);
5
8
  if (!user)
6
9
  return;
@@ -8,7 +11,7 @@ export function registerProductsClaimsRoutes(app, deps) {
8
11
  if (isTrustedRole(user)) {
9
12
  return void errorRes(res, 403, 'TRUSTED_ROLE_NO_CLAIM', '受信角色不可发起商品声明');
10
13
  }
11
- const product = db.prepare('SELECT * FROM products WHERE id = ?').get(req.params.id);
14
+ const product = await dbOne('SELECT * FROM products WHERE id = ?', [req.params.id]);
12
15
  if (!product)
13
16
  return void res.status(404).json({ error: '商品不存在' });
14
17
  if (product.seller_id === user.id)
@@ -24,29 +27,45 @@ export function registerProductsClaimsRoutes(app, deps) {
24
27
  return void res.status(400).json({ error: 'claim_text 长度需 6-500 字' });
25
28
  }
26
29
  const evidence_uri = req.body?.evidence_uri ? String(req.body.evidence_uri).trim().slice(0, 500) : null;
27
- 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]);
28
32
  const stake = PRODUCT_CLAIM_STAKE_DEFAULT;
29
33
  if (!wallet || wallet.balance < stake) {
30
34
  return void res.status(400).json({ error: `余额不足:发起需锁 ${stake} WAZ,当前 ${wallet?.balance ?? 0} WAZ` });
31
35
  }
32
- // 同用户对同商品同 target 只能挂一个 open claim
33
- const dup = db.prepare(`SELECT id FROM product_claim_tasks WHERE product_id = ? AND claimant_id = ? AND claim_target = ? AND status = 'open'`)
34
- .get(req.params.id, user.id, claim_target);
35
- if (dup)
36
- return void res.status(409).json({ error: '你已对此商品的同一项发起过 open 声明' });
37
36
  const id = generateId('pct');
38
37
  const deadline = new Date(Date.now() + PRODUCT_CLAIM_DEADLINE_HOURS * 3600_000).toISOString();
39
- db.prepare(`INSERT INTO product_claim_tasks
40
- (id, product_id, claimant_id, seller_id, claim_target, claim_text, evidence_uri, stake_claimant, deadline_at, status)
41
- VALUES (?,?,?,?,?,?,?,?,?,'open')`)
42
- .run(id, req.params.id, user.id, product.seller_id, claim_target, claim_text, evidence_uri, stake, deadline);
43
- db.prepare('UPDATE wallets SET balance = balance - ?, escrowed = escrowed + ? WHERE user_id = ?')
44
- .run(stake, stake, user.id);
38
+ // 质押/escrow 原子段(同步事务):dup 门 + 钱包扣减(守恒 guard)+ INSERT 任务。
39
+ try {
40
+ db.transaction(() => {
41
+ const dup = db.prepare(`SELECT id FROM product_claim_tasks WHERE product_id = ? AND claimant_id = ? AND claim_target = ? AND status = 'open'`)
42
+ .get(req.params.id, user.id, claim_target);
43
+ if (dup)
44
+ throw new Error('CLAIM_DUP');
45
+ const debit = db.prepare('UPDATE wallets SET balance = balance - ?, escrowed = escrowed + ? WHERE user_id = ? AND balance >= ?')
46
+ .run(stake, stake, user.id, stake);
47
+ if (debit.changes === 0)
48
+ throw new Error('CLAIM_INSUFFICIENT');
49
+ db.prepare(`INSERT INTO product_claim_tasks
50
+ (id, product_id, claimant_id, seller_id, claim_target, claim_text, evidence_uri, stake_claimant, deadline_at, status)
51
+ VALUES (?,?,?,?,?,?,?,?,?,'open')`)
52
+ .run(id, req.params.id, user.id, product.seller_id, claim_target, claim_text, evidence_uri, stake, deadline);
53
+ })();
54
+ }
55
+ catch (e) {
56
+ const msg = e.message;
57
+ if (msg === 'CLAIM_DUP')
58
+ return void res.status(409).json({ error: '你已对此商品的同一项发起过 open 声明' });
59
+ if (msg === 'CLAIM_INSUFFICIENT')
60
+ return void res.status(400).json({ error: `余额不足:发起需锁 ${stake} WAZ` });
61
+ console.error('[products-claims tx]', msg);
62
+ return void res.status(500).json({ error: '发起声明失败,请重试' });
63
+ }
45
64
  res.json({ success: true, claim_id: id, deadline_at: deadline, stake_locked: stake });
46
65
  });
47
66
  // 公开:列出某商品的全部声明(含已结算)
48
- app.get('/api/products/:id/claims', (req, res) => {
49
- const rows = db.prepare(`
67
+ app.get('/api/products/:id/claims', async (req, res) => {
68
+ const rows = await dbAll(`
50
69
  SELECT pct.id, pct.claim_target, pct.claim_text, pct.evidence_uri, pct.status, pct.ruling, pct.deadline_at, pct.resolved_at, pct.created_at,
51
70
  u.name as claimant_name,
52
71
  (SELECT COUNT(*) FROM product_claim_votes WHERE claim_id = pct.id) as votes_count
@@ -54,7 +73,7 @@ export function registerProductsClaimsRoutes(app, deps) {
54
73
  JOIN users u ON u.id = pct.claimant_id
55
74
  WHERE pct.product_id = ?
56
75
  ORDER BY pct.created_at DESC LIMIT 50
57
- `).all(req.params.id);
76
+ `, [req.params.id]);
58
77
  res.json({ claims: rows, votes_needed: PRODUCT_CLAIM_VERIFIERS_NEEDED });
59
78
  });
60
79
  }