@seasonkoh/webaz 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/README.md +5 -1
  2. package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
  3. package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
  4. package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
  5. package/dist/layer0-foundation/L0-1-database/db.js +65 -0
  6. package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
  7. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
  8. package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
  9. package/dist/layer1-agent/L1-1-mcp-server/server.js +288 -208
  10. package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
  11. package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
  12. package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
  13. package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
  14. package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
  15. package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +182 -0
  16. package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
  17. package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
  18. package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +11 -3
  19. package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
  20. package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
  21. package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
  22. package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
  23. package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
  24. package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
  25. package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
  26. package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
  27. package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
  28. package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
  29. package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
  30. package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
  31. package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
  32. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
  33. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
  34. package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
  35. package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
  36. package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
  37. package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
  38. package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
  39. package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
  40. package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +191 -0
  41. package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
  42. package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
  43. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
  44. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
  45. package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
  46. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
  47. package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
  48. package/dist/pwa/acp-feed.js +13 -1
  49. package/dist/pwa/admin-bearer-auth.js +21 -0
  50. package/dist/pwa/contract-fingerprint.js +2 -0
  51. package/dist/pwa/email-delivery.js +127 -0
  52. package/dist/pwa/endpoint-actions.js +5 -1
  53. package/dist/pwa/goal-index.js +8 -8
  54. package/dist/pwa/human-presence.js +62 -0
  55. package/dist/pwa/public/app.js +1485 -283
  56. package/dist/pwa/public/i18n.js +297 -59
  57. package/dist/pwa/public/index.html +1 -0
  58. package/dist/pwa/public/openapi.json +5 -5
  59. package/dist/pwa/public/whitepaper/en/index.html +153 -0
  60. package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
  61. package/dist/pwa/rate-limit.js +22 -0
  62. package/dist/pwa/routes/account-deletion.js +15 -13
  63. package/dist/pwa/routes/addresses.js +10 -9
  64. package/dist/pwa/routes/admin-admins.js +13 -14
  65. package/dist/pwa/routes/admin-analytics.js +109 -69
  66. package/dist/pwa/routes/admin-atomic.js +10 -4
  67. package/dist/pwa/routes/admin-catalog.js +13 -11
  68. package/dist/pwa/routes/admin-editor-picks.js +15 -10
  69. package/dist/pwa/routes/admin-events.js +5 -3
  70. package/dist/pwa/routes/admin-health.js +2 -1
  71. package/dist/pwa/routes/admin-moderation.js +50 -29
  72. package/dist/pwa/routes/admin-ops.js +35 -23
  73. package/dist/pwa/routes/admin-protocol-params.js +16 -19
  74. package/dist/pwa/routes/admin-reports.js +23 -21
  75. package/dist/pwa/routes/admin-tokenomics.js +26 -25
  76. package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
  77. package/dist/pwa/routes/admin-users-query.js +65 -53
  78. package/dist/pwa/routes/admin-verifier-flow.js +82 -41
  79. package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
  80. package/dist/pwa/routes/admin-wallet-ops.js +32 -7
  81. package/dist/pwa/routes/agent-buy.js +46 -22
  82. package/dist/pwa/routes/agent-governance.js +52 -56
  83. package/dist/pwa/routes/ai.js +7 -5
  84. package/dist/pwa/routes/analytics.js +43 -41
  85. package/dist/pwa/routes/anchors.js +19 -20
  86. package/dist/pwa/routes/announcements.js +13 -13
  87. package/dist/pwa/routes/arbitrator.js +97 -31
  88. package/dist/pwa/routes/auction.js +157 -116
  89. package/dist/pwa/routes/auth-login.js +6 -4
  90. package/dist/pwa/routes/auth-read.js +21 -10
  91. package/dist/pwa/routes/auth-register.js +111 -26
  92. package/dist/pwa/routes/auth-sessions.js +12 -11
  93. package/dist/pwa/routes/blocklist.js +16 -15
  94. package/dist/pwa/routes/build-feedback.js +10 -9
  95. package/dist/pwa/routes/build-reputation.js +6 -2
  96. package/dist/pwa/routes/build-tasks.js +45 -13
  97. package/dist/pwa/routes/buyer-feeds.js +27 -25
  98. package/dist/pwa/routes/cart.js +16 -15
  99. package/dist/pwa/routes/charity.js +212 -150
  100. package/dist/pwa/routes/chat.js +42 -43
  101. package/dist/pwa/routes/checkin-tasks.js +10 -9
  102. package/dist/pwa/routes/checkout-helpers.js +12 -10
  103. package/dist/pwa/routes/claim-initiators.js +34 -14
  104. package/dist/pwa/routes/claim-verify.js +86 -53
  105. package/dist/pwa/routes/claim-voting.js +43 -18
  106. package/dist/pwa/routes/contribution-identity.js +164 -0
  107. package/dist/pwa/routes/contribution-score.js +19 -0
  108. package/dist/pwa/routes/coupons.js +19 -16
  109. package/dist/pwa/routes/dashboards.js +18 -16
  110. package/dist/pwa/routes/dispute-cases.js +25 -24
  111. package/dist/pwa/routes/disputes-read.js +45 -51
  112. package/dist/pwa/routes/disputes-write.js +124 -61
  113. package/dist/pwa/routes/evidence.js +9 -9
  114. package/dist/pwa/routes/external-anchors.js +13 -12
  115. package/dist/pwa/routes/feedback.js +29 -33
  116. package/dist/pwa/routes/flash-sales.js +18 -16
  117. package/dist/pwa/routes/follows.js +25 -24
  118. package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
  119. package/dist/pwa/routes/governance-onboarding.js +70 -59
  120. package/dist/pwa/routes/group-buys.js +22 -22
  121. package/dist/pwa/routes/growth.js +34 -31
  122. package/dist/pwa/routes/import-product.js +12 -10
  123. package/dist/pwa/routes/kyc.js +9 -8
  124. package/dist/pwa/routes/leaderboard.js +20 -18
  125. package/dist/pwa/routes/listings.js +23 -22
  126. package/dist/pwa/routes/logistics.js +10 -8
  127. package/dist/pwa/routes/manifests.js +27 -27
  128. package/dist/pwa/routes/me-data.js +23 -21
  129. package/dist/pwa/routes/notifications.js +7 -6
  130. package/dist/pwa/routes/offers.js +30 -12
  131. package/dist/pwa/routes/orders-action.js +51 -29
  132. package/dist/pwa/routes/orders-create.js +75 -20
  133. package/dist/pwa/routes/orders-read.js +21 -20
  134. package/dist/pwa/routes/p2p-products.js +30 -18
  135. package/dist/pwa/routes/payments-governance.js +61 -56
  136. package/dist/pwa/routes/peers.js +9 -8
  137. package/dist/pwa/routes/pin-receipts.js +13 -13
  138. package/dist/pwa/routes/products-aliases.js +12 -10
  139. package/dist/pwa/routes/products-claims.js +36 -17
  140. package/dist/pwa/routes/products-create.js +53 -38
  141. package/dist/pwa/routes/products-crud.js +17 -16
  142. package/dist/pwa/routes/products-links.js +49 -26
  143. package/dist/pwa/routes/products-list.js +6 -4
  144. package/dist/pwa/routes/products-meta.js +40 -39
  145. package/dist/pwa/routes/products-update.js +19 -5
  146. package/dist/pwa/routes/profile-credentials.js +20 -19
  147. package/dist/pwa/routes/profile-identity.js +14 -13
  148. package/dist/pwa/routes/profile-location.js +7 -6
  149. package/dist/pwa/routes/profile-placement.js +20 -19
  150. package/dist/pwa/routes/profile-prefs.js +11 -11
  151. package/dist/pwa/routes/promoter.js +58 -66
  152. package/dist/pwa/routes/public-build-tasks.js +19 -0
  153. package/dist/pwa/routes/public-utils.js +108 -46
  154. package/dist/pwa/routes/push.js +16 -15
  155. package/dist/pwa/routes/ratings.js +92 -32
  156. package/dist/pwa/routes/recover-key.js +66 -26
  157. package/dist/pwa/routes/referral.js +37 -52
  158. package/dist/pwa/routes/reputation.js +3 -2
  159. package/dist/pwa/routes/returns.js +76 -73
  160. package/dist/pwa/routes/reviews.js +41 -18
  161. package/dist/pwa/routes/rewards-apply.js +16 -15
  162. package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
  163. package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
  164. package/dist/pwa/routes/rfqs.js +163 -85
  165. package/dist/pwa/routes/search.js +16 -14
  166. package/dist/pwa/routes/secondhand.js +25 -22
  167. package/dist/pwa/routes/seller-quota.js +24 -26
  168. package/dist/pwa/routes/share-redirects.js +60 -55
  169. package/dist/pwa/routes/shareables-interactions.js +34 -35
  170. package/dist/pwa/routes/shareables.js +55 -51
  171. package/dist/pwa/routes/shop-referral.js +58 -0
  172. package/dist/pwa/routes/shops.js +25 -20
  173. package/dist/pwa/routes/signaling.js +10 -9
  174. package/dist/pwa/routes/skill-market.js +16 -16
  175. package/dist/pwa/routes/skills.js +15 -14
  176. package/dist/pwa/routes/snf.js +14 -13
  177. package/dist/pwa/routes/tags.js +10 -9
  178. package/dist/pwa/routes/task-proposals.js +121 -0
  179. package/dist/pwa/routes/trial.js +72 -52
  180. package/dist/pwa/routes/trusted-kpi.js +20 -18
  181. package/dist/pwa/routes/url-claim.js +67 -28
  182. package/dist/pwa/routes/users-public.js +62 -70
  183. package/dist/pwa/routes/variants.js +12 -13
  184. package/dist/pwa/routes/verifier-user.js +61 -21
  185. package/dist/pwa/routes/verify-tasks.js +49 -25
  186. package/dist/pwa/routes/waitlist.js +16 -15
  187. package/dist/pwa/routes/wallet-read.js +75 -37
  188. package/dist/pwa/routes/wallet-write.js +12 -9
  189. package/dist/pwa/routes/webauthn.js +25 -26
  190. package/dist/pwa/routes/webhooks.js +26 -26
  191. package/dist/pwa/routes/welcome.js +45 -50
  192. package/dist/pwa/routes/wishlist-qa.js +29 -32
  193. package/dist/pwa/server.js +304 -90
  194. package/dist/version.js +1 -1
  195. package/package.json +76 -3
