@seasonkoh/webaz 0.1.23 → 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 +198 -83
  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,18 +1,21 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerAdminVerifierFlowRoutes(app, deps) {
3
+ // 只读站点走 RFC-016 异步 seam;db 保留:approve/reject/decide 含状态翻转 + 退质押 + 补发奖励,
4
+ // 必须原子(db.transaction + CAS 翻转,退款/奖励仅在本请求真翻转时落),Phase 3 迁 pg 行锁。
2
5
  const { db, requireVerifierMgmtAdmin, TIER_QUOTAS, VERIFIER_STAKE_REQUIRED, todayStartISO, logAdminAction } = deps;
3
- app.get('/api/admin/verifier-applications', (req, res) => {
6
+ app.get('/api/admin/verifier-applications', async (req, res) => {
4
7
  const admin = requireVerifierMgmtAdmin(req, res);
5
8
  if (!admin)
6
9
  return;
7
10
  const status = req.query.status || 'pending';
8
- const rows = db.prepare(`
11
+ const rows = await dbAll(`
9
12
  SELECT va.id, va.user_id, va.status, va.applied_at, va.reviewed_at, va.reviewed_by, va.decision_note, va.snapshot,
10
13
  u.name as user_name, u.email
11
14
  FROM verifier_applications va
12
15
  LEFT JOIN users u ON u.id = va.user_id
13
16
  WHERE va.status = ?
14
17
  ORDER BY va.applied_at DESC LIMIT 100
15
- `).all(status);
18
+ `, [status]);
16
19
  res.json({
17
20
  applications: rows.map(r => ({
18
21
  ...r,
@@ -25,59 +28,83 @@ export function registerAdminVerifierFlowRoutes(app, deps) {
25
28
  })),
26
29
  });
27
30
  });
28
- app.post('/api/admin/verifier-applications/:id/approve', (req, res) => {
31
+ app.post('/api/admin/verifier-applications/:id/approve', async (req, res) => {
29
32
  const admin = requireVerifierMgmtAdmin(req, res);
30
33
  if (!admin)
31
34
  return;
32
35
  const { tier, note } = req.body;
33
36
  const validTier = ['trial-1', 'trial-2', 'trial-3', 'active-1', 'active-2'].includes(tier) ? tier : 'trial-1';
34
- const apl = db.prepare("SELECT id, user_id, status FROM verifier_applications WHERE id = ?").get(req.params.id);
37
+ const apl = await dbOne("SELECT id, user_id, status FROM verifier_applications WHERE id = ?", [req.params.id]);
35
38
  if (!apl)
36
39
  return void res.json({ error: '申请不存在' });
37
40
  if (apl.status !== 'pending')
38
41
  return void res.json({ error: '该申请不在待审状态' });
39
- db.prepare("UPDATE verifier_applications SET status='approved', reviewed_at=datetime('now'), reviewed_by=?, decision_note=? WHERE id=?")
40
- .run(admin.id, note || null, apl.id);
41
- db.prepare(`INSERT OR REPLACE INTO verifier_whitelist
42
- (user_id, note, tier, daily_quota, tasks_today, quota_reset_at, granted_by, stake_amount, is_system)
43
- VALUES (?,?,?,?,0,?,?,?,0)`)
44
- .run(apl.user_id, note || `批准为 ${validTier}`, validTier, TIER_QUOTAS[validTier], todayStartISO(), admin.id, VERIFIER_STAKE_REQUIRED);
45
- db.prepare("INSERT OR IGNORE INTO verifier_stats (user_id) VALUES (?)").run(apl.user_id);
42
+ // 原子段:CAS 翻转 pending→approved + 入白名单 + stats(防并发双批准)
43
+ try {
44
+ db.transaction(() => {
45
+ const cas = db.prepare("UPDATE verifier_applications SET status='approved', reviewed_at=datetime('now'), reviewed_by=?, decision_note=? WHERE id=? AND status='pending'")
46
+ .run(admin.id, note || null, apl.id);
47
+ if (cas.changes === 0)
48
+ throw new Error('APP_RACE');
49
+ db.prepare(`INSERT OR REPLACE INTO verifier_whitelist
50
+ (user_id, note, tier, daily_quota, tasks_today, quota_reset_at, granted_by, stake_amount, is_system)
51
+ VALUES (?,?,?,?,0,?,?,?,0)`)
52
+ .run(apl.user_id, note || `批准为 ${validTier}`, validTier, TIER_QUOTAS[validTier], todayStartISO(), admin.id, VERIFIER_STAKE_REQUIRED);
53
+ db.prepare("INSERT OR IGNORE INTO verifier_stats (user_id) VALUES (?)").run(apl.user_id);
54
+ })();
55
+ }
56
+ catch (e) {
57
+ if (e.message === 'APP_RACE')
58
+ return void res.json({ error: '该申请不在待审状态' });
59
+ console.error('[verifier approve tx]', e.message);
60
+ return void res.status(500).json({ error: '批准失败,请重试' });
61
+ }
46
62
  logAdminAction(admin.id, 'approve_verifier', 'user', apl.user_id, { tier: validTier, note });
47
63
  res.json({ success: true });
48
64
  });
49
- app.post('/api/admin/verifier-applications/:id/reject', (req, res) => {
65
+ app.post('/api/admin/verifier-applications/:id/reject', async (req, res) => {
50
66
  const admin = requireVerifierMgmtAdmin(req, res);
51
67
  if (!admin)
52
68
  return;
53
69
  const { note } = req.body;
54
- const apl = db.prepare("SELECT id, user_id, status FROM verifier_applications WHERE id = ?").get(req.params.id);
70
+ const apl = await dbOne("SELECT id, user_id, status FROM verifier_applications WHERE id = ?", [req.params.id]);
55
71
  if (!apl)
56
72
  return void res.json({ error: '申请不存在' });
57
73
  if (apl.status !== 'pending')
58
74
  return void res.json({ error: '该申请不在待审状态' });
59
- db.prepare("UPDATE verifier_applications SET status='rejected', reviewed_at=datetime('now'), reviewed_by=?, decision_note=? WHERE id=?")
60
- .run(admin.id, note || null, apl.id);
61
- // 退回质押
62
- if (VERIFIER_STAKE_REQUIRED > 0) {
63
- db.prepare("UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?")
64
- .run(VERIFIER_STAKE_REQUIRED, VERIFIER_STAKE_REQUIRED, apl.user_id);
75
+ // 原子段:CAS 翻转 pending→rejected + 退质押仅在本请求真翻转时(防并发双拒双退)
76
+ try {
77
+ db.transaction(() => {
78
+ const cas = db.prepare("UPDATE verifier_applications SET status='rejected', reviewed_at=datetime('now'), reviewed_by=?, decision_note=? WHERE id=? AND status='pending'")
79
+ .run(admin.id, note || null, apl.id);
80
+ if (cas.changes === 0)
81
+ throw new Error('APP_RACE');
82
+ if (VERIFIER_STAKE_REQUIRED > 0) {
83
+ db.prepare("UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?").run(VERIFIER_STAKE_REQUIRED, VERIFIER_STAKE_REQUIRED, apl.user_id);
84
+ }
85
+ })();
86
+ }
87
+ catch (e) {
88
+ if (e.message === 'APP_RACE')
89
+ return void res.json({ error: '该申请不在待审状态' });
90
+ console.error('[verifier reject tx]', e.message);
91
+ return void res.status(500).json({ error: '拒绝失败,请重试' });
65
92
  }
66
93
  logAdminAction(admin.id, 'reject_verifier', 'user', apl.user_id, { note });
67
94
  res.json({ success: true });
68
95
  });
69
- app.get('/api/admin/verifier-appeals', (req, res) => {
96
+ app.get('/api/admin/verifier-appeals', async (req, res) => {
70
97
  const admin = requireVerifierMgmtAdmin(req, res);
71
98
  if (!admin)
72
99
  return;
73
100
  const status = req.query.status || 'pending';
74
- const rows = db.prepare(`
101
+ const rows = await dbAll(`
75
102
  SELECT va.id, va.user_id, va.task_id, va.submission_id, va.reason, va.evidence_urls, va.status,
76
103
  va.admin_note, va.reviewed_by, va.reviewed_at, va.created_at, u.name as user_name
77
104
  FROM verifier_appeals va LEFT JOIN users u ON u.id = va.user_id
78
105
  WHERE va.status = ?
79
106
  ORDER BY va.created_at DESC LIMIT 100
80
- `).all(status);
107
+ `, [status]);
81
108
  res.json({
82
109
  appeals: rows.map(r => ({
83
110
  ...r,
@@ -90,35 +117,49 @@ export function registerAdminVerifierFlowRoutes(app, deps) {
90
117
  })),
91
118
  });
92
119
  });
93
- app.post('/api/admin/verifier-appeals/:id/decide', (req, res) => {
120
+ app.post('/api/admin/verifier-appeals/:id/decide', async (req, res) => {
94
121
  const admin = requireVerifierMgmtAdmin(req, res);
95
122
  if (!admin)
96
123
  return;
97
124
  const { decision, note } = req.body; // 'accepted' | 'rejected'
98
125
  if (!['accepted', 'rejected'].includes(decision))
99
126
  return void res.json({ error: 'decision 无效' });
100
- const appeal = db.prepare("SELECT id, user_id, status FROM verifier_appeals WHERE id = ?").get(req.params.id);
127
+ const appeal = await dbOne("SELECT id, user_id, status FROM verifier_appeals WHERE id = ?", [req.params.id]);
101
128
  if (!appeal)
102
129
  return void res.json({ error: '申诉不存在' });
103
130
  if (appeal.status !== 'pending')
104
131
  return void res.json({ error: '该申诉已处理' });
105
- db.prepare("UPDATE verifier_appeals SET status = ?, admin_note = ?, reviewed_by = ?, reviewed_at = datetime('now') WHERE id = ?")
106
- .run(decision, note || null, admin.id, appeal.id);
107
- if (decision === 'accepted') {
108
- // 解封 + 验证权 +2 + 错误次数 -1
109
- db.prepare("UPDATE verifier_stats SET suspended_until = NULL, verify_rights = verify_rights + 2 WHERE user_id = ?").run(appeal.user_id);
110
- db.prepare("UPDATE verifier_whitelist SET error_count_180d = MAX(0, error_count_180d - 1) WHERE user_id = ?").run(appeal.user_id);
111
- // 完整重审:翻转该 verifier 在该 task 的 verdict + 补发奖励 + 翻回 stats
112
- const fullAppeal = db.prepare("SELECT task_id FROM verifier_appeals WHERE id = ?").get(appeal.id);
113
- if (fullAppeal?.task_id) {
114
- const sub = db.prepare("SELECT vs.id, vs.verdict, vt.reward_per_verifier FROM verify_submissions vs JOIN verify_tasks vt ON vt.id = vs.task_id WHERE vs.task_id = ? AND vs.verifier_id = ?")
115
- .get(fullAppeal.task_id, appeal.user_id);
116
- if (sub && sub.verdict === 'wrong') {
117
- db.prepare("UPDATE verify_submissions SET verdict = 'correct' WHERE id = ?").run(sub.id);
118
- db.prepare("UPDATE verifier_stats SET tasks_correct = tasks_correct + 1, tasks_wrong = MAX(0, tasks_wrong - 1), verify_rights = verify_rights + 3 WHERE user_id = ?").run(appeal.user_id);
119
- db.prepare("UPDATE wallets SET balance = balance + ? WHERE user_id = ?").run(sub.reward_per_verifier, appeal.user_id);
132
+ // 原子段:CAS 翻转 appeal pending→decision + (accepted )解封/重审/补发奖励 一起落,
133
+ // 防并发双裁决导致 verify_rights 多加、verdict 多翻、奖励多发。
134
+ try {
135
+ db.transaction(() => {
136
+ const cas = db.prepare("UPDATE verifier_appeals SET status = ?, admin_note = ?, reviewed_by = ?, reviewed_at = datetime('now') WHERE id = ? AND status = 'pending'")
137
+ .run(decision, note || null, admin.id, appeal.id);
138
+ if (cas.changes === 0)
139
+ throw new Error('APPEAL_RACE');
140
+ if (decision === 'accepted') {
141
+ // 解封 + 验证权 +2 + 错误次数 -1
142
+ db.prepare("UPDATE verifier_stats SET suspended_until = NULL, verify_rights = verify_rights + 2 WHERE user_id = ?").run(appeal.user_id);
143
+ db.prepare("UPDATE verifier_whitelist SET error_count_180d = MAX(0, error_count_180d - 1) WHERE user_id = ?").run(appeal.user_id);
144
+ // 完整重审:翻转该 verifier 在该 task 的 verdict + 补发奖励 + 翻回 stats
145
+ const fullAppeal = db.prepare("SELECT task_id FROM verifier_appeals WHERE id = ?").get(appeal.id);
146
+ if (fullAppeal?.task_id) {
147
+ const sub = db.prepare("SELECT vs.id, vs.verdict, vt.reward_per_verifier FROM verify_submissions vs JOIN verify_tasks vt ON vt.id = vs.task_id WHERE vs.task_id = ? AND vs.verifier_id = ?")
148
+ .get(fullAppeal.task_id, appeal.user_id);
149
+ if (sub && sub.verdict === 'wrong') {
150
+ db.prepare("UPDATE verify_submissions SET verdict = 'correct' WHERE id = ?").run(sub.id);
151
+ db.prepare("UPDATE verifier_stats SET tasks_correct = tasks_correct + 1, tasks_wrong = MAX(0, tasks_wrong - 1), verify_rights = verify_rights + 3 WHERE user_id = ?").run(appeal.user_id);
152
+ db.prepare("UPDATE wallets SET balance = balance + ? WHERE user_id = ?").run(sub.reward_per_verifier, appeal.user_id);
153
+ }
154
+ }
120
155
  }
121
- }
156
+ })();
157
+ }
158
+ catch (e) {
159
+ if (e.message === 'APPEAL_RACE')
160
+ return void res.json({ error: '该申诉已处理' });
161
+ console.error('[verifier appeal decide tx]', e.message);
162
+ return void res.status(500).json({ error: '裁决失败,请重试' });
122
163
  }
123
164
  logAdminAction(admin.id, 'decide_appeal', 'user', appeal.user_id, { decision, note: note || null });
124
165
  res.json({ success: true });
@@ -1,40 +1,43 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerAdminVerifierWhitelistRoutes(app, deps) {
3
+ // 单写白名单管理站点走 RFC-016 异步 seam;db 保留:revoke 是没收/退质押 + 白名单重写的
4
+ // 多写资金路径,必须原子(db.transaction + 防重复没收 guard),Phase 3 迁 pg 行锁。
2
5
  const { db, requireVerifierMgmtAdmin, adminCanOperateOn, logAdminAction, INTERNAL_AUDITOR_ID, TIER_QUOTAS, REVOKE_COOLDOWN_DAYS } = deps;
3
- app.get('/api/admin/verifier-whitelist', (req, res) => {
6
+ app.get('/api/admin/verifier-whitelist', async (req, res) => {
4
7
  const user = requireVerifierMgmtAdmin(req, res);
5
8
  if (!user)
6
9
  return;
7
- const list = db.prepare(`
10
+ const list = await dbAll(`
8
11
  SELECT vw.user_id, vw.added_at, vw.note, u.name, u.role
9
12
  FROM verifier_whitelist vw
10
13
  JOIN users u ON u.id = vw.user_id
11
14
  ORDER BY vw.added_at ASC
12
- `).all();
15
+ `);
13
16
  res.json(list);
14
17
  });
15
- app.post('/api/admin/verifier-whitelist', (req, res) => {
18
+ app.post('/api/admin/verifier-whitelist', async (req, res) => {
16
19
  const admin = requireVerifierMgmtAdmin(req, res);
17
20
  if (!admin)
18
21
  return;
19
22
  const { user_id, name, note } = req.body;
20
23
  let targetId = user_id;
21
24
  if (!targetId && name) {
22
- const found = db.prepare('SELECT id FROM users WHERE name = ?').get(name);
25
+ const found = await dbOne('SELECT id FROM users WHERE name = ?', [name]);
23
26
  if (!found)
24
27
  return void res.json({ error: `用户「${name}」不存在` });
25
28
  targetId = found.id;
26
29
  }
27
30
  if (!targetId)
28
31
  return void res.json({ error: '请提供 user_id 或 name' });
29
- const target = db.prepare('SELECT id, name FROM users WHERE id = ?').get(targetId);
32
+ const target = await dbOne('SELECT id, name FROM users WHERE id = ?', [targetId]);
30
33
  if (!target)
31
34
  return void res.json({ error: '用户不存在' });
32
35
  if (!adminCanOperateOn(admin, targetId, res))
33
36
  return;
34
- db.prepare('INSERT OR IGNORE INTO verifier_whitelist (user_id, note) VALUES (?, ?)').run(targetId, note ?? null);
37
+ await dbRun('INSERT OR IGNORE INTO verifier_whitelist (user_id, note) VALUES (?, ?)', [targetId, note ?? null]);
35
38
  res.json({ success: true, user_id: targetId, name: target.name });
36
39
  });
37
- app.delete('/api/admin/verifier-whitelist/:userId', (req, res) => {
40
+ app.delete('/api/admin/verifier-whitelist/:userId', async (req, res) => {
38
41
  const admin = requireVerifierMgmtAdmin(req, res);
39
42
  if (!admin)
40
43
  return;
@@ -43,10 +46,10 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
43
46
  return void res.json({ error: '内部审核员不可移除' });
44
47
  if (!adminCanOperateOn(admin, targetId, res))
45
48
  return;
46
- db.prepare('DELETE FROM verifier_whitelist WHERE user_id = ?').run(targetId);
49
+ await dbRun('DELETE FROM verifier_whitelist WHERE user_id = ?', [targetId]);
47
50
  res.json({ success: true });
48
51
  });
49
- app.post('/api/admin/verifier-whitelist/:userId/promote', (req, res) => {
52
+ app.post('/api/admin/verifier-whitelist/:userId/promote', async (req, res) => {
50
53
  const admin = requireVerifierMgmtAdmin(req, res);
51
54
  if (!admin)
52
55
  return;
@@ -56,17 +59,16 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
56
59
  if (!TIER_QUOTAS[tier])
57
60
  return void res.json({ error: 'tier 无效' });
58
61
  const targetId = req.params.userId;
59
- const wl = db.prepare("SELECT is_system FROM verifier_whitelist WHERE user_id = ?").get(targetId);
62
+ const wl = await dbOne("SELECT is_system FROM verifier_whitelist WHERE user_id = ?", [targetId]);
60
63
  if (!wl)
61
64
  return void res.json({ error: '该用户不在白名单' });
62
65
  if (wl.is_system)
63
66
  return void res.json({ error: '系统兜底账户不可手动 promote' });
64
- db.prepare("UPDATE verifier_whitelist SET tier = ?, daily_quota = ? WHERE user_id = ?")
65
- .run(tier, TIER_QUOTAS[tier], targetId);
67
+ await dbRun("UPDATE verifier_whitelist SET tier = ?, daily_quota = ? WHERE user_id = ?", [tier, TIER_QUOTAS[tier], targetId]);
66
68
  logAdminAction(admin.id, 'promote_verifier', 'user', targetId, { tier });
67
69
  res.json({ success: true });
68
70
  });
69
- app.post('/api/admin/verifier-whitelist/:userId/suspend', (req, res) => {
71
+ app.post('/api/admin/verifier-whitelist/:userId/suspend', async (req, res) => {
70
72
  const admin = requireVerifierMgmtAdmin(req, res);
71
73
  if (!admin)
72
74
  return;
@@ -76,12 +78,12 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
76
78
  const targetId = req.params.userId;
77
79
  const n = Number(days) > 0 ? Number(days) : 7;
78
80
  const until = new Date(Date.now() + n * 86400_000).toISOString();
79
- db.prepare("INSERT OR IGNORE INTO verifier_stats (user_id) VALUES (?)").run(targetId);
80
- db.prepare("UPDATE verifier_stats SET suspended_until = ? WHERE user_id = ?").run(until, targetId);
81
+ await dbRun("INSERT OR IGNORE INTO verifier_stats (user_id) VALUES (?)", [targetId]);
82
+ await dbRun("UPDATE verifier_stats SET suspended_until = ? WHERE user_id = ?", [until, targetId]);
81
83
  logAdminAction(admin.id, 'suspend_verifier', 'user', targetId, { days: n, reason: reason || null, until });
82
84
  res.json({ success: true, suspended_until: until });
83
85
  });
84
- app.post('/api/admin/verifier-whitelist/:userId/revoke', (req, res) => {
86
+ app.post('/api/admin/verifier-whitelist/:userId/revoke', async (req, res) => {
85
87
  const admin = requireVerifierMgmtAdmin(req, res);
86
88
  if (!admin)
87
89
  return;
@@ -89,22 +91,48 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
89
91
  return;
90
92
  const { reason } = req.body;
91
93
  const targetId = req.params.userId;
92
- const wl = db.prepare("SELECT is_system, stake_amount FROM verifier_whitelist WHERE user_id = ?").get(targetId);
94
+ // 友好预检查(读);真正的守恒 + 防重复没收门在事务内(重读 active + cooldown guard)
95
+ const wl = await dbOne("SELECT is_system, stake_amount FROM verifier_whitelist WHERE user_id = ?", [targetId]);
93
96
  if (!wl)
94
97
  return void res.json({ error: '该用户不在白名单' });
95
98
  if (wl.is_system)
96
99
  return void res.json({ error: '系统兜底账户不可撤销' });
97
100
  const cooldownUntil = new Date(Date.now() + REVOKE_COOLDOWN_DAYS * 86400_000).toISOString();
98
- // 没收 50% 质押
99
- const forfeit = (wl.stake_amount || 0) * 0.5;
100
- db.prepare("UPDATE wallets SET staked = staked - ? WHERE user_id = ?").run(wl.stake_amount || 0, targetId);
101
- if ((wl.stake_amount || 0) > forfeit) {
102
- db.prepare("UPDATE wallets SET balance = balance + ? WHERE user_id = ?").run((wl.stake_amount || 0) - forfeit, targetId);
101
+ // 原子段:重读 active 行 → 没收 50% + 退还另一半 + DELETE active + INSERT cooldown 一起落。
102
+ // cooldown guard 防并发两次 revoke 重复没收/退款。
103
+ let forfeit = 0;
104
+ try {
105
+ forfeit = db.transaction(() => {
106
+ const cur = db.prepare("SELECT is_system, stake_amount, cooldown_until FROM verifier_whitelist WHERE user_id = ?")
107
+ .get(targetId);
108
+ if (!cur)
109
+ throw new Error('REVOKE_GONE');
110
+ if (cur.is_system)
111
+ throw new Error('REVOKE_SYSTEM');
112
+ if (cur.cooldown_until)
113
+ throw new Error('REVOKE_ALREADY'); // 已在撤销冷却,不再没收一次
114
+ const stakeAmt = cur.stake_amount || 0;
115
+ const f = Math.round(stakeAmt * 0.5 * 100) / 100;
116
+ db.prepare("UPDATE wallets SET staked = staked - ? WHERE user_id = ?").run(stakeAmt, targetId);
117
+ if (stakeAmt > f)
118
+ db.prepare("UPDATE wallets SET balance = balance + ? WHERE user_id = ?").run(stakeAmt - f, targetId);
119
+ db.prepare("DELETE FROM verifier_whitelist WHERE user_id = ?").run(targetId);
120
+ db.prepare(`INSERT INTO verifier_whitelist (user_id, note, tier, daily_quota, cooldown_until, is_system) VALUES (?,?,?,?,?,0)`)
121
+ .run(targetId, `撤销冷却中: ${reason || ''}`, 'trial-1', 0, cooldownUntil);
122
+ return f;
123
+ })();
124
+ }
125
+ catch (e) {
126
+ const msg = e.message;
127
+ if (msg === 'REVOKE_GONE')
128
+ return void res.json({ error: '该用户不在白名单' });
129
+ if (msg === 'REVOKE_SYSTEM')
130
+ return void res.json({ error: '系统兜底账户不可撤销' });
131
+ if (msg === 'REVOKE_ALREADY')
132
+ return void res.json({ error: '该用户已在撤销冷却中' });
133
+ console.error('[verifier revoke tx]', msg);
134
+ return void res.status(500).json({ error: '撤销失败,请重试' });
103
135
  }
104
- db.prepare("DELETE FROM verifier_whitelist WHERE user_id = ?").run(targetId);
105
- // 用 cooldown 记录在 verifier_stats 上(应用层兼容)
106
- db.prepare(`INSERT INTO verifier_whitelist (user_id, note, tier, daily_quota, cooldown_until, is_system) VALUES (?,?,?,?,?,0)`)
107
- .run(targetId, `撤销冷却中: ${reason || ''}`, 'trial-1', 0, cooldownUntil);
108
136
  logAdminAction(admin.id, 'revoke_verifier', 'user', targetId, { reason: reason || null, forfeit, cooldown_until: cooldownUntil });
109
137
  res.json({ success: true, cooldown_until: cooldownUntil, forfeit });
110
138
  });
@@ -1,5 +1,7 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerAdminWalletOpsRoutes(app, deps) {
2
- const { db, requireProtocolAdmin, adminAuth, getPublicClient, getUsdcAddr, getUsdcAbi, getHotWalletAddr, wazToUsdc, getIsMainnet, getNetwork, executeWithdrawal } = deps;
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll),不再直接用 deps.db
4
+ const { requireProtocolAdmin, adminAuth, getPublicClient, getUsdcAddr, getUsdcAbi, getHotWalletAddr, wazToUsdc, getIsMainnet, getNetwork, executeWithdrawal } = deps;
3
5
  // P2-5: protocol 权限(区域 admin 看不到全局热钱包)
4
6
  app.get('/api/admin/hot-wallet/status', async (req, res) => {
5
7
  const admin = requireProtocolAdmin(req, res);
@@ -12,7 +14,7 @@ export function registerAdminWalletOpsRoutes(app, deps) {
12
14
  address: getUsdcAddr(), abi: getUsdcAbi(), functionName: 'balanceOf', args: [hw],
13
15
  });
14
16
  const ethBal = await pc.getBalance({ address: hw });
15
- const pending = db.prepare("SELECT COALESCE(SUM(amount), 0) as t FROM withdrawal_requests WHERE status = 'pending'").get();
17
+ const pending = (await dbOne("SELECT COALESCE(SUM(amount), 0) as t FROM withdrawal_requests WHERE status = 'pending'"));
16
18
  const pendingUsdc = wazToUsdc(Number(pending.t));
17
19
  res.json({
18
20
  address: hw,
@@ -45,14 +47,14 @@ export function registerAdminWalletOpsRoutes(app, deps) {
45
47
  res.json({ address: hw, usdc_balance: null, error: e.message });
46
48
  }
47
49
  });
48
- app.get('/api/admin/withdrawals', (req, res) => {
50
+ app.get('/api/admin/withdrawals', async (req, res) => {
49
51
  if (!adminAuth(req, res))
50
52
  return;
51
- const list = db.prepare(`
53
+ const list = await dbAll(`
52
54
  SELECT wr.*, u.name as user_name
53
55
  FROM withdrawal_requests wr JOIN users u ON wr.user_id = u.id
54
56
  WHERE wr.status = 'pending' ORDER BY wr.created_at ASC
55
- `).all();
57
+ `);
56
58
  res.json(list);
57
59
  });
58
60
  app.post('/api/admin/withdrawals/:id/approve', async (req, res) => {
@@ -163,33 +163,57 @@ ${webazFormatted.length > 0 ? JSON.stringify(webazFormatted.map(p => ({
163
163
  const now = new Date();
164
164
  const expiresAt = new Date(now.getTime() + 10 * 60_000);
165
165
  sessionToken = generateId('pst');
166
- db.prepare(`INSERT INTO price_sessions (token, product_id, user_id, price, quantity, created_at, expires_at) VALUES (?,?,?,?,1,?,?)`)
167
- .run(sessionToken, product.id, user.id, product.price, now.toISOString(), expiresAt.toISOString());
168
- verifiedPrice = product.price;
169
166
  const oId = generateId('ord');
170
167
  const totalAmount = product.price;
171
168
  const seller = db.prepare('SELECT id FROM users WHERE id = ?').get(product.seller_id);
172
- db.prepare(`INSERT INTO orders (
173
- id, product_id, buyer_id, seller_id, quantity, unit_price, total_amount, escrow_amount,
174
- status, shipping_address, notes, pay_deadline, accept_deadline, ship_deadline,
175
- pickup_deadline, delivery_deadline, confirm_deadline
176
- ) VALUES (?,?,?,?,1,?,?,?,'created',?,?,?,?,?,?,?,?)`).run(oId, product.id, user.id, seller.id, totalAmount, totalAmount, totalAmount, shipping_address, `[智能下单] ${decision.reason}`, addHours(now, 24), addHours(now, 48), addHours(now, 120), addHours(now, 168), addHours(now, 336), addHours(now, 408));
177
- db.prepare('UPDATE wallets SET balance = balance - ?, escrowed = escrowed + ? WHERE user_id = ?')
178
- .run(totalAmount, totalAmount, user.id);
179
- db.prepare('UPDATE products SET stock = stock - 1 WHERE id = ?').run(product.id);
180
- checkStockAndMaybeDelist(String(product.id));
181
- db.prepare(`UPDATE price_sessions SET used_at = datetime('now') WHERE token = ?`).run(sessionToken);
182
- transition(db, oId, 'paid', user.id, [], '智能下单:模拟支付完成');
183
- notifyTransition(db, oId, 'created', 'paid');
184
- if (shouldAutoAccept(db, oId)) {
185
- const sys = db.prepare("SELECT id FROM users WHERE id = 'sys_protocol'").get();
186
- if (sys) {
187
- const ar = transition(db, oId, 'accepted', sys.id, [], '⚡ auto_accept Skill 自动接单');
188
- if (ar.success)
189
- notifyTransition(db, oId, 'paid', 'accepted');
169
+ // 原子核心:余额扣款(balance>=amount 守卫)+ 库存 CAS(stock>=1)+ 建单 + 价格锁,任一 changes!==1 抛回滚整笔。
170
+ // 注:transition() 自带 db.transaction,不能嵌套进来(better-sqlite3 禁套娃);故状态推进 + 通知放 tx 提交后,
171
+ // 与原顺序一致(原本就是 insert(created) 扣款 transition(paid))。守卫杜绝并发超卖/超扣 + 半写(Phase 3 pg 安全)。
172
+ let committed = false;
173
+ try {
174
+ db.transaction(() => {
175
+ const deb = db.prepare('UPDATE wallets SET balance = balance - ?, escrowed = escrowed + ? WHERE user_id = ? AND balance >= ?')
176
+ .run(totalAmount, totalAmount, user.id, totalAmount);
177
+ if (deb.changes !== 1)
178
+ throw new Error('AGENTBUY_INSUFFICIENT_BALANCE');
179
+ const dec = db.prepare('UPDATE products SET stock = stock - 1 WHERE id = ? AND stock >= 1').run(product.id);
180
+ if (dec.changes !== 1)
181
+ throw new Error('AGENTBUY_OUT_OF_STOCK');
182
+ db.prepare(`INSERT INTO price_sessions (token, product_id, user_id, price, quantity, created_at, expires_at) VALUES (?,?,?,?,1,?,?)`)
183
+ .run(sessionToken, product.id, user.id, product.price, now.toISOString(), expiresAt.toISOString());
184
+ db.prepare(`INSERT INTO orders (
185
+ id, product_id, buyer_id, seller_id, quantity, unit_price, total_amount, escrow_amount,
186
+ status, shipping_address, notes, pay_deadline, accept_deadline, ship_deadline,
187
+ pickup_deadline, delivery_deadline, confirm_deadline
188
+ ) VALUES (?,?,?,?,1,?,?,?,'created',?,?,?,?,?,?,?,?)`).run(oId, product.id, user.id, seller.id, totalAmount, totalAmount, totalAmount, shipping_address, `[智能下单] ${decision.reason}`, addHours(now, 24), addHours(now, 48), addHours(now, 120), addHours(now, 168), addHours(now, 336), addHours(now, 408));
189
+ db.prepare(`UPDATE price_sessions SET used_at = datetime('now') WHERE token = ?`).run(sessionToken);
190
+ committed = true;
191
+ })();
192
+ }
193
+ catch (e) {
194
+ const m = e.message;
195
+ if (m !== 'AGENTBUY_INSUFFICIENT_BALANCE' && m !== 'AGENTBUY_OUT_OF_STOCK')
196
+ throw e;
197
+ // 并发售罄 / 余额已变 → 不下单(auto_bought=false),在 reason 里说明
198
+ sessionToken = null;
199
+ decision.reason = (decision.reason || '') + (m === 'AGENTBUY_OUT_OF_STOCK' ? ' · auto_buy 跳过:商品已售罄' : ' · auto_buy 跳过:余额不足');
200
+ }
201
+ if (committed) {
202
+ // tx 提交后:状态推进 + 通知(transition 自带事务,故置于此)
203
+ checkStockAndMaybeDelist(String(product.id));
204
+ transition(db, oId, 'paid', user.id, [], '智能下单:模拟支付完成');
205
+ notifyTransition(db, oId, 'created', 'paid');
206
+ if (shouldAutoAccept(db, oId)) {
207
+ const sys = db.prepare("SELECT id FROM users WHERE id = 'sys_protocol'").get();
208
+ if (sys) {
209
+ const ar = transition(db, oId, 'accepted', sys.id, [], '⚡ auto_accept Skill 自动接单');
210
+ if (ar.success)
211
+ notifyTransition(db, oId, 'paid', 'accepted');
212
+ }
190
213
  }
214
+ verifiedPrice = product.price;
215
+ orderId = oId;
191
216
  }
192
- orderId = oId;
193
217
  }
194
218
  }
195
219
  }