@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,20 +1,24 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerVerifyTasksRoutes(app, deps) {
3
+ // 只读/单写站点走 RFC-016 异步 seam;db 保留:submit 是"提交→封顶→结算"裁决资金路径,
4
+ // 提交 CAS + 计票 + seal-CAS 必须原子(db.transaction);settleTask(发奖/扣权,server.ts 无状态门)
5
+ // 在 tx 提交后只对真正封顶的那一票触发,防并发双结算双发奖。Phase 3 迁 pg 行锁。
2
6
  const { db, auth, assignVerifiers, settleTask, getVerifierStats } = deps;
3
7
  // 卖家确认:已在原平台添加验证码 → 任务进入分配池
4
- app.post('/api/verify-tasks/:id/confirm', (req, res) => {
8
+ app.post('/api/verify-tasks/:id/confirm', async (req, res) => {
5
9
  const user = auth(req, res);
6
10
  if (!user)
7
11
  return;
8
- const task = db.prepare(`SELECT * FROM verify_tasks WHERE id = ? AND status IN ('code_issued','open')`).get(req.params.id);
12
+ const task = await dbOne(`SELECT * FROM verify_tasks WHERE id = ? AND status IN ('code_issued','open')`, [req.params.id]);
9
13
  if (!task)
10
14
  return void res.json({ error: '任务不存在或已结束' });
11
- const product = db.prepare('SELECT seller_id FROM products WHERE id = ?').get(task.product_id);
15
+ const product = await dbOne('SELECT seller_id FROM products WHERE id = ?', [task.product_id]);
12
16
  if (!product || product.seller_id !== user.id)
13
17
  return void res.status(403).json({ error: '无权限' });
14
18
  if (task.status === 'open') {
15
19
  return void res.json({ success: true, already_open: true, message: '任务已在验证中,无需重复确认' });
16
20
  }
17
- db.prepare(`UPDATE verify_tasks SET status='open' WHERE id=?`).run(req.params.id);
21
+ await dbRun(`UPDATE verify_tasks SET status='open' WHERE id=?`, [req.params.id]);
18
22
  try {
19
23
  assignVerifiers(req.params.id);
20
24
  }
@@ -22,26 +26,26 @@ export function registerVerifyTasksRoutes(app, deps) {
22
26
  res.json({ success: true, message: '任务已提交到验证池,等待审核员确认' });
23
27
  });
24
28
  // 卖家:查询某商品的进行中验证任务(供编辑页展示验证码)
25
- app.get('/api/verify-tasks/by-product/:productId', (req, res) => {
29
+ app.get('/api/verify-tasks/by-product/:productId', async (req, res) => {
26
30
  const user = auth(req, res);
27
31
  if (!user)
28
32
  return;
29
- const product = db.prepare('SELECT seller_id FROM products WHERE id = ?').get(req.params.productId);
33
+ const product = await dbOne('SELECT seller_id FROM products WHERE id = ?', [req.params.productId]);
30
34
  if (!product || product.seller_id !== user.id)
31
35
  return void res.status(403).json({ error: '无权限' });
32
- const tasks = db.prepare(`
36
+ const tasks = await dbAll(`
33
37
  SELECT id, type, url, code, status, expires_at, created_at,
34
38
  (SELECT COUNT(*) FROM verify_submissions WHERE task_id = verify_tasks.id AND submitted_at IS NOT NULL) as submissions_done
35
39
  FROM verify_tasks WHERE product_id = ? AND status IN ('code_issued','open') ORDER BY created_at DESC
36
- `).all(req.params.productId);
40
+ `, [req.params.productId]);
37
41
  res.json(tasks);
38
42
  });
39
43
  // 卖家:查询我发起的所有认领任务(用于"查看任务进度"页)
40
- app.get('/api/verify-tasks/my-claims', (req, res) => {
44
+ app.get('/api/verify-tasks/my-claims', async (req, res) => {
41
45
  const user = auth(req, res);
42
46
  if (!user)
43
47
  return;
44
- const tasks = db.prepare(`
48
+ const tasks = await dbAll(`
45
49
  SELECT vt.id, vt.type, vt.url, vt.code, vt.status, vt.result,
46
50
  vt.verifiers_needed, vt.expires_at, vt.created_at, vt.settled_at,
47
51
  p.title as product_title, p.id as product_id,
@@ -51,14 +55,14 @@ export function registerVerifyTasksRoutes(app, deps) {
51
55
  WHERE p.seller_id = ?
52
56
  ORDER BY vt.created_at DESC
53
57
  LIMIT 30
54
- `).all(user.id);
58
+ `, [user.id]);
55
59
  res.json(tasks);
56
60
  });
57
- app.get('/api/verify-tasks/mine', (req, res) => {
61
+ app.get('/api/verify-tasks/mine', async (req, res) => {
58
62
  const user = auth(req, res);
59
63
  if (!user)
60
64
  return;
61
- const tasks = db.prepare(`
65
+ const tasks = await dbAll(`
62
66
  SELECT vt.id, vt.type, vt.url, vt.verifiers_needed, vt.reward_per_verifier, vt.expires_at,
63
67
  vs.id as sub_id, vs.submitted_at, vs.verdict,
64
68
  (SELECT COUNT(*) FROM verify_submissions WHERE task_id = vt.id AND submitted_at IS NOT NULL) as submissions_done
@@ -66,40 +70,60 @@ export function registerVerifyTasksRoutes(app, deps) {
66
70
  JOIN verify_submissions vs ON vs.task_id = vt.id AND vs.verifier_id = ?
67
71
  WHERE vt.status = 'open'
68
72
  ORDER BY vt.created_at DESC
69
- `).all(user.id);
73
+ `, [user.id]);
70
74
  const stats = getVerifierStats(user.id);
71
75
  res.json({ tasks, stats });
72
76
  });
73
77
  // 验证者:提交验证结果(填入式)
74
- app.post('/api/verify-tasks/:id/submit', (req, res) => {
78
+ app.post('/api/verify-tasks/:id/submit', async (req, res) => {
75
79
  const user = auth(req, res);
76
80
  if (!user)
77
81
  return;
78
82
  const { submission } = req.body;
79
- const sub = db.prepare(`SELECT * FROM verify_submissions WHERE task_id = ? AND verifier_id = ?`)
80
- .get(req.params.id, user.id);
83
+ const sub = await dbOne(`SELECT * FROM verify_submissions WHERE task_id = ? AND verifier_id = ?`, [req.params.id, user.id]);
81
84
  if (!sub)
82
85
  return void res.json({ error: '未分配到此任务' });
83
86
  if (sub.submitted_at)
84
87
  return void res.json({ error: '已提交过' });
85
- const task = db.prepare('SELECT * FROM verify_tasks WHERE id = ? AND status = ?').get(req.params.id, 'open');
88
+ const task = await dbOne('SELECT * FROM verify_tasks WHERE id = ? AND status = ?', [req.params.id, 'open']);
86
89
  if (!task)
87
90
  return void res.json({ error: '任务已结束或不存在' });
88
91
  if (new Date(task.expires_at) < new Date())
89
92
  return void res.json({ error: '任务已过期' });
90
- db.prepare(`UPDATE verify_submissions SET submission=?, submitted_at=datetime('now') WHERE task_id=? AND verifier_id=?`)
91
- .run((submission ?? '').trim(), req.params.id, user.id);
92
- const doneCount = db.prepare(`SELECT COUNT(*) as n FROM verify_submissions WHERE task_id = ? AND submitted_at IS NOT NULL`).get(req.params.id).n;
93
- if (doneCount >= task.verifiers_needed)
93
+ // 裁决原子段:CAS 写本验证者未提交的行(防同人并发双提交) 计票 达标则 CAS 翻 open→settling。
94
+ // 返回 didReach=true 仅给真正把任务翻到结算态的那一票。
95
+ const submissionText = (submission ?? '').trim();
96
+ let didReach = false;
97
+ try {
98
+ didReach = db.transaction(() => {
99
+ const upd = db.prepare(`UPDATE verify_submissions SET submission=?, submitted_at=datetime('now') WHERE task_id=? AND verifier_id=? AND submitted_at IS NULL`)
100
+ .run(submissionText, req.params.id, user.id);
101
+ if (upd.changes === 0)
102
+ throw new Error('ALREADY_SUBMITTED');
103
+ const doneCount = db.prepare(`SELECT COUNT(*) as n FROM verify_submissions WHERE task_id = ? AND submitted_at IS NOT NULL`).get(req.params.id).n;
104
+ if (doneCount < task.verifiers_needed)
105
+ return false;
106
+ const seal = db.prepare(`UPDATE verify_tasks SET status='settling' WHERE id=? AND status='open'`).run(req.params.id);
107
+ return seal.changes === 1;
108
+ })();
109
+ }
110
+ catch (e) {
111
+ if (e.message === 'ALREADY_SUBMITTED')
112
+ return void res.json({ error: '已提交过' });
113
+ console.error('[verify-tasks submit tx]', e.message);
114
+ return void res.status(500).json({ error: '提交失败,请重试' });
115
+ }
116
+ // 结算在事务提交后只对触发封顶的那一票执行(settleTask 自身写 status='settled' + 发奖)。
117
+ if (didReach)
94
118
  settleTask(req.params.id);
95
119
  res.json({ success: true, message: '提交成功,等待其他验证者完成后自动结算' });
96
120
  });
97
121
  // 公开验证大厅 — 仅显示分配给我的未提交任务
98
- app.get('/api/verify-tasks/open', (req, res) => {
122
+ app.get('/api/verify-tasks/open', async (req, res) => {
99
123
  const user = auth(req, res);
100
124
  if (!user)
101
125
  return;
102
- const tasks = db.prepare(`
126
+ const tasks = await dbAll(`
103
127
  SELECT vt.id, vt.type, vt.url, vt.reward_per_verifier, vt.expires_at,
104
128
  (SELECT COUNT(*) FROM verify_submissions WHERE task_id=vt.id AND submitted_at IS NOT NULL) as done,
105
129
  vt.verifiers_needed
@@ -108,7 +132,7 @@ export function registerVerifyTasksRoutes(app, deps) {
108
132
  WHERE vt.status = 'open'
109
133
  ORDER BY vt.created_at ASC
110
134
  LIMIT 10
111
- `).all(user.id);
135
+ `, [user.id]);
112
136
  res.json(tasks);
113
137
  });
114
138
  app.get('/api/verify-stats', (req, res) => {
@@ -1,12 +1,14 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerWaitlistRoutes(app, deps) {
2
- const { db, auth, isTrustedRole, errorRes } = deps;
3
- app.post('/api/products/:product_id/waitlist', (req, res) => {
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
4
+ const { auth, isTrustedRole, errorRes } = deps;
5
+ app.post('/api/products/:product_id/waitlist', async (req, res) => {
4
6
  const user = auth(req, res);
5
7
  if (!user)
6
8
  return;
7
9
  if (isTrustedRole(user))
8
10
  return void errorRes(res, 403, 'TRUSTED_ROLE_NO_TRADE', '受信角色无购物功能');
9
- const p = db.prepare('SELECT id, seller_id, status, stock FROM products WHERE id = ?').get(req.params.product_id);
11
+ const p = await dbOne('SELECT id, seller_id, status, stock FROM products WHERE id = ?', [req.params.product_id]);
10
12
  if (!p)
11
13
  return void res.status(404).json({ error: '商品不存在' });
12
14
  if (p.seller_id === user.id)
@@ -17,22 +19,21 @@ export function registerWaitlistRoutes(app, deps) {
17
19
  return void res.status(400).json({ error: '商品有货,无需排队 — 直接下单即可' });
18
20
  const qty = Math.max(1, Math.min(99, Number(req.body?.desired_qty) || 1));
19
21
  const note = req.body?.note ? String(req.body.note).slice(0, 200) : null;
20
- db.prepare(`INSERT OR REPLACE INTO product_waitlist (user_id, product_id, desired_qty, note) VALUES (?,?,?,?)`)
21
- .run(user.id, req.params.product_id, qty, note);
22
+ await dbRun(`INSERT OR REPLACE INTO product_waitlist (user_id, product_id, desired_qty, note) VALUES (?,?,?,?)`, [user.id, req.params.product_id, qty, note]);
22
23
  res.json({ success: true });
23
24
  });
24
- app.delete('/api/products/:product_id/waitlist', (req, res) => {
25
+ app.delete('/api/products/:product_id/waitlist', async (req, res) => {
25
26
  const user = auth(req, res);
26
27
  if (!user)
27
28
  return;
28
- db.prepare('DELETE FROM product_waitlist WHERE user_id = ? AND product_id = ?').run(user.id, req.params.product_id);
29
+ await dbRun('DELETE FROM product_waitlist WHERE user_id = ? AND product_id = ?', [user.id, req.params.product_id]);
29
30
  res.json({ success: true });
30
31
  });
31
- app.get('/api/waitlist', (req, res) => {
32
+ app.get('/api/waitlist', async (req, res) => {
32
33
  const user = auth(req, res);
33
34
  if (!user)
34
35
  return;
35
- const rows = db.prepare(`
36
+ const rows = await dbAll(`
36
37
  SELECT w.product_id, w.desired_qty, w.note, w.notified_at, w.created_at,
37
38
  p.title, p.price, p.stock, p.status as product_status, p.category,
38
39
  u.name as seller_name, u.handle as seller_handle
@@ -41,25 +42,25 @@ export function registerWaitlistRoutes(app, deps) {
41
42
  JOIN users u ON u.id = p.seller_id
42
43
  WHERE w.user_id = ?
43
44
  ORDER BY w.created_at DESC LIMIT 200
44
- `).all(user.id);
45
+ `, [user.id]);
45
46
  res.json({ items: rows });
46
47
  });
47
- app.get('/api/products/:product_id/waitlist/check', (req, res) => {
48
+ app.get('/api/products/:product_id/waitlist/check', async (req, res) => {
48
49
  const user = auth(req, res);
49
50
  if (!user)
50
51
  return;
51
- const exists = db.prepare('SELECT 1 FROM product_waitlist WHERE user_id = ? AND product_id = ?').get(user.id, req.params.product_id);
52
+ const exists = await dbOne('SELECT 1 FROM product_waitlist WHERE user_id = ? AND product_id = ?', [user.id, req.params.product_id]);
52
53
  res.json({ in_waitlist: !!exists });
53
54
  });
54
55
  // seller 查 waitlist count(决定备多少货)
55
- app.get('/api/products/:product_id/waitlist/count', (req, res) => {
56
+ app.get('/api/products/:product_id/waitlist/count', async (req, res) => {
56
57
  const user = auth(req, res);
57
58
  if (!user)
58
59
  return;
59
- const p = db.prepare('SELECT seller_id FROM products WHERE id = ?').get(req.params.product_id);
60
+ const p = await dbOne('SELECT seller_id FROM products WHERE id = ?', [req.params.product_id]);
60
61
  if (!p || p.seller_id !== user.id)
61
62
  return void res.status(403).json({ error: '仅卖家可看' });
62
- const r = db.prepare(`SELECT COUNT(*) as cnt, COALESCE(SUM(desired_qty), 0) as total_qty FROM product_waitlist WHERE product_id = ? AND notified_at IS NULL`).get(req.params.product_id);
63
+ const r = (await dbOne(`SELECT COUNT(*) as cnt, COALESCE(SUM(desired_qty), 0) as total_qty FROM product_waitlist WHERE product_id = ? AND notified_at IS NULL`, [req.params.product_id]));
63
64
  res.json({ pending_users: r.cnt, total_desired: r.total_qty });
64
65
  });
65
66
  }
@@ -1,18 +1,20 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerWalletReadRoutes(app, deps) {
2
- const { db, auth, isTrustedRole, generateId, verifyPassword, deriveDepositAddress, getProtocolParam, getPublicClient, getIsMainnet, getActiveChainId, getUsdcContract, getNetwork } = deps;
3
+ // db 已全量走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
4
+ const { auth, isTrustedRole, generateId, verifyPassword, deriveDepositAddress, getProtocolParam, getPublicClient, getIsMainnet, getActiveChainId, getUsdcContract, getNetwork } = deps;
3
5
  // 钱包状态
4
- app.get('/api/wallet', (req, res) => {
6
+ app.get('/api/wallet', async (req, res) => {
5
7
  const user = auth(req, res);
6
8
  if (!user)
7
9
  return;
8
10
  if (isTrustedRole(user))
9
11
  return void res.status(403).json({ error: '受信角色无钱包', error_code: 'TRUSTED_ROLE_NO_WALLET' });
10
- const wallet = db.prepare('SELECT * FROM wallets WHERE user_id = ?').get(user.id);
12
+ const wallet = await dbOne('SELECT * FROM wallets WHERE user_id = ?', [user.id]);
11
13
  if (!wallet)
12
14
  return void res.status(500).json({ error: '钱包记录缺失', error_code: 'WALLET_MISSING' });
13
15
  if (!wallet.deposit_address) {
14
16
  const addr = deriveDepositAddress(user.id);
15
- db.prepare('UPDATE wallets SET deposit_address = ? WHERE user_id = ?').run(addr, user.id);
17
+ await dbRun('UPDATE wallets SET deposit_address = ? WHERE user_id = ?', [addr, user.id]);
16
18
  wallet.deposit_address = addr;
17
19
  }
18
20
  res.json(wallet);
@@ -22,7 +24,7 @@ export function registerWalletReadRoutes(app, deps) {
22
24
  const user = auth(req, res);
23
25
  if (!user)
24
26
  return;
25
- const wallet = db.prepare('SELECT deposit_address FROM wallets WHERE user_id = ?').get(user.id);
27
+ const wallet = await dbOne('SELECT deposit_address FROM wallets WHERE user_id = ?', [user.id]);
26
28
  if (!wallet?.deposit_address)
27
29
  return void res.status(404).json({ error: '充值地址未生成' });
28
30
  try {
@@ -52,16 +54,16 @@ export function registerWalletReadRoutes(app, deps) {
52
54
  });
53
55
  });
54
56
  // 白名单 GET / POST / DELETE
55
- app.get('/api/wallet/whitelist', (req, res) => {
57
+ app.get('/api/wallet/whitelist', async (req, res) => {
56
58
  const user = auth(req, res);
57
59
  if (!user)
58
60
  return;
59
- const rows = db.prepare(`
61
+ const rows = await dbAll(`
60
62
  SELECT id, address, label, added_at, activates_at
61
63
  FROM withdrawal_whitelist
62
64
  WHERE user_id = ? AND revoked_at IS NULL
63
65
  ORDER BY added_at DESC
64
- `).all(user.id);
66
+ `, [user.id]);
65
67
  const now = Date.now();
66
68
  res.json({
67
69
  whitelist: rows.map(r => ({
@@ -70,7 +72,7 @@ export function registerWalletReadRoutes(app, deps) {
70
72
  })),
71
73
  });
72
74
  });
73
- app.post('/api/wallet/whitelist', (req, res) => {
75
+ app.post('/api/wallet/whitelist', async (req, res) => {
74
76
  const user = auth(req, res);
75
77
  if (!user)
76
78
  return;
@@ -85,39 +87,38 @@ export function registerWalletReadRoutes(app, deps) {
85
87
  return void res.json({ error: '请输入有效的以太坊地址' });
86
88
  // P0-1: 统一小写存储
87
89
  const address = addressRaw.toLowerCase();
88
- const existing = db.prepare(`SELECT id, revoked_at FROM withdrawal_whitelist WHERE user_id = ? AND address = ?`).get(user.id, address);
90
+ const existing = await dbOne(`SELECT id, revoked_at FROM withdrawal_whitelist WHERE user_id = ? AND address = ?`, [user.id, address]);
89
91
  if (existing && !existing.revoked_at)
90
92
  return void res.json({ error: '该地址已在白名单中' });
91
93
  const id = generateId('wl');
92
94
  const activatesAt = new Date(Date.now() + 24 * 3600_000).toISOString().slice(0, 19).replace('T', ' ');
93
95
  if (existing) {
94
- db.prepare(`UPDATE withdrawal_whitelist
96
+ await dbRun(`UPDATE withdrawal_whitelist
95
97
  SET revoked_at = NULL, added_at = datetime('now'), activates_at = ?, label = ?, id = ?
96
- WHERE user_id = ? AND address = ?`)
97
- .run(activatesAt, label || null, id, user.id, address);
98
+ WHERE user_id = ? AND address = ?`, [activatesAt, label || null, id, user.id, address]);
98
99
  }
99
100
  else {
100
- db.prepare(`INSERT INTO withdrawal_whitelist (id, user_id, address, label, activates_at)
101
- VALUES (?,?,?,?,?)`).run(id, user.id, address, label || null, activatesAt);
101
+ await dbRun(`INSERT INTO withdrawal_whitelist (id, user_id, address, label, activates_at)
102
+ VALUES (?,?,?,?,?)`, [id, user.id, address, label || null, activatesAt]);
102
103
  }
103
104
  res.json({ ok: true, id, activates_at: activatesAt, message: '地址已添加,24 小时冷却期后可用于提现' });
104
105
  });
105
- app.delete('/api/wallet/whitelist/:id', (req, res) => {
106
+ app.delete('/api/wallet/whitelist/:id', async (req, res) => {
106
107
  const user = auth(req, res);
107
108
  if (!user)
108
109
  return;
109
- const row = db.prepare(`SELECT user_id FROM withdrawal_whitelist WHERE id = ?`).get(req.params.id);
110
+ const row = await dbOne(`SELECT user_id FROM withdrawal_whitelist WHERE id = ?`, [req.params.id]);
110
111
  if (!row || row.user_id !== user.id)
111
112
  return void res.status(404).json({ error: '地址不存在' });
112
- db.prepare(`UPDATE withdrawal_whitelist SET revoked_at = datetime('now') WHERE id = ?`).run(req.params.id);
113
+ await dbRun(`UPDATE withdrawal_whitelist SET revoked_at = datetime('now') WHERE id = ?`, [req.params.id]);
113
114
  res.json({ ok: true });
114
115
  });
115
116
  // 我的提现记录
116
- app.get('/api/wallet/withdrawals', (req, res) => {
117
+ app.get('/api/wallet/withdrawals', async (req, res) => {
117
118
  const user = auth(req, res);
118
119
  if (!user)
119
120
  return;
120
- const list = db.prepare(`SELECT id, to_address, amount, status, created_at, tx_hash FROM withdrawal_requests WHERE user_id = ? ORDER BY created_at DESC LIMIT 10`).all(user.id);
121
+ const list = await dbAll(`SELECT id, to_address, amount, status, created_at, tx_hash FROM withdrawal_requests WHERE user_id = ? ORDER BY created_at DESC LIMIT 10`, [user.id]);
121
122
  res.json(list);
122
123
  });
123
124
  // 我的充值记录(含确认进度)
@@ -140,8 +141,8 @@ export function registerWalletReadRoutes(app, deps) {
140
141
  const user = auth(req, res);
141
142
  if (!user)
142
143
  return;
143
- const list = db.prepare(`SELECT tx_hash, amount, credited_waz, block_number, swept, confirmed_at, block_at_seen, created_at
144
- FROM deposit_txns WHERE user_id = ? ORDER BY created_at DESC LIMIT 10`).all(user.id);
144
+ const list = await dbAll(`SELECT tx_hash, amount, credited_waz, block_number, swept, confirmed_at, block_at_seen, created_at
145
+ FROM deposit_txns WHERE user_id = ? ORDER BY created_at DESC LIMIT 10`, [user.id]);
145
146
  const requiredConf = getProtocolParam('usdc_required_confirmations', 12);
146
147
  let latestBlock = null;
147
148
  if (list.some(r => !r.confirmed_at)) {
@@ -159,32 +160,32 @@ export function registerWalletReadRoutes(app, deps) {
159
160
  });
160
161
  res.json(enriched);
161
162
  });
162
- // 收入构成:分享 / 双轨对碰 / 销售
163
- app.get('/api/wallet/income', (req, res) => {
163
+ // 收入构成:销售 / 分享归因 / PV 记录(pre-launch,若适用)
164
+ app.get('/api/wallet/income', async (req, res) => {
164
165
  const user = auth(req, res);
165
166
  if (!user)
166
167
  return;
167
- const commByLevel = db.prepare(`
168
+ const commByLevel = await dbAll(`
168
169
  SELECT level, COUNT(*) as cnt, COALESCE(SUM(amount),0) as total
169
170
  FROM commission_records WHERE beneficiary_id = ? GROUP BY level
170
- `).all(user.id);
171
+ `, [user.id]);
171
172
  const commMap = { l1: { count: 0, total: 0 }, l2: { count: 0, total: 0 }, l3: { count: 0, total: 0 } };
172
173
  for (const r of commByLevel) {
173
174
  const key = `l${r.level}`;
174
175
  if (commMap[key])
175
176
  commMap[key] = { count: r.cnt, total: Number(r.total.toFixed(2)) };
176
177
  }
177
- const binary = db.prepare(`
178
+ const binary = (await dbOne(`
178
179
  SELECT
179
180
  COUNT(CASE WHEN settled_at IS NOT NULL THEN 1 END) as settled_cnt,
180
181
  COALESCE(SUM(CASE WHEN settled_at IS NOT NULL THEN waz_amount END), 0) as settled_waz,
181
182
  COALESCE(SUM(CASE WHEN settled_at IS NULL THEN score END), 0) as pending_score
182
183
  FROM binary_score_records WHERE user_id = ?
183
- `).get(user.id);
184
- const sales = db.prepare(`
184
+ `, [user.id]));
185
+ const sales = (await dbOne(`
185
186
  SELECT COUNT(*) as cnt, COALESCE(SUM(total_amount),0) as total
186
187
  FROM orders WHERE seller_id = ? AND status = 'completed'
187
- `).get(user.id);
188
+ `, [user.id]));
188
189
  const totalIncome = commMap.l1.total + commMap.l2.total + commMap.l3.total +
189
190
  Number(binary.settled_waz) + Number(sales.total);
190
191
  res.json({
@@ -199,20 +200,57 @@ export function registerWalletReadRoutes(app, deps) {
199
200
  });
200
201
  });
201
202
  // P0 测试充值(Phase 0 专用)
202
- app.post('/api/wallet/topup', (req, res) => {
203
+ // 测试水龙头(faucet)— 资金铸造路径,fail-safe **默认关闭**(Codex #187/#222 / task #1128)。
204
+ // 1) 环境门禁(见 isFaucetAllowed):**绝不靠 NODE_ENV 的默认值**关闭铸币路径 —— 本仓库不设 NODE_ENV,
205
+ // 生产全靠 Railway 面板注入;旧代码 `NODE_ENV || 'development'` 若漏设会把 faucet 在生产打开。
206
+ // 现在:显式 production → 永远关;部署平台(Railway 注入 RAILWAY_ENVIRONMENT)→ 默认关,
207
+ // 仅显式 WEBAZ_ENABLE_TEST_FAUCET=1 才开;本地(无平台信号 + dev/test/未设)→ 开。
208
+ // 2) cap 原子化:单条 `balance = MIN(5000, balance + ?)`,并发/Phase 3 pg 下都不会越过 5000。
209
+ // 3) Phase 3 资金路径:迁 pg 时应移到 wallet-write + SELECT...FOR UPDATE 行锁。
210
+ const FAUCET_ALLOWED = isFaucetAllowed({
211
+ nodeEnv: process.env.NODE_ENV,
212
+ onDeployPlatform: !!(process.env.RAILWAY_ENVIRONMENT || process.env.RAILWAY_PROJECT_ID || process.env.RAILWAY_SERVICE_ID),
213
+ explicitEnableFlag: process.env.WEBAZ_ENABLE_TEST_FAUCET === '1',
214
+ });
215
+ app.post('/api/wallet/topup', async (req, res) => {
203
216
  const user = auth(req, res);
204
217
  if (!user)
205
218
  return;
219
+ if (!FAUCET_ALLOWED)
220
+ return void res.status(403).json({ error: '充值水龙头仅在测试环境开放', error_code: 'FAUCET_DISABLED' });
206
221
  if (isTrustedRole(user))
207
222
  return void res.status(403).json({ error: '受信角色无钱包', error_code: 'TRUSTED_ROLE_NO_WALLET' });
208
223
  const amount = Math.min(1000, Math.max(1, Number(req.body?.amount) || 500));
209
- const wallet = db.prepare('SELECT balance FROM wallets WHERE user_id = ?').get(user.id);
210
- if (!wallet)
224
+ const before = await dbOne('SELECT balance FROM wallets WHERE user_id = ?', [user.id]);
225
+ if (!before)
211
226
  return void res.status(500).json({ error: '钱包记录缺失', error_code: 'WALLET_MISSING' });
212
- if (wallet.balance >= 5000)
227
+ if (before.balance >= 5000)
213
228
  return void res.json({ error: '余额已达上限 5000 WAZ,无需充值' });
214
- const actual = Math.min(amount, 5000 - wallet.balance);
215
- db.prepare('UPDATE wallets SET balance = balance + ? WHERE user_id = ?').run(actual, user.id);
216
- res.json({ success: true, added: actual, new_balance: wallet.balance + actual });
229
+ // 原子封顶:无论并发,balance 永不超 5000(MIN 在单条 UPDATE 内对当前值求值)
230
+ await dbRun('UPDATE wallets SET balance = MIN(5000, balance + ?) WHERE user_id = ?', [amount, user.id]);
231
+ const after = (await dbOne('SELECT balance FROM wallets WHERE user_id = ?', [user.id])).balance;
232
+ res.json({ success: true, added: Math.round((after - before.balance) * 100) / 100, new_balance: after, capped: after >= 5000 });
217
233
  });
218
234
  }
235
+ /**
236
+ * Faucet (WAZ mint path) gate — fail-safe DEFAULT-CLOSED (task #1128 / Codex #187/#222).
237
+ *
238
+ * The repo never sets NODE_ENV; production relies on the Railway dashboard injecting it. So a
239
+ * mint path must NOT lean on a `NODE_ENV || 'development'` default to stay shut — an unset
240
+ * NODE_ENV in prod would open it. Rules (in order):
241
+ * 1. explicit NODE_ENV==='production' → CLOSED (even if the enable flag is mis-set).
242
+ * 2. explicit enable flag (WEBAZ_ENABLE_TEST_FAUCET=1) → OPEN (staging/test boxes opt in).
243
+ * 3. on a deploy platform (Railway injects RAILWAY_ENVIRONMENT/PROJECT/SERVICE) without the
244
+ * flag → CLOSED — this is the fail-safe for a misconfigured/unset NODE_ENV in prod.
245
+ * 4. off-platform (local dev) → OPEN only for unset / 'development' / 'test'; any other
246
+ * explicit value → CLOSED.
247
+ */
248
+ export function isFaucetAllowed(e) {
249
+ if (e.nodeEnv === 'production')
250
+ return false;
251
+ if (e.explicitEnableFlag)
252
+ return true;
253
+ if (e.onDeployPlatform)
254
+ return false;
255
+ return e.nodeEnv === undefined || e.nodeEnv === 'development' || e.nodeEnv === 'test';
256
+ }
@@ -1,4 +1,5 @@
1
1
  import { verifyMessage } from 'viem';
2
+ import { dbOne, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
2
3
  export function registerWalletWriteRoutes(app, deps) {
3
4
  const { db, auth, isTrustedRole, generateId, getProtocolParam, consumeGateToken, issueCode, findActiveCode, maskEmail, LARGE_WITHDRAW_THRESHOLD } = deps;
4
5
  // Wave G-1: 签名挑战 — 5min 一次性 nonce
@@ -59,20 +60,20 @@ export function registerWalletWriteRoutes(app, deps) {
59
60
  }
60
61
  // 已通过签名校验 → 加入白名单,免 24h 冷却(activates_at = NOW)
61
62
  const addrLc = String(address).toLowerCase();
62
- const existing = db.prepare('SELECT id, revoked_at FROM withdrawal_whitelist WHERE user_id = ? AND address = ?').get(user.id, addrLc);
63
+ const existing = await dbOne('SELECT id, revoked_at FROM withdrawal_whitelist WHERE user_id = ? AND address = ?', [user.id, addrLc]);
63
64
  if (existing) {
64
- db.prepare(`UPDATE withdrawal_whitelist SET activates_at = datetime('now'), revoked_at = NULL,
65
- signature_verified_at = datetime('now'), label = COALESCE(?, label) WHERE id = ?`)
66
- .run(label || null, existing.id);
65
+ await dbRun(`UPDATE withdrawal_whitelist SET activates_at = datetime('now'), revoked_at = NULL,
66
+ signature_verified_at = datetime('now'), label = COALESCE(?, label) WHERE id = ?`, [label || null, existing.id]);
67
67
  return void res.json({ success: true, id: existing.id, activated: true });
68
68
  }
69
69
  const id = generateId('wl');
70
- db.prepare(`INSERT INTO withdrawal_whitelist (id, user_id, address, label, activates_at, signature_verified_at)
71
- VALUES (?,?,?,?,datetime('now'),datetime('now'))`)
72
- .run(id, user.id, addrLc, label ? String(label).slice(0, 30) : null);
70
+ await dbRun(`INSERT INTO withdrawal_whitelist (id, user_id, address, label, activates_at, signature_verified_at)
71
+ VALUES (?,?,?,?,datetime('now'),datetime('now'))`, [id, user.id, addrLc, label ? String(label).slice(0, 30) : null]);
73
72
  res.json({ success: true, id, activated: true });
74
73
  });
75
74
  // 提现申请
75
+ // RFC-016: withdraw + confirm 是铁律资金转出路径,余额扣减是裸顺序写(非 db.transaction)。
76
+ // 保持整体同步,Phase 3 随资金路径整体迁 pg(BEGIN + SELECT...FOR UPDATE 行锁),不在此引入 await 间隙。
76
77
  app.post('/api/wallet/withdraw', (req, res) => {
77
78
  const user = auth(req, res);
78
79
  if (!user)
@@ -129,8 +130,10 @@ export function registerWalletWriteRoutes(app, deps) {
129
130
  // 资金转出 = 真人在场(spec §4 铁律,与 vote/arbitrate/agent_revoke 同档)。
130
131
  // email-OTP 在 agent 威胁模型下不足(agent 可读监护人收件箱);故弃用旧的"非 Passkey → email 兜底"路径。
131
132
  // 未注册 Passkey 的账户:不能提现,先去「安全」绑 Passkey(pre-launch 0 真用户,推动资金操作 Passkey 化)。
132
- const hpEnabled = Number(getProtocolParam('require_human_presence_for_withdraw', 1)) === 1;
133
- if (hpEnabled) {
133
+ // Codex #100 P1:提现真人 Passkey 是【铁律】,绝不可被任何 protocol param 关闭 → 无条件执行,不读开关。
134
+ // (旧代码 if (require_human_presence_for_withdraw===1) 让 protocol admin 把它设 0 即可绕过铁律。)
135
+ // 该 param 已锁死 value=min=max=1,仅作展示(见 server.ts DEFAULT_PARAMS + 启动迁移)。
136
+ {
134
137
  const hasPasskeyRow = db.prepare('SELECT COUNT(*) as n FROM webauthn_credentials WHERE user_id = ?').get(user.id);
135
138
  const hasPasskey = (hasPasskeyRow?.n || 0) > 0;
136
139
  if (!hasPasskey) {