@@ -1,3 +1,4 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  /** 拿商品(含 variant)当前生效的 flash sale;多重叠时取价最低。orders 下单也会用。 */
2
3
  export function getActiveFlashSale(db, productId, variantId) {
3
4
  const variantClause = variantId
@@ -18,12 +19,14 @@ export function getActiveFlashSale(db, productId, variantId) {
18
19
  return db.prepare(sql).get(...args);
19
20
  }
20
21
  export function registerFlashSalesRoutes(app, deps) {
22
+ // db 仍在 destructure 中——传给同步 getActiveFlashSale(订单金钱路径消费,随 money batch 迁);
23
+ // 本文件的 handler 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun)
21
24
  const { db, generateId, auth, broadcastSystemEvent } = deps;
22
- app.post('/api/products/:product_id/flash-sale', (req, res) => {
25
+ app.post('/api/products/:product_id/flash-sale', async (req, res) => {
23
26
  const user = auth(req, res);
24
27
  if (!user)
25
28
  return;
26
- const product = db.prepare('SELECT id, seller_id, price, has_variants FROM products WHERE id = ? AND status = \'active\'').get(req.params.product_id);
29
+ const product = await dbOne('SELECT id, seller_id, price, has_variants FROM products WHERE id = ? AND status = \'active\'', [req.params.product_id]);
27
30
  if (!product)
28
31
  return void res.status(404).json({ error: '商品不存在或已下架' });
29
32
  if (product.seller_id !== user.id)
@@ -50,7 +53,7 @@ export function registerFlashSalesRoutes(app, deps) {
50
53
  if (variant_id) {
51
54
  if (!product.has_variants)
52
55
  return void res.status(400).json({ error: '该商品无规格,不可绑定 variant' });
53
- const v = db.prepare('SELECT id FROM product_variants WHERE id = ? AND product_id = ? AND is_active = 1').get(variant_id, product.id);
56
+ const v = await dbOne('SELECT id FROM product_variants WHERE id = ? AND product_id = ? AND is_active = 1', [variant_id, product.id]);
54
57
  if (!v)
55
58
  return void res.status(400).json({ error: 'variant 不存在' });
56
59
  variantId = String(variant_id);
@@ -60,15 +63,14 @@ export function registerFlashSalesRoutes(app, deps) {
60
63
  }
61
64
  const maxQty = Number.isFinite(Number(max_qty)) ? Math.max(0, Number(max_qty)) : 0;
62
65
  // 防重叠:同 product+variant 不能有进行中的促销
63
- const conflict = db.prepare(`
66
+ const conflict = await dbOne(`
64
67
  SELECT id FROM flash_sales WHERE product_id = ? AND ${variantId ? 'variant_id = ?' : 'variant_id IS NULL'}
65
68
  AND is_active = 1 AND ends_at > datetime('now') LIMIT 1
66
- `).get(...(variantId ? [product.id, variantId] : [product.id]));
69
+ `, variantId ? [product.id, variantId] : [product.id]);
67
70
  if (conflict)
68
71
  return void res.status(409).json({ error: '已有进行中的促销,请先结束', existing_id: conflict.id });
69
72
  const id = generateId('fls');
70
- db.prepare(`INSERT INTO flash_sales (id, seller_id, product_id, variant_id, sale_price, original_price, max_qty, starts_at, ends_at) VALUES (?,?,?,?,?,?,?,?,?)`)
71
- .run(id, user.id, product.id, variantId, salePrice, Number(product.price), maxQty, new Date(startsT).toISOString(), new Date(endsT).toISOString());
73
+ await dbRun(`INSERT INTO flash_sales (id, seller_id, product_id, variant_id, sale_price, original_price, max_qty, starts_at, ends_at) VALUES (?,?,?,?,?,?,?,?,?)`, [id, user.id, product.id, variantId, salePrice, Number(product.price), maxQty, new Date(startsT).toISOString(), new Date(endsT).toISOString()]);
72
74
  try {
73
75
  broadcastSystemEvent('flash_sale', '⚡', `限时促销创建 ${product.id} · ${salePrice}/${product.price} WAZ`, product.id);
74
76
  }
@@ -82,25 +84,25 @@ export function registerFlashSalesRoutes(app, deps) {
82
84
  res.json({ sale });
83
85
  });
84
86
  // seller 自己的 flash sales(全部状态)
85
- app.get('/api/sellers/me/flash-sales', (req, res) => {
87
+ app.get('/api/sellers/me/flash-sales', async (req, res) => {
86
88
  const user = auth(req, res);
87
89
  if (!user)
88
90
  return;
89
- const rows = db.prepare(`
91
+ const rows = await dbAll(`
90
92
  SELECT f.*, p.title as product_title
91
93
  FROM flash_sales f
92
94
  JOIN products p ON p.id = f.product_id
93
95
  WHERE f.seller_id = ?
94
96
  ORDER BY f.ends_at DESC LIMIT 100
95
- `).all(user.id);
97
+ `, [user.id]);
96
98
  res.json({ items: rows });
97
99
  });
98
100
  // 取消(仅 seller 自己,且未开始)
99
- app.delete('/api/flash-sales/:id', (req, res) => {
101
+ app.delete('/api/flash-sales/:id', async (req, res) => {
100
102
  const user = auth(req, res);
101
103
  if (!user)
102
104
  return;
103
- const f = db.prepare('SELECT seller_id, starts_at FROM flash_sales WHERE id = ?').get(req.params.id);
105
+ const f = await dbOne('SELECT seller_id, starts_at FROM flash_sales WHERE id = ?', [req.params.id]);
104
106
  if (!f)
105
107
  return void res.status(404).json({ error: 'flash sale 不存在' });
106
108
  if (f.seller_id !== user.id)
@@ -108,12 +110,12 @@ export function registerFlashSalesRoutes(app, deps) {
108
110
  if (new Date(f.starts_at).getTime() <= Date.now()) {
109
111
  return void res.status(400).json({ error: '已开始的促销不可取消,请设置 is_active=0 提前结束' });
110
112
  }
111
- db.prepare('DELETE FROM flash_sales WHERE id = ?').run(req.params.id);
113
+ await dbRun('DELETE FROM flash_sales WHERE id = ?', [req.params.id]);
112
114
  res.json({ success: true });
113
115
  });
114
116
  // buyer 视角:当前全平台正在进行的 flash sales(首屏 discovery)
115
- app.get('/api/flash-sales/live', (req, res) => {
116
- const rows = db.prepare(`
117
+ app.get('/api/flash-sales/live', async (req, res) => {
118
+ const rows = await dbAll(`
117
119
  SELECT f.id, f.product_id, f.variant_id, f.sale_price, f.original_price, f.ends_at, f.max_qty, f.sold_count,
118
120
  p.title, p.images, p.category,
119
121
  u.handle as seller_handle, u.name as seller_name
@@ -124,7 +126,7 @@ export function registerFlashSalesRoutes(app, deps) {
124
126
  AND f.starts_at <= datetime('now') AND f.ends_at > datetime('now')
125
127
  AND (f.max_qty = 0 OR f.sold_count < f.max_qty)
126
128
  ORDER BY f.ends_at ASC LIMIT 100
127
- `).all();
129
+ `);
128
130
  res.json({ items: rows });
129
131
  });
130
132
  }
@@ -1,31 +1,32 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerFollowsRoutes(app, deps) {
2
- const { db, auth, generateId } = deps;
3
- app.get('/api/follows/:user_id/status', (req, res) => {
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
4
+ const { auth, generateId } = deps;
5
+ app.get('/api/follows/:user_id/status', async (req, res) => {
4
6
  const user = auth(req, res);
5
7
  if (!user)
6
8
  return;
7
- const r = db.prepare("SELECT 1 FROM follows WHERE follower_id=? AND followee_id=?").get(user.id, req.params.user_id);
8
- const followers = db.prepare("SELECT COUNT(*) as n FROM follows WHERE followee_id=?").get(req.params.user_id).n;
9
- const following = db.prepare("SELECT COUNT(*) as n FROM follows WHERE follower_id=?").get(req.params.user_id).n;
9
+ const r = await dbOne("SELECT 1 FROM follows WHERE follower_id=? AND followee_id=?", [user.id, req.params.user_id]);
10
+ const followers = (await dbOne("SELECT COUNT(*) as n FROM follows WHERE followee_id=?", [req.params.user_id])).n;
11
+ const following = (await dbOne("SELECT COUNT(*) as n FROM follows WHERE follower_id=?", [req.params.user_id])).n;
10
12
  res.json({ following: !!r, followers, following_count: following });
11
13
  });
12
- app.post('/api/follows/:user_id', (req, res) => {
14
+ app.post('/api/follows/:user_id', async (req, res) => {
13
15
  const user = auth(req, res);
14
16
  if (!user)
15
17
  return;
16
18
  if (user.id === req.params.user_id)
17
19
  return void res.json({ error: '不能关注自己' });
18
- const target = db.prepare("SELECT id FROM users WHERE id=?").get(req.params.user_id);
20
+ const target = await dbOne("SELECT id FROM users WHERE id=?", [req.params.user_id]);
19
21
  if (!target)
20
22
  return void res.json({ error: '用户不存在' });
21
- const result = db.prepare("INSERT OR IGNORE INTO follows (follower_id, followee_id) VALUES (?, ?)").run(user.id, req.params.user_id);
23
+ const result = await dbRun("INSERT OR IGNORE INTO follows (follower_id, followee_id) VALUES (?, ?)", [user.id, req.params.user_id]);
22
24
  // 2026-05-24 新关注 → 通知被关注者(仅首次关注时;重复点击不重发)
23
25
  if (result.changes > 0) {
24
26
  try {
25
- const followerName = db.prepare("SELECT name, handle FROM users WHERE id = ?").get(user.id);
27
+ const followerName = await dbOne("SELECT name, handle FROM users WHERE id = ?", [user.id]);
26
28
  const display = followerName?.handle ? '@' + followerName.handle : followerName?.name || 'someone';
27
- db.prepare(`INSERT INTO notifications (id, user_id, type, title, body, order_id) VALUES (?,?,?,?,?,?)`)
28
- .run(generateId('ntf'), req.params.user_id, 'social', `🤝 新关注`, `${display} 关注了你`, null);
29
+ await dbRun(`INSERT INTO notifications (id, user_id, type, title, body, order_id) VALUES (?,?,?,?,?,?)`, [generateId('ntf'), req.params.user_id, 'social', `🤝 新关注`, `${display} 关注了你`, null]);
29
30
  }
30
31
  catch (e) {
31
32
  console.error('[follow notif]', e);
@@ -33,51 +34,51 @@ export function registerFollowsRoutes(app, deps) {
33
34
  }
34
35
  res.json({ ok: true, following: true });
35
36
  });
36
- app.delete('/api/follows/:user_id', (req, res) => {
37
+ app.delete('/api/follows/:user_id', async (req, res) => {
37
38
  const user = auth(req, res);
38
39
  if (!user)
39
40
  return;
40
- db.prepare("DELETE FROM follows WHERE follower_id=? AND followee_id=?").run(user.id, req.params.user_id);
41
+ await dbRun("DELETE FROM follows WHERE follower_id=? AND followee_id=?", [user.id, req.params.user_id]);
41
42
  res.json({ ok: true, following: false });
42
43
  });
43
- app.get('/api/follows/me', (req, res) => {
44
+ app.get('/api/follows/me', async (req, res) => {
44
45
  const user = auth(req, res);
45
46
  if (!user)
46
47
  return;
47
- const followers = db.prepare(`
48
+ const followers = await dbAll(`
48
49
  SELECT u.id, u.name, u.role, f.created_at
49
50
  FROM follows f JOIN users u ON u.id = f.follower_id
50
51
  WHERE f.followee_id = ? ORDER BY f.created_at DESC LIMIT 100
51
- `).all(user.id);
52
- const following = db.prepare(`
52
+ `, [user.id]);
53
+ const following = await dbAll(`
53
54
  SELECT u.id, u.name, u.role, f.created_at
54
55
  FROM follows f JOIN users u ON u.id = f.followee_id
55
56
  WHERE f.follower_id = ? ORDER BY f.created_at DESC LIMIT 100
56
- `).all(user.id);
57
+ `, [user.id]);
57
58
  res.json({ followers, following });
58
59
  });
59
60
  // Wave D-1: 关注卖家动态 feed — new_product + restock 合并 + 去重
60
- app.get('/api/follows/feed', (req, res) => {
61
+ app.get('/api/follows/feed', async (req, res) => {
61
62
  const user = auth(req, res);
62
63
  if (!user)
63
64
  return;
64
65
  const limit = Math.min(100, Math.max(10, Number(req.query.limit) || 50));
65
- const followees = db.prepare(`SELECT followee_id FROM follows WHERE follower_id = ?`).all(user.id);
66
+ const followees = await dbAll(`SELECT followee_id FROM follows WHERE follower_id = ?`, [user.id]);
66
67
  if (followees.length === 0)
67
68
  return void res.json({ items: [] });
68
69
  const ids = followees.map(f => f.followee_id);
69
70
  const placeholders = ids.map(() => '?').join(',');
70
71
  // 新品(近 30 天 active)
71
- const newProducts = db.prepare(`
72
+ const newProducts = await dbAll(`
72
73
  SELECT 'new_product' as type, p.created_at as ts, p.id as product_id, p.title, p.price, p.stock, p.category, p.images,
73
74
  u.id as seller_id, u.name as seller_name, u.handle as seller_handle
74
75
  FROM products p JOIN users u ON u.id = p.seller_id
75
76
  WHERE p.seller_id IN (${placeholders}) AND p.status = 'active'
76
77
  AND p.created_at > datetime('now', '-30 days')
77
78
  ORDER BY p.created_at DESC LIMIT 100
78
- `).all(...ids);
79
+ `, ids);
79
80
  // 重新上架 / 补货(近 7 天 updated_at > created_at + 1 天,stock > 0)
80
- const restocks = db.prepare(`
81
+ const restocks = await dbAll(`
81
82
  SELECT 'restock' as type, p.updated_at as ts, p.id as product_id, p.title, p.price, p.stock, p.category, p.images,
82
83
  u.id as seller_id, u.name as seller_name, u.handle as seller_handle
83
84
  FROM products p JOIN users u ON u.id = p.seller_id
@@ -86,7 +87,7 @@ export function registerFollowsRoutes(app, deps) {
86
87
  AND p.updated_at > datetime('now', '-7 days')
87
88
  AND p.updated_at > datetime(p.created_at, '+1 days')
88
89
  ORDER BY p.updated_at DESC LIMIT 30
89
- `).all(...ids);
90
+ `, ids);
90
91
  // 合并 + 去重(同 product 同时 new + restock → 优先 new)
91
92
  const seen = new Set();
92
93
  const merged = [];
@@ -1,8 +1,10 @@
1
+ // RFC-016 Phase 1 — cron 候选扫描读 → async seam;逐用户卸任的 db.transaction 写仍同步(Phase 3 迁 pg)。
2
+ import { dbAll } from '../../layer0-foundation/L0-1-database/db.js';
1
3
  /**
2
4
  * Run one auto-deactivate sweep. Returns deactivation report for caller
3
5
  * (cron caller logs to console; admin endpoint can call directly for query).
4
6
  */
5
- export function runAutoDeactivateSweep(deps) {
7
+ export async function runAutoDeactivateSweep(deps) {
6
8
  const { db, generateId, getProtocolParam } = deps;
7
9
  const thresholdCount = Number(getProtocolParam('governance_auto_deactivate_threshold_count', 5));
8
10
  const thresholdPct = Number(getProtocolParam('governance_auto_deactivate_threshold_pct', 0.3));
@@ -15,7 +17,7 @@ export function runAutoDeactivateSweep(deps) {
15
17
  // (verifier_stats is the source-of-truth for confirmed_wrong; server.ts:5387 increments it
16
18
  // on overturn, admin-verifier-flow.ts:130 decrements it on appeal success — exactly the
17
19
  // "confirmed_wrong" signal playbook §6.2 requires.)
18
- const candidates = db.prepare(`
20
+ const candidates = await dbAll(`
19
21
  SELECT u.id AS user_id, u.roles,
20
22
  vs.tasks_done, vs.tasks_wrong
21
23
  FROM users u
@@ -25,7 +27,7 @@ export function runAutoDeactivateSweep(deps) {
25
27
  AND vs.tasks_done >= ?
26
28
  AND vs.tasks_wrong >= ?
27
29
  AND (CAST(vs.tasks_wrong AS REAL) / CAST(vs.tasks_done AS REAL)) >= ?
28
- `).all(minSample, thresholdCount, thresholdPct);
30
+ `, [minSample, thresholdCount, thresholdPct]);
29
31
  const result = { scanned: candidates.length, deactivated: [] };
30
32
  const cooldownUntil = Math.floor(Date.now() / 1000) + cooldownDays * 86400;
31
33
  for (const c of candidates) {
@@ -43,8 +45,18 @@ export function runAutoDeactivateSweep(deps) {
43
45
  }
44
46
  if (!roles.includes('verifier'))
45
47
  return; // already deactivated
46
- const wrongPct = c.tasks_wrong / c.tasks_done;
47
- const reason = `confirmed_wrong_count=${c.tasks_wrong}/${c.tasks_done} (${(wrongPct * 100).toFixed(1)}%) ≥ threshold (count=${thresholdCount}, pct=${(thresholdPct * 100).toFixed(0)}%, min_sample=${minSample})`;
48
+ // Codex #231 P1:扫描与本 tx 之间 verifier_stats 可能变化(申诉成功/纠正会减 tasks_wrong)。
49
+ // 必须在 tx 内重读 stats 并基于重读值重算阈值,否则会用陈旧的越线结果误卸任。
50
+ const vs = db.prepare("SELECT tasks_done, tasks_wrong FROM verifier_stats WHERE user_id = ?").get(c.user_id);
51
+ if (!vs)
52
+ return; // stats 行已不存在
53
+ const tasksDone = Number(vs.tasks_done);
54
+ const tasksWrong = Number(vs.tasks_wrong);
55
+ const overThreshold = tasksDone > 0 && tasksDone >= minSample && tasksWrong >= thresholdCount && (tasksWrong / tasksDone) >= thresholdPct;
56
+ if (!overThreshold)
57
+ return; // 重读后已不再越线 → 不写任何东西
58
+ const wrongPct = tasksWrong / tasksDone;
59
+ const reason = `confirmed_wrong_count=${tasksWrong}/${tasksDone} (${(wrongPct * 100).toFixed(1)}%) ≥ threshold (count=${thresholdCount}, pct=${(thresholdPct * 100).toFixed(0)}%, min_sample=${minSample})`;
48
60
  // 1. UPDATE all active rows → inactive
49
61
  db.prepare("UPDATE governance_applications SET status = 'inactive' WHERE user_id = ? AND role = 'verifier' AND status = 'active'").run(c.user_id);
50
62
  // 2. INSERT auto_deactivate row(audit + appeal source)
@@ -69,8 +81,8 @@ export function runAutoDeactivateSweep(deps) {
69
81
  result.deactivated.push({
70
82
  user_id: c.user_id,
71
83
  role: 'verifier',
72
- tasks_done: c.tasks_done,
73
- tasks_wrong: c.tasks_wrong,
84
+ tasks_done: tasksDone,
85
+ tasks_wrong: tasksWrong,
74
86
  wrong_pct: wrongPct,
75
87
  reason,
76
88
  });
@@ -93,9 +105,9 @@ export function runAutoDeactivateSweep(deps) {
93
105
  export function startAutoDeactivateCron(deps) {
94
106
  const hours = Number(deps.getProtocolParam('governance_auto_deactivate_cron_hours', 24));
95
107
  const ms = Math.max(1, hours) * 60 * 60 * 1000;
96
- setInterval(() => {
108
+ setInterval(async () => {
97
109
  try {
98
- const r = runAutoDeactivateSweep(deps);
110
+ const r = await runAutoDeactivateSweep(deps);
99
111
  if (r.deactivated.length > 0) {
100
112
  console.log(`[gov-auto-deactivate] swept ${r.scanned} candidates, deactivated ${r.deactivated.length}:`, r.deactivated.map(d => `${d.user_id}(${d.tasks_wrong}/${d.tasks_done})`).join(', '));
101
113
  }