@seasonkoh/webaz 0.1.24 → 0.1.25

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 (187) hide show
  1. package/README.md +2 -0
  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 +165 -64
  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 +173 -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 +10 -2
  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-engine.js +109 -0
  35. package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
  36. package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
  37. package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
  38. package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
  39. package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
  40. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
  41. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
  42. package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
  43. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
  44. package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
  45. package/dist/pwa/acp-feed.js +13 -1
  46. package/dist/pwa/contract-fingerprint.js +2 -0
  47. package/dist/pwa/endpoint-actions.js +5 -1
  48. package/dist/pwa/goal-index.js +8 -8
  49. package/dist/pwa/human-presence.js +62 -0
  50. package/dist/pwa/public/app.js +575 -68
  51. package/dist/pwa/public/i18n.js +29 -20
  52. package/dist/pwa/public/index.html +1 -0
  53. package/dist/pwa/public/openapi.json +2 -2
  54. package/dist/pwa/rate-limit.js +22 -0
  55. package/dist/pwa/routes/account-deletion.js +15 -13
  56. package/dist/pwa/routes/addresses.js +10 -9
  57. package/dist/pwa/routes/admin-admins.js +13 -14
  58. package/dist/pwa/routes/admin-analytics.js +109 -69
  59. package/dist/pwa/routes/admin-catalog.js +13 -11
  60. package/dist/pwa/routes/admin-editor-picks.js +15 -10
  61. package/dist/pwa/routes/admin-events.js +5 -3
  62. package/dist/pwa/routes/admin-health.js +2 -1
  63. package/dist/pwa/routes/admin-moderation.js +26 -29
  64. package/dist/pwa/routes/admin-ops.js +22 -21
  65. package/dist/pwa/routes/admin-protocol-params.js +16 -19
  66. package/dist/pwa/routes/admin-reports.js +23 -21
  67. package/dist/pwa/routes/admin-tokenomics.js +26 -25
  68. package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
  69. package/dist/pwa/routes/admin-users-query.js +54 -53
  70. package/dist/pwa/routes/admin-verifier-flow.js +82 -41
  71. package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
  72. package/dist/pwa/routes/admin-wallet-ops.js +7 -5
  73. package/dist/pwa/routes/agent-buy.js +46 -22
  74. package/dist/pwa/routes/agent-governance.js +52 -56
  75. package/dist/pwa/routes/ai.js +7 -5
  76. package/dist/pwa/routes/analytics.js +43 -41
  77. package/dist/pwa/routes/anchors.js +19 -20
  78. package/dist/pwa/routes/announcements.js +13 -13
  79. package/dist/pwa/routes/arbitrator.js +97 -31
  80. package/dist/pwa/routes/auction.js +153 -114
  81. package/dist/pwa/routes/auth-login.js +6 -4
  82. package/dist/pwa/routes/auth-read.js +11 -9
  83. package/dist/pwa/routes/auth-register.js +35 -20
  84. package/dist/pwa/routes/auth-sessions.js +12 -11
  85. package/dist/pwa/routes/blocklist.js +16 -15
  86. package/dist/pwa/routes/build-feedback.js +10 -9
  87. package/dist/pwa/routes/build-reputation.js +6 -2
  88. package/dist/pwa/routes/build-tasks.js +45 -13
  89. package/dist/pwa/routes/buyer-feeds.js +27 -25
  90. package/dist/pwa/routes/cart.js +16 -15
  91. package/dist/pwa/routes/charity.js +212 -150
  92. package/dist/pwa/routes/chat.js +42 -43
  93. package/dist/pwa/routes/checkin-tasks.js +10 -9
  94. package/dist/pwa/routes/checkout-helpers.js +12 -10
  95. package/dist/pwa/routes/claim-initiators.js +34 -14
  96. package/dist/pwa/routes/claim-verify.js +86 -53
  97. package/dist/pwa/routes/claim-voting.js +43 -18
  98. package/dist/pwa/routes/contribution-identity.js +147 -0
  99. package/dist/pwa/routes/contribution-score.js +19 -0
  100. package/dist/pwa/routes/coupons.js +19 -16
  101. package/dist/pwa/routes/dashboards.js +18 -16
  102. package/dist/pwa/routes/dispute-cases.js +25 -24
  103. package/dist/pwa/routes/disputes-read.js +45 -51
  104. package/dist/pwa/routes/disputes-write.js +124 -61
  105. package/dist/pwa/routes/evidence.js +9 -9
  106. package/dist/pwa/routes/external-anchors.js +13 -12
  107. package/dist/pwa/routes/feedback.js +29 -33
  108. package/dist/pwa/routes/flash-sales.js +18 -16
  109. package/dist/pwa/routes/follows.js +25 -24
  110. package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
  111. package/dist/pwa/routes/governance-onboarding.js +70 -59
  112. package/dist/pwa/routes/group-buys.js +22 -22
  113. package/dist/pwa/routes/growth.js +33 -30
  114. package/dist/pwa/routes/import-product.js +12 -10
  115. package/dist/pwa/routes/kyc.js +9 -8
  116. package/dist/pwa/routes/leaderboard.js +20 -18
  117. package/dist/pwa/routes/listings.js +23 -22
  118. package/dist/pwa/routes/logistics.js +10 -8
  119. package/dist/pwa/routes/manifests.js +27 -27
  120. package/dist/pwa/routes/me-data.js +23 -21
  121. package/dist/pwa/routes/notifications.js +7 -6
  122. package/dist/pwa/routes/offers.js +30 -12
  123. package/dist/pwa/routes/orders-action.js +33 -17
  124. package/dist/pwa/routes/orders-create.js +75 -20
  125. package/dist/pwa/routes/orders-read.js +21 -20
  126. package/dist/pwa/routes/p2p-products.js +30 -18
  127. package/dist/pwa/routes/payments-governance.js +61 -56
  128. package/dist/pwa/routes/peers.js +9 -8
  129. package/dist/pwa/routes/pin-receipts.js +13 -13
  130. package/dist/pwa/routes/products-aliases.js +12 -10
  131. package/dist/pwa/routes/products-claims.js +36 -17
  132. package/dist/pwa/routes/products-create.js +53 -38
  133. package/dist/pwa/routes/products-crud.js +17 -16
  134. package/dist/pwa/routes/products-links.js +49 -26
  135. package/dist/pwa/routes/products-list.js +6 -4
  136. package/dist/pwa/routes/products-meta.js +40 -39
  137. package/dist/pwa/routes/products-update.js +19 -5
  138. package/dist/pwa/routes/profile-credentials.js +14 -16
  139. package/dist/pwa/routes/profile-identity.js +14 -13
  140. package/dist/pwa/routes/profile-location.js +7 -6
  141. package/dist/pwa/routes/profile-placement.js +19 -17
  142. package/dist/pwa/routes/profile-prefs.js +11 -11
  143. package/dist/pwa/routes/promoter.js +55 -49
  144. package/dist/pwa/routes/public-build-tasks.js +19 -0
  145. package/dist/pwa/routes/public-utils.js +108 -46
  146. package/dist/pwa/routes/push.js +16 -15
  147. package/dist/pwa/routes/ratings.js +30 -30
  148. package/dist/pwa/routes/recover-key.js +13 -12
  149. package/dist/pwa/routes/referral.js +37 -32
  150. package/dist/pwa/routes/reputation.js +3 -2
  151. package/dist/pwa/routes/returns.js +76 -73
  152. package/dist/pwa/routes/reviews.js +41 -18
  153. package/dist/pwa/routes/rewards-apply.js +16 -15
  154. package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
  155. package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
  156. package/dist/pwa/routes/rfqs.js +163 -85
  157. package/dist/pwa/routes/search.js +16 -14
  158. package/dist/pwa/routes/secondhand.js +25 -22
  159. package/dist/pwa/routes/seller-quota.js +24 -26
  160. package/dist/pwa/routes/share-redirects.js +59 -55
  161. package/dist/pwa/routes/shareables-interactions.js +34 -35
  162. package/dist/pwa/routes/shareables.js +55 -51
  163. package/dist/pwa/routes/shop-referral.js +57 -0
  164. package/dist/pwa/routes/shops.js +20 -18
  165. package/dist/pwa/routes/signaling.js +10 -9
  166. package/dist/pwa/routes/skill-market.js +16 -16
  167. package/dist/pwa/routes/skills.js +15 -14
  168. package/dist/pwa/routes/snf.js +14 -13
  169. package/dist/pwa/routes/tags.js +10 -9
  170. package/dist/pwa/routes/task-proposals.js +45 -0
  171. package/dist/pwa/routes/trial.js +69 -51
  172. package/dist/pwa/routes/trusted-kpi.js +20 -18
  173. package/dist/pwa/routes/url-claim.js +67 -28
  174. package/dist/pwa/routes/users-public.js +62 -60
  175. package/dist/pwa/routes/variants.js +12 -13
  176. package/dist/pwa/routes/verifier-user.js +61 -21
  177. package/dist/pwa/routes/verify-tasks.js +49 -25
  178. package/dist/pwa/routes/waitlist.js +16 -15
  179. package/dist/pwa/routes/wallet-read.js +74 -36
  180. package/dist/pwa/routes/wallet-write.js +12 -9
  181. package/dist/pwa/routes/webauthn.js +25 -26
  182. package/dist/pwa/routes/webhooks.js +26 -26
  183. package/dist/pwa/routes/welcome.js +45 -50
  184. package/dist/pwa/routes/wishlist-qa.js +29 -32
  185. package/dist/pwa/server.js +237 -81
  186. package/dist/version.js +1 -1
  187. package/package.json +47 -2
@@ -1,4 +1,5 @@
1
1
  import crypto from 'crypto';
2
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
2
3
  const MANIFEST_DAILY_LIMIT = 20;
3
4
  const THUMB_MAX_BYTES = 12000; // ~12KB base64 ≈ 9KB 原始图
4
5
  function verifyManifestSig(hash, ownerId, contentType, byteSize, signedAt, apiKey, signature) {
@@ -7,8 +8,9 @@ function verifyManifestSig(hash, ownerId, contentType, byteSize, signedAt, apiKe
7
8
  return expected === signature;
8
9
  }
9
10
  export function registerManifestsRoutes(app, deps) {
10
- const { db, auth, safeRoles } = deps;
11
- app.post('/api/manifests', (req, res) => {
11
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
12
+ const { auth, safeRoles } = deps;
13
+ app.post('/api/manifests', async (req, res) => {
12
14
  const me = auth(req, res);
13
15
  if (!me)
14
16
  return;
@@ -29,21 +31,20 @@ export function registerManifestsRoutes(app, deps) {
29
31
  return void res.json({ error: '签名验证失败' });
30
32
  }
31
33
  // 日上限
32
- const todayCount = db.prepare(`SELECT COUNT(*) as n FROM manifest_registry WHERE owner_id = ? AND created_at > datetime('now', '-1 day')`).get(me.id).n;
34
+ const todayCount = (await dbOne(`SELECT COUNT(*) as n FROM manifest_registry WHERE owner_id = ? AND created_at > datetime('now', '-1 day')`, [me.id])).n;
33
35
  if (todayCount >= MANIFEST_DAILY_LIMIT)
34
36
  return void res.json({ error: `每日上限 ${MANIFEST_DAILY_LIMIT} 条` });
35
37
  if (related_product_id) {
36
- const p = db.prepare("SELECT id FROM products WHERE id = ?").get(related_product_id);
38
+ const p = await dbOne("SELECT id FROM products WHERE id = ?", [related_product_id]);
37
39
  if (!p)
38
40
  return void res.json({ error: '关联商品不存在' });
39
41
  }
40
42
  try {
41
- db.prepare(`INSERT INTO manifest_registry (hash, owner_id, content_type, byte_size, title, description, thumbnail_data_uri, signature, signed_at, related_product_id, related_anchor)
42
- VALUES (?,?,?,?,?,?,?,?,?,?,?)`)
43
- .run(hash, me.id, content_type, byte_size, title || null, description || null, thumbnail_data_uri || null, signature, signed_at, related_product_id || null, related_anchor || null);
43
+ await dbRun(`INSERT INTO manifest_registry (hash, owner_id, content_type, byte_size, title, description, thumbnail_data_uri, signature, signed_at, related_product_id, related_anchor)
44
+ VALUES (?,?,?,?,?,?,?,?,?,?,?)`, [hash, me.id, content_type, byte_size, title || null, description || null, thumbnail_data_uri || null, signature, signed_at, related_product_id || null, related_anchor || null]);
44
45
  // 创作者立即注册为 owner peer
45
- db.prepare(`INSERT OR REPLACE INTO peer_directory (peer_id, manifest_hash, is_owner, pin_intent, last_heartbeat)
46
- VALUES (?,?,1,1,datetime('now'))`).run(me.id, hash);
46
+ await dbRun(`INSERT OR REPLACE INTO peer_directory (peer_id, manifest_hash, is_owner, pin_intent, last_heartbeat)
47
+ VALUES (?,?,1,1,datetime('now'))`, [me.id, hash]);
47
48
  res.json({ ok: true, hash });
48
49
  }
49
50
  catch (e) {
@@ -53,63 +54,63 @@ export function registerManifestsRoutes(app, deps) {
53
54
  res.json({ error: '发布失败:' + msg });
54
55
  }
55
56
  });
56
- app.get('/api/manifests/me', (req, res) => {
57
+ app.get('/api/manifests/me', async (req, res) => {
57
58
  const me = auth(req, res);
58
59
  if (!me)
59
60
  return;
60
- const rows = db.prepare(`
61
+ const rows = await dbAll(`
61
62
  SELECT m.*, p.title as product_title FROM manifest_registry m
62
63
  LEFT JOIN products p ON p.id = m.related_product_id
63
64
  WHERE m.owner_id = ? AND m.status != 'removed'
64
65
  ORDER BY m.created_at DESC LIMIT 100
65
- `).all(me.id);
66
+ `, [me.id]);
66
67
  res.json({ manifests: rows });
67
68
  });
68
- app.get('/api/manifests/:hash', (req, res) => {
69
+ app.get('/api/manifests/:hash', async (req, res) => {
69
70
  const me = auth(req, res);
70
71
  if (!me)
71
72
  return;
72
- const m = db.prepare(`SELECT * FROM manifest_registry WHERE hash = ?`).get(req.params.hash);
73
+ const m = await dbOne(`SELECT * FROM manifest_registry WHERE hash = ?`, [req.params.hash]);
73
74
  if (!m)
74
75
  return void res.status(404).json({ error: 'manifest 不存在' });
75
76
  if (m.status === 'removed' || m.status === 'takedown_admin')
76
77
  return void res.json({ error: '内容已下架', removed: true, reason: m.takedown_reason || null });
77
- const peers = db.prepare(`
78
+ const peers = await dbAll(`
78
79
  SELECT peer_id, is_owner, pin_intent, last_heartbeat FROM peer_directory
79
80
  WHERE manifest_hash = ? AND last_heartbeat > datetime('now', '-5 minutes')
80
81
  ORDER BY is_owner DESC, last_heartbeat DESC LIMIT 30
81
- `).all(req.params.hash);
82
+ `, [req.params.hash]);
82
83
  res.json({ manifest: m, peers });
83
84
  });
84
- app.get('/api/manifests/by-product/:pid', (req, res) => {
85
+ app.get('/api/manifests/by-product/:pid', async (req, res) => {
85
86
  const user = auth(req, res);
86
87
  if (!user)
87
88
  return;
88
- const rows = db.prepare(`
89
+ const rows = await dbAll(`
89
90
  SELECT m.*, u.name as owner_name FROM manifest_registry m
90
91
  LEFT JOIN users u ON u.id = m.owner_id
91
92
  WHERE m.related_product_id = ? AND m.status = 'active'
92
93
  ORDER BY m.created_at DESC LIMIT 20
93
- `).all(req.params.pid);
94
+ `, [req.params.pid]);
94
95
  res.json({ manifests: rows });
95
96
  });
96
- app.get('/api/manifests/by-anchor/:anchor', (req, res) => {
97
+ app.get('/api/manifests/by-anchor/:anchor', async (req, res) => {
97
98
  const user = auth(req, res);
98
99
  if (!user)
99
100
  return;
100
- const rows = db.prepare(`
101
+ const rows = await dbAll(`
101
102
  SELECT m.*, u.name as owner_name FROM manifest_registry m
102
103
  LEFT JOIN users u ON u.id = m.owner_id
103
104
  WHERE m.related_anchor = ? AND m.status = 'active'
104
105
  ORDER BY m.created_at DESC LIMIT 50
105
- `).all(req.params.anchor);
106
+ `, [req.params.anchor]);
106
107
  res.json({ manifests: rows });
107
108
  });
108
- app.patch('/api/manifests/:hash/takedown', (req, res) => {
109
+ app.patch('/api/manifests/:hash/takedown', async (req, res) => {
109
110
  const me = auth(req, res);
110
111
  if (!me)
111
112
  return;
112
- const m = db.prepare("SELECT owner_id FROM manifest_registry WHERE hash = ?").get(req.params.hash);
113
+ const m = await dbOne("SELECT owner_id FROM manifest_registry WHERE hash = ?", [req.params.hash]);
113
114
  if (!m)
114
115
  return void res.status(404).json({ error: 'manifest 不存在' });
115
116
  const isAdmin = me.role === 'admin' || safeRoles(me).includes('admin');
@@ -117,10 +118,9 @@ export function registerManifestsRoutes(app, deps) {
117
118
  if (!isOwner && !isAdmin)
118
119
  return void res.json({ error: '无权下架' });
119
120
  const reason = (req.body?.reason || '').toString().slice(0, 200);
120
- db.prepare(`UPDATE manifest_registry SET status = ?, takedown_reason = ?, takedown_at = datetime('now'), takedown_by = ? WHERE hash = ?`)
121
- .run(isAdmin && !isOwner ? 'takedown_admin' : 'removed', reason, me.id, req.params.hash);
121
+ await dbRun(`UPDATE manifest_registry SET status = ?, takedown_reason = ?, takedown_at = datetime('now'), takedown_by = ? WHERE hash = ?`, [isAdmin && !isOwner ? 'takedown_admin' : 'removed', reason, me.id, req.params.hash]);
122
122
  // 同步清空 peer directory(强制客户端 evict)
123
- db.prepare("DELETE FROM peer_directory WHERE manifest_hash = ?").run(req.params.hash);
123
+ await dbRun("DELETE FROM peer_directory WHERE manifest_hash = ?", [req.params.hash]);
124
124
  res.json({ ok: true });
125
125
  });
126
126
  }
@@ -1,11 +1,13 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerMeDataRoutes(app, deps) {
2
- const { db, auth } = deps;
3
+ // db 已全量走 RFC-016 异步 seam(dbOne/dbAll),不再直接用 deps.db
4
+ const { auth } = deps;
3
5
  // COP 飞轮: 完成订单 7d 引导发笔记
4
- app.get('/api/me/note-prompts', (req, res) => {
6
+ app.get('/api/me/note-prompts', async (req, res) => {
5
7
  const user = auth(req, res);
6
8
  if (!user)
7
9
  return;
8
- const rows = db.prepare(`
10
+ const rows = await dbAll(`
9
11
  SELECT o.id as order_id, o.product_id, o.updated_at as completed_at, o.total_amount,
10
12
  p.title as product_title, p.images as product_images
11
13
  FROM orders o
@@ -19,7 +21,7 @@ export function registerMeDataRoutes(app, deps) {
19
21
  )
20
22
  ORDER BY o.updated_at DESC
21
23
  LIMIT 10
22
- `).all(user.id, user.id);
24
+ `, [user.id, user.id]);
23
25
  const prompts = rows.map(r => {
24
26
  let firstImage = null;
25
27
  try {
@@ -40,7 +42,7 @@ export function registerMeDataRoutes(app, deps) {
40
42
  res.json({ prompts });
41
43
  });
42
44
  // COP P0-1: 数据导出(用户主权)
43
- app.get('/api/me/export', (req, res) => {
45
+ app.get('/api/me/export', async (req, res) => {
44
46
  const user = auth(req, res);
45
47
  if (!user)
46
48
  return;
@@ -51,33 +53,33 @@ export function registerMeDataRoutes(app, deps) {
51
53
  notice: 'WebAZ COP 承诺:你的数据属于你。可随时导出,可随时迁出。',
52
54
  };
53
55
  try {
54
- data.profile = db.prepare(`SELECT id, name, handle, role, region, bio, search_anchor, email, phone, permanent_code, created_at, reputation FROM users WHERE id = ?`).get(uid);
55
- data.wallet = db.prepare(`SELECT balance, staked, escrowed, earned FROM wallets WHERE user_id = ?`).get(uid);
56
- data.orders = db.prepare(`SELECT * FROM orders WHERE buyer_id = ? OR seller_id = ? ORDER BY created_at DESC LIMIT 1000`).all(uid, uid);
57
- data.shareables = db.prepare(`SELECT * FROM shareables WHERE owner_id = ? AND status != 'removed'`).all(uid);
58
- data.bookmarks = db.prepare(`SELECT b.*, s.title FROM shareable_bookmarks b LEFT JOIN shareables s ON s.id = b.shareable_id WHERE b.user_id = ?`).all(uid);
59
- data.likes = db.prepare(`SELECT l.*, s.title FROM shareable_likes l LEFT JOIN shareables s ON s.id = l.shareable_id WHERE l.user_id = ?`).all(uid);
60
- data.follows_following = db.prepare(`SELECT followee_id, created_at FROM follows WHERE follower_id = ?`).all(uid);
61
- data.follows_followers = db.prepare(`SELECT follower_id, created_at FROM follows WHERE followee_id = ?`).all(uid);
62
- data.addresses = db.prepare(`SELECT * FROM user_addresses WHERE user_id = ?`).all(uid);
63
- data.kyc = db.prepare(`SELECT status, id_type, id_number_last4, submitted_at, reviewed_at FROM kyc_records WHERE user_id = ?`).get(uid);
56
+ 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
+ data.wallet = await dbOne(`SELECT balance, staked, escrowed, earned FROM wallets WHERE user_id = ?`, [uid]);
58
+ data.orders = await dbAll(`SELECT * FROM orders WHERE buyer_id = ? OR seller_id = ? ORDER BY created_at DESC LIMIT 1000`, [uid, uid]);
59
+ data.shareables = await dbAll(`SELECT * FROM shareables WHERE owner_id = ? AND status != 'removed'`, [uid]);
60
+ 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
+ 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]);
62
+ data.follows_following = await dbAll(`SELECT followee_id, created_at FROM follows WHERE follower_id = ?`, [uid]);
63
+ data.follows_followers = await dbAll(`SELECT follower_id, created_at FROM follows WHERE followee_id = ?`, [uid]);
64
+ data.addresses = await dbAll(`SELECT * FROM user_addresses WHERE user_id = ?`, [uid]);
65
+ data.kyc = await dbOne(`SELECT status, id_type, id_number_last4, submitted_at, reviewed_at FROM kyc_records WHERE user_id = ?`, [uid]);
64
66
  // #1017: wallet_history 不存在 — 用 deposits + withdrawals + commissions 复合
65
67
  try {
66
- data.deposits = db.prepare(`SELECT * FROM deposit_txns WHERE user_id = ? ORDER BY created_at DESC LIMIT 200`).all(uid);
68
+ data.deposits = await dbAll(`SELECT * FROM deposit_txns WHERE user_id = ? ORDER BY created_at DESC LIMIT 200`, [uid]);
67
69
  }
68
70
  catch {
69
71
  data.deposits = [];
70
72
  }
71
73
  try {
72
- data.withdrawals = db.prepare(`SELECT * FROM withdrawal_requests WHERE user_id = ? ORDER BY created_at DESC LIMIT 200`).all(uid);
74
+ data.withdrawals = await dbAll(`SELECT * FROM withdrawal_requests WHERE user_id = ? ORDER BY created_at DESC LIMIT 200`, [uid]);
73
75
  }
74
76
  catch {
75
77
  data.withdrawals = [];
76
78
  }
77
- data.commissions = db.prepare(`SELECT * FROM commission_records WHERE beneficiary_id = ? ORDER BY created_at DESC LIMIT 500`).all(uid) || [];
78
- data.anchors = db.prepare(`SELECT anchor, target_kind, target_id, status, created_at FROM anchor_registry WHERE owner_id = ?`).all(uid);
79
- data.notifications = db.prepare(`SELECT * FROM notifications WHERE user_id = ? ORDER BY created_at DESC LIMIT 500`).all(uid);
80
- data.error_log = db.prepare(`SELECT id, source, message, created_at FROM error_log WHERE user_id = ? ORDER BY id DESC LIMIT 100`).all(uid) || [];
79
+ data.commissions = await dbAll(`SELECT * FROM commission_records WHERE beneficiary_id = ? ORDER BY created_at DESC LIMIT 500`, [uid]) || [];
80
+ data.anchors = await dbAll(`SELECT anchor, target_kind, target_id, status, created_at FROM anchor_registry WHERE owner_id = ?`, [uid]);
81
+ data.notifications = await dbAll(`SELECT * FROM notifications WHERE user_id = ? ORDER BY created_at DESC LIMIT 500`, [uid]);
82
+ data.error_log = await dbAll(`SELECT id, source, message, created_at FROM error_log WHERE user_id = ? ORDER BY id DESC LIMIT 100`, [uid]) || [];
81
83
  }
82
84
  catch (e) {
83
85
  console.warn('[export] partial:', e.message);
@@ -1,10 +1,11 @@
1
+ import { dbOne } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  import { getNotifications, getUnreadCount, markRead } from '../../layer2-business/L2-6-notifications/notification-engine.js';
2
3
  export function registerNotificationsRoutes(app, deps) {
3
4
  const { db, auth, sseClients } = deps;
4
5
  // SSE 实时推送流(EventSource 不支持自定义 header,URL ?key= 也兼容)
5
- app.get('/api/notifications/stream', (req, res) => {
6
+ app.get('/api/notifications/stream', async (req, res) => {
6
7
  const key = req.query.key ?? req.headers.authorization?.replace('Bearer ', '');
7
- const user = key ? db.prepare('SELECT * FROM users WHERE api_key = ?').get(key) : null;
8
+ const user = key ? await dbOne('SELECT * FROM users WHERE api_key = ?', [key]) : null;
8
9
  if (!user)
9
10
  return void res.status(401).end();
10
11
  res.setHeader('Content-Type', 'text/event-stream');
@@ -13,7 +14,7 @@ export function registerNotificationsRoutes(app, deps) {
13
14
  res.flushHeaders();
14
15
  sseClients.set(user.id, res);
15
16
  // 连接时推送未读数
16
- const unread = getUnreadCount(db, user.id);
17
+ const unread = await getUnreadCount(db, user.id);
17
18
  res.write(`data: ${JSON.stringify({ type: 'init', unread })}\n\n`);
18
19
  // 心跳保活(每 30s)
19
20
  const heartbeat = setInterval(() => {
@@ -29,13 +30,13 @@ export function registerNotificationsRoutes(app, deps) {
29
30
  clearInterval(heartbeat);
30
31
  });
31
32
  });
32
- app.get('/api/notifications', (req, res) => {
33
+ app.get('/api/notifications', async (req, res) => {
33
34
  const user = auth(req, res);
34
35
  if (!user)
35
36
  return;
36
37
  const onlyUnread = req.query.unread === '1';
37
- const notifs = getNotifications(db, user.id, onlyUnread);
38
- const unread = getUnreadCount(db, user.id);
38
+ const notifs = await getNotifications(db, user.id, onlyUnread);
39
+ const unread = await getUnreadCount(db, user.id);
39
40
  res.json({ unread, notifications: notifs });
40
41
  });
41
42
  app.post('/api/notifications/read', (req, res) => {
@@ -1,10 +1,13 @@
1
+ import { dbOne, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerOffersRoutes(app, deps) {
3
+ // db 仍保留:用于 DELETE /offers 的 db.transaction(撤回 offer + 释放质押守恒,better-sqlite3 事务须同步)。
4
+ // 其余只读/单写站点已走 RFC-016 异步 seam(dbOne/dbRun)。
2
5
  const { db, auth, VALID_FULFILLMENT_TYPES } = deps;
3
- app.patch('/api/offers/:id', (req, res) => {
6
+ app.patch('/api/offers/:id', async (req, res) => {
4
7
  const user = auth(req, res);
5
8
  if (!user)
6
9
  return;
7
- const offer = db.prepare("SELECT * FROM products WHERE id = ? AND listing_id IS NOT NULL").get(req.params.id);
10
+ const offer = await dbOne("SELECT * FROM products WHERE id = ? AND listing_id IS NOT NULL", [req.params.id]);
8
11
  if (!offer)
9
12
  return void res.status(404).json({ error: 'offer 不存在' });
10
13
  if (offer.seller_id !== user.id)
@@ -48,27 +51,37 @@ export function registerOffersRoutes(app, deps) {
48
51
  updates.push("updated_at = datetime('now')");
49
52
  updates.push("freshness_ts = datetime('now')");
50
53
  args.push(req.params.id);
51
- db.prepare(`UPDATE products SET ${updates.join(', ')} WHERE id = ?`).run(...args);
54
+ await dbRun(`UPDATE products SET ${updates.join(', ')} WHERE id = ?`, args);
52
55
  res.json({ success: true });
53
56
  });
54
57
  // 撤回 offer(status=warehouse + 释放 stake;不真删 product)
55
- app.delete('/api/offers/:id', (req, res) => {
58
+ app.delete('/api/offers/:id', async (req, res) => {
56
59
  const user = auth(req, res);
57
60
  if (!user)
58
61
  return;
59
- const offer = db.prepare("SELECT * FROM products WHERE id = ? AND listing_id IS NOT NULL").get(req.params.id);
62
+ const offer = await dbOne("SELECT * FROM products WHERE id = ? AND listing_id IS NOT NULL", [req.params.id]);
60
63
  if (!offer)
61
64
  return void res.status(404).json({ error: 'offer 不存在' });
62
65
  if (offer.seller_id !== user.id)
63
66
  return void res.status(403).json({ error: '仅卖家本人可撤回' });
64
- const pending = db.prepare(`SELECT COUNT(1) as n FROM orders WHERE product_id = ? AND status NOT IN ('completed','cancelled','refunded','expired')`).get(req.params.id);
67
+ const pending = (await dbOne(`SELECT COUNT(1) as n FROM orders WHERE product_id = ? AND status NOT IN ('completed','cancelled','refunded','expired')`, [req.params.id]));
65
68
  if (pending.n > 0)
66
69
  return void res.json({ error: `该 offer 有 ${pending.n} 个进行中订单,暂无法撤回` });
67
70
  const stake = Number(offer.listing_stake_locked) || 0;
68
71
  const tx = db.transaction(() => {
69
- db.prepare(`UPDATE products SET status = 'warehouse', listing_stake_locked = 0, updated_at = datetime('now') WHERE id = ?`).run(req.params.id);
72
+ // await-gap P1(proactive sweep,与 #239 系列同类):offer 行先 await 读出 stake,再进同步 tx 释放。
73
+ // 并发双撤回会都读到同一 listing_stake_locked 并各退一次 → 双倍退质押(印钱)。CAS 抢占该 offer
74
+ // (仍未撤回 + stake 仍等于读到的值),changes!==1 即并发已撤回 → 抛回滚,先于任何钱写。
75
+ const flip = db.prepare(`UPDATE products SET status = 'warehouse', listing_stake_locked = 0, updated_at = datetime('now') WHERE id = ? AND status != 'warehouse' AND listing_stake_locked = ?`).run(req.params.id, stake);
76
+ if (flip.changes !== 1)
77
+ throw new Error('OFFER_ALREADY_WITHDRAWN');
70
78
  if (stake > 0) {
71
- db.prepare(`UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?`).run(stake, stake, user.id);
79
+ // Codex #254 follow-up P2:钱包释放带 staked>=stake 守卫 + changes 校验。若 wallet 行缺失或
80
+ // staked < listing_stake_locked(历史漂移/并发异常/前序 bug),不能在已清零 products.stake 的同时
81
+ // 让 staked 变负或丢质押 —— changes!==1 即抛回滚整笔(products 不清零、listings 不递减、钱包不动)。
82
+ const rel = db.prepare(`UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ? AND staked >= ?`).run(stake, stake, user.id, stake);
83
+ if (rel.changes !== 1)
84
+ throw new Error('OFFER_STAKE_INVARIANT_VIOLATION');
72
85
  }
73
86
  db.prepare(`UPDATE listings SET total_offers = MAX(0, total_offers - 1) WHERE id = ?`).run(String(offer.listing_id));
74
87
  });
@@ -76,21 +89,26 @@ export function registerOffersRoutes(app, deps) {
76
89
  tx();
77
90
  }
78
91
  catch (e) {
79
- return void res.status(500).json({ error: String(e.message) });
92
+ const m = e.message;
93
+ if (m === 'OFFER_ALREADY_WITHDRAWN')
94
+ return void res.status(409).json({ error: '该 offer 已撤回(请刷新)', error_code: 'OFFER_ALREADY_WITHDRAWN' });
95
+ if (m === 'OFFER_STAKE_INVARIANT_VIOLATION')
96
+ return void res.status(500).json({ error: '质押释放校验失败(资金状态异常,已回滚未做任何变更,请联系支持)', error_code: 'OFFER_STAKE_INVARIANT_VIOLATION' });
97
+ return void res.status(500).json({ error: String(m) });
80
98
  }
81
99
  res.json({ success: true, stake_released: stake });
82
100
  });
83
101
  // 刷新 freshness(卖家点 "现货确认")
84
- app.post('/api/offers/:id/refresh', (req, res) => {
102
+ app.post('/api/offers/:id/refresh', async (req, res) => {
85
103
  const user = auth(req, res);
86
104
  if (!user)
87
105
  return;
88
- const offer = db.prepare("SELECT seller_id FROM products WHERE id = ? AND listing_id IS NOT NULL").get(req.params.id);
106
+ const offer = await dbOne("SELECT seller_id FROM products WHERE id = ? AND listing_id IS NOT NULL", [req.params.id]);
89
107
  if (!offer)
90
108
  return void res.status(404).json({ error: 'offer 不存在' });
91
109
  if (offer.seller_id !== user.id)
92
110
  return void res.status(403).json({ error: '仅卖家本人可刷新' });
93
- db.prepare(`UPDATE products SET freshness_ts = datetime('now') WHERE id = ?`).run(req.params.id);
111
+ await dbRun(`UPDATE products SET freshness_ts = datetime('now') WHERE id = ?`, [req.params.id]);
94
112
  res.json({ success: true });
95
113
  });
96
114
  }
@@ -1,3 +1,4 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js';
1
2
  export function registerOrdersActionRoutes(app, deps) {
2
3
  const { db, auth, isTrustedRole, generateId, transition, notifyTransition, settleOrder, settleFault, detectFraud, createDispute, checkTimeouts, recordViolationReputation, broadcastSystemEvent } = deps;
3
4
  // RFC-007 stage 2:卖家主动拒单 reason_code 白名单。
@@ -11,7 +12,7 @@ export function registerOrdersActionRoutes(app, deps) {
11
12
  // 客观-声称理由:链下事实(外部已售/损毁),协议无确定性信号可自动核验 → 临时判责 + 举证窗口(stage 5 仲裁)。
12
13
  const OBJECTIVE_DECLINE_REASONS = new Set(['stock_consumed_concurrent', 'stale_price_snapshot', 'force_majeure']);
13
14
  // C-4: 卖家批量发货
14
- app.post('/api/orders/batch-ship', (req, res) => {
15
+ app.post('/api/orders/batch-ship', async (req, res) => {
15
16
  const user = auth(req, res);
16
17
  if (!user)
17
18
  return;
@@ -22,7 +23,8 @@ export function registerOrdersActionRoutes(app, deps) {
22
23
  return void res.status(400).json({ error: '单次最多 100 单' });
23
24
  if (!logistics_company_id)
24
25
  return void res.status(400).json({ error: 'logistics_company_id 必填' });
25
- const lc = db.prepare("SELECT id FROM users WHERE id = ? AND role = 'logistics'").get(logistics_company_id);
26
+ // RFC-016: 纯校验读 异步 seam(物流公司是否存在);循环内的逐单 read+write 仍同步(Phase 3 随订单事务迁)
27
+ const lc = await dbOne("SELECT id FROM users WHERE id = ? AND role = 'logistics'", [logistics_company_id]);
26
28
  if (!lc)
27
29
  return void res.status(400).json({ error: '物流公司不存在' });
28
30
  const results = [];
@@ -69,11 +71,12 @@ export function registerOrdersActionRoutes(app, deps) {
69
71
  res.json({ success: true, shipped, skipped: results.filter(r => r.status === 'skipped').length, results });
70
72
  });
71
73
  // 买家确认面交完成 → 直接 completed + settleOrder
72
- app.post('/api/orders/:id/confirm-in-person', (req, res) => {
74
+ app.post('/api/orders/:id/confirm-in-person', async (req, res) => {
73
75
  const user = auth(req, res);
74
76
  if (!user)
75
77
  return;
76
- const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(req.params.id);
78
+ // RFC-016: 校验读 → 异步 seam;下方 completed+history 写仍是同步 db.transaction(Phase 3 pg 事务)
79
+ const order = await dbOne('SELECT * FROM orders WHERE id = ?', [req.params.id]);
77
80
  if (!order)
78
81
  return void res.status(404).json({ error: '订单不存在' });
79
82
  if (order.fulfillment_mode !== 'in_person')
@@ -105,7 +108,7 @@ export function registerOrdersActionRoutes(app, deps) {
105
108
  res.json({ success: true });
106
109
  });
107
110
  // 通用状态机 action — accept/ship/pickup/transit/deliver/confirm/dispute
108
- app.post('/api/orders/:id/action', (req, res) => {
111
+ app.post('/api/orders/:id/action', async (req, res) => {
109
112
  const user = auth(req, res);
110
113
  if (!user)
111
114
  return;
@@ -113,7 +116,8 @@ export function registerOrdersActionRoutes(app, deps) {
113
116
  if (isTrustedRole(user))
114
117
  return void res.status(403).json({ error: '受信角色不可参与订单流转', error_code: 'TRUSTED_ROLE_NO_TRADE' });
115
118
  const { action, notes = '', evidence_description = '', logistics_company_id = '' } = req.body;
116
- const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(req.params.id);
119
+ // RFC-016: 顶层校验读 异步 seam;state-machine / settle / decline 写序列仍同步(Phase 3 迁 pg 行锁+事务)
120
+ const order = await dbOne('SELECT * FROM orders WHERE id = ?', [req.params.id]);
117
121
  if (!order)
118
122
  return void res.status(404).json({ error: '订单不存在' });
119
123
  // P0: 路由层 ownership 校验(engine 层只看 role,必须补 ownership)
@@ -190,16 +194,26 @@ export function registerOrdersActionRoutes(app, deps) {
190
194
  });
191
195
  }
192
196
  // 主观(或窗口=0):立即违约结算(退款买家 + 按 stake_backing 罚没,守恒,绝不印钱)
197
+ // Codex #119 P1:这是资金结算路径,settleFault / completed transition 失败【绝不能】吞掉后仍报 success。
198
+ // 订单此刻已在 fault_seller(上面 transition 已提交)。只有结算 + 推进 completed 都成功才返回 success;
199
+ // 任一失败 → 返回 500 DECLINE_SETTLEMENT_FAILED(订单停在 fault_seller,可重试/人工/cron 终结),不谎称已退款。
193
200
  const sysUser = db.prepare("SELECT id FROM users WHERE id = 'sys_protocol'").get();
194
201
  try {
202
+ if (!sysUser)
203
+ throw new Error('sys_protocol user missing — cannot finalize decline settlement');
195
204
  settleFault(db, req.params.id, 'fault_seller');
196
- if (sysUser) {
197
- transition(db, req.params.id, 'completed', sysUser.id, [], '主动拒单:系统执行违约结算');
198
- notifyTransition(db, req.params.id, 'fault_seller', 'completed');
199
- }
205
+ const rc = transition(db, req.params.id, 'completed', sysUser.id, [], '主动拒单:系统执行违约结算');
206
+ if (!rc?.success)
207
+ throw new Error(`fault_seller→completed transition failed: ${rc?.error || 'unknown'}`);
208
+ notifyTransition(db, req.params.id, 'fault_seller', 'completed');
200
209
  }
201
210
  catch (e) {
202
211
  console.error('[decline settleFault]', e);
212
+ return void res.status(500).json({
213
+ error: '违约结算未完成,订单仍停在 fault_seller,请稍后重试或联系支持(买家尚未退款)',
214
+ error_code: 'DECLINE_SETTLEMENT_FAILED',
215
+ outcome: 'fault_seller',
216
+ });
203
217
  }
204
218
  return void res.json({
205
219
  success: true, outcome: 'fault_seller', decline_reason_code: reasonCode,
@@ -278,7 +292,8 @@ export function registerOrdersActionRoutes(app, deps) {
278
292
  // QA 轮 9.4-retry-v3 P1:post-hoc build breakdown 从 DB,让 agent 看清每分钱去哪
279
293
  try {
280
294
  const round2 = (n) => Math.round(n * 100) / 100;
281
- const ord = db.prepare("SELECT id, total_amount, source, fulfillment_mode, snapshot_commission_rate, l1_uid, l2_uid, l3_uid, logistics_id, seller_id FROM orders WHERE id = ?").get(req.params.id);
295
+ // RFC-016: settleOrder 已完成,以下纯只读 breakdown 查询 异步 seam(无写,无原子性要求)
296
+ const ord = await dbOne("SELECT id, total_amount, source, fulfillment_mode, snapshot_commission_rate, l1_uid, l2_uid, l3_uid, logistics_id, seller_id FROM orders WHERE id = ?", [req.params.id]);
282
297
  if (ord) {
283
298
  const total = Number(ord.total_amount);
284
299
  const isSecondhand = ord.source === 'secondhand';
@@ -289,7 +304,7 @@ export function registerOrdersActionRoutes(app, deps) {
289
304
  const logisticsActual = ord.logistics_id ? logisticsFee : 0;
290
305
  const commissionRate = Number(ord.snapshot_commission_rate ?? 0.10);
291
306
  const commissionPool = round2(total * commissionRate);
292
- const commRecs = db.prepare("SELECT level, amount, beneficiary_id FROM commission_records WHERE order_id = ?").all(req.params.id);
307
+ const commRecs = await dbAll("SELECT level, amount, beneficiary_id FROM commission_records WHERE order_id = ?", [req.params.id]);
293
308
  const commByLevel = {
294
309
  1: { amount: 0, to: null }, 2: { amount: 0, to: null }, 3: { amount: 0, to: null },
295
310
  };
@@ -301,9 +316,9 @@ export function registerOrdersActionRoutes(app, deps) {
301
316
  const commissionRedirected = round2(commissionPool - commissionDistributed);
302
317
  // QA 轮 14.b P2:redirected_total 拆 chain_gap(→charity) vs region_cap(→global_fund)
303
318
  // 之前单一数字让 agent 无法分辨钱去哪(global region L2/L3 进 global_fund,不是 charity)
304
- const charityRow = db.prepare("SELECT COALESCE(SUM(amount),0) AS s FROM charity_fund_txns WHERE related_order_id = ?").get(req.params.id);
319
+ const charityRow = (await dbOne("SELECT COALESCE(SUM(amount),0) AS s FROM charity_fund_txns WHERE related_order_id = ?", [req.params.id]));
305
320
  const redirectedToCharity = round2(Number(charityRow.s));
306
- const fundDepRow = db.prepare("SELECT COALESCE(SUM(amount_l3),0) AS s FROM fund_deposits WHERE order_id = ?").get(req.params.id);
321
+ const fundDepRow = (await dbOne("SELECT COALESCE(SUM(amount_l3),0) AS s FROM fund_deposits WHERE order_id = ?", [req.params.id]));
307
322
  const redirectedToGlobalFund = round2(Number(fundDepRow.s));
308
323
  // QA 轮 9.5 P2:payouts 表只 MCP legacy 写,PWA settleOrder 直更 wallet.balance 不写 payouts
309
324
  // 改用公式推算 sellerAmount(跟 PWA settleOrder 内部计算一致),更可靠
@@ -347,11 +362,12 @@ export function registerOrdersActionRoutes(app, deps) {
347
362
  });
348
363
  });
349
364
  // 手动触发超时判责(当事人)
350
- app.post('/api/orders/:id/force-timeout-check', (req, res) => {
365
+ app.post('/api/orders/:id/force-timeout-check', async (req, res) => {
351
366
  const user = auth(req, res);
352
367
  if (!user)
353
368
  return;
354
- const order = db.prepare('SELECT buyer_id, seller_id, logistics_id, status FROM orders WHERE id = ?').get(req.params.id);
369
+ // RFC-016: 当事人校验读 异步 seam;checkTimeouts(db) 自身仍是同步判责引擎(Phase 3 内部迁)
370
+ const order = await dbOne('SELECT buyer_id, seller_id, logistics_id, status FROM orders WHERE id = ?', [req.params.id]);
355
371
  if (!order)
356
372
  return void res.status(404).json({ error: '订单不存在' });
357
373
  const uid = user.id;
@@ -360,7 +376,7 @@ export function registerOrdersActionRoutes(app, deps) {
360
376
  }
361
377
  const beforeStatus = order.status;
362
378
  const r = checkTimeouts(db);
363
- const after = db.prepare('SELECT status FROM orders WHERE id = ?').get(req.params.id);
379
+ const after = (await dbOne('SELECT status FROM orders WHERE id = ?', [req.params.id]));
364
380
  const touched = r.details.find((d) => d.orderId === req.params.id) || null;
365
381
  if (touched) {
366
382
  const faultMatch = touched.action.match(/→ (fault_\w+)/);