@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
@@ -4,6 +4,9 @@ import { transition, settleFault, settleDeclinedNoFault } from '../../layer0-fou
4
4
  // RFC-014 PR5 — 争议后佣金/基金 clawback 走整数 base-units + 绝对值落库。
5
5
  import { toUnits, toDecimal, mulRate } from '../../money.js';
6
6
  import { applyWalletDelta } from '../../ledger.js';
7
+ // RFC-016 Phase 1 — 纯只读端点/校验读/SNF 分发读/标记写 → async seam;arbitrate 仲裁核心(原子领取 +
8
+ // 2 settlement db.transaction + reputation/strike/publish)与 tx 内 appendAuditLog 保持同步(Phase 3 迁 pg)。
9
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js';
7
10
  export function registerDisputesWriteRoutes(app, deps) {
8
11
  const { db, auth, generateId, detectFraud, errorRes, isEligibleArbitrator, requireHumanPresence, getDisputeDetails, respondToDispute, arbitrateDispute, addPartyEvidence, requestEvidence, markEvidenceExpiry, uploadEvidence, EVIDENCE_MAX_BYTES, EVIDENCE_ALLOWED_MIME, appendOrderEvent, FUND_BASE_RATE, settleCommission, depositToFund, calculatePv, recordDisputeReputation, issueAgentStrike, publishDisputeCase, logAdminAction, snfSend, getProtocolParam } = deps;
9
12
  // ── RFC-007 stage 5:客观拒单【临时判责】的仲裁翻案 ─────────────────────────────
@@ -11,23 +14,23 @@ export function registerDisputesWriteRoutes(app, deps) {
11
14
  // 仲裁员(真实人工 + WebAuthn)裁决:uphold → declined_nofault(免责全退+退质押);reject → 违约结算。
12
15
  const SYS = 'sys_protocol';
13
16
  // 仲裁员待办:列出所有被举证的临时判责拒单
14
- app.get('/api/admin/decline-contests', (req, res) => {
17
+ app.get('/api/admin/decline-contests', async (req, res) => {
15
18
  const user = auth(req, res);
16
19
  if (!user)
17
20
  return;
18
21
  const elig = isEligibleArbitrator(user.id);
19
22
  if (!elig.ok)
20
23
  return void errorRes(res, 403, 'NOT_ARBITRATOR', elig.reason || '仅限仲裁员');
21
- const rows = db.prepare(`
24
+ const rows = await dbAll(`
22
25
  SELECT id AS order_id, buyer_id, seller_id, product_id, total_amount, decline_reason_code, declined_at, decline_contest_deadline
23
26
  FROM orders
24
27
  WHERE status = 'fault_seller' AND COALESCE(decline_objective_pending,0)=1 AND COALESCE(decline_contested,0)=1 AND settled_fault_at IS NULL
25
28
  ORDER BY declined_at ASC
26
- `).all();
29
+ `);
27
30
  res.json({ contests: rows });
28
31
  });
29
32
  // 仲裁员裁决
30
- app.post('/api/admin/decline-contests/:orderId/resolve', (req, res) => {
33
+ app.post('/api/admin/decline-contests/:orderId/resolve', async (req, res) => {
31
34
  const user = auth(req, res);
32
35
  if (!user)
33
36
  return;
@@ -43,7 +46,7 @@ export function registerDisputesWriteRoutes(app, deps) {
43
46
  return void errorRes(res, 400, 'BAD_DECISION', "decision 必须为 'uphold'(认定无责) 或 'reject'(驳回,判违约)");
44
47
  if (!reason || !String(reason).trim())
45
48
  return void errorRes(res, 400, 'REASON_REQUIRED', '请提供裁决理由');
46
- const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(req.params.orderId);
49
+ const order = await dbOne('SELECT * FROM orders WHERE id = ?', [req.params.orderId]);
47
50
  if (!order)
48
51
  return void res.status(404).json({ error: '订单不存在' });
49
52
  if (order.status !== 'fault_seller' || Number(order.decline_objective_pending) !== 1 || Number(order.decline_contested) !== 1 || order.settled_fault_at) {
@@ -73,12 +76,12 @@ export function registerDisputesWriteRoutes(app, deps) {
73
76
  res.json({ success: true, outcome: 'fault_seller', note: '裁决:驳回。按违约结算,买家已全额退款,卖家质押按规则罚没。' });
74
77
  });
75
78
  // 被诉方反驳
76
- app.post('/api/disputes/:id/respond', (req, res) => {
79
+ app.post('/api/disputes/:id/respond', async (req, res) => {
77
80
  const user = auth(req, res);
78
81
  if (!user)
79
82
  return;
80
83
  const { notes = '', evidence_description = '' } = req.body;
81
- const dispute = getDisputeDetails(db, req.params.id);
84
+ const dispute = await getDisputeDetails(db, req.params.id);
82
85
  if (!dispute)
83
86
  return void res.status(404).json({ error: '争议不存在' });
84
87
  if (dispute.defendant_id !== user.id)
@@ -97,7 +100,7 @@ export function registerDisputesWriteRoutes(app, deps) {
97
100
  res.json({ success: true, message: result.message });
98
101
  });
99
102
  // 仲裁员裁定
100
- app.post('/api/disputes/:id/arbitrate', (req, res) => {
103
+ app.post('/api/disputes/:id/arbitrate', async (req, res) => {
101
104
  const user = auth(req, res);
102
105
  if (!user)
103
106
  return;
@@ -128,7 +131,7 @@ export function registerDisputesWriteRoutes(app, deps) {
128
131
  }
129
132
  }
130
133
  }
131
- const dispute = getDisputeDetails(db, req.params.id);
134
+ const dispute = await getDisputeDetails(db, req.params.id);
132
135
  if (!dispute)
133
136
  return void res.status(404).json({ error: '争议不存在' });
134
137
  // P0: 防"任意仲裁员裁决任意争议"
@@ -345,7 +348,7 @@ export function registerDisputesWriteRoutes(app, deps) {
345
348
  res.json({ success: true, message: result.message, settlement: result.settlement });
346
349
  });
347
350
  // 参与方主动举证(text)+ SNF 信封分发
348
- app.post('/api/disputes/:id/add-evidence', (req, res) => {
351
+ app.post('/api/disputes/:id/add-evidence', async (req, res) => {
349
352
  const user = auth(req, res);
350
353
  if (!user)
351
354
  return;
@@ -360,14 +363,13 @@ export function registerDisputesWriteRoutes(app, deps) {
360
363
  const evReasons = detectFraud(rawDesc);
361
364
  if (evReasons.length > 0 && result.evidenceId) {
362
365
  try {
363
- db.prepare(`UPDATE evidence SET flag_reasons = ? WHERE id = ?`)
364
- .run(JSON.stringify(evReasons), result.evidenceId);
366
+ await dbRun(`UPDATE evidence SET flag_reasons = ? WHERE id = ?`, [JSON.stringify(evReasons), result.evidenceId]);
365
367
  }
366
368
  catch { }
367
369
  }
368
370
  // 协议层:作为签名 SNF 信封投到对方 + 已分配仲裁员 inbox
369
371
  try {
370
- const d = db.prepare(`SELECT order_id, initiator_id, defendant_id, assigned_arbitrators FROM disputes WHERE id = ?`).get(req.params.id);
372
+ const d = await dbOne(`SELECT order_id, initiator_id, defendant_id, assigned_arbitrators FROM disputes WHERE id = ?`, [req.params.id]);
371
373
  if (d) {
372
374
  const uid = user.id;
373
375
  const recipients = new Set();
@@ -419,7 +421,7 @@ export function registerDisputesWriteRoutes(app, deps) {
419
421
  next();
420
422
  };
421
423
  // N: limit 精确 = EVIDENCE_MAX_BYTES
422
- app.post('/api/disputes/:id/evidence-blob', lightAuthGuard, express.raw({ type: 'application/octet-stream', limit: EVIDENCE_MAX_BYTES }), (req, res) => {
424
+ app.post('/api/disputes/:id/evidence-blob', lightAuthGuard, express.raw({ type: 'application/octet-stream', limit: EVIDENCE_MAX_BYTES }), async (req, res) => {
423
425
  const user = auth(req, res);
424
426
  if (!user)
425
427
  return;
@@ -458,15 +460,13 @@ export function registerDisputesWriteRoutes(app, deps) {
458
460
  const evReasons = detectFraud(description);
459
461
  if (evReasons.length > 0 && out.id) {
460
462
  try {
461
- db.prepare(`UPDATE evidence SET flag_reasons = ? WHERE id = ?`)
462
- .run(JSON.stringify(evReasons), out.id);
463
+ await dbRun(`UPDATE evidence SET flag_reasons = ? WHERE id = ?`, [JSON.stringify(evReasons), out.id]);
463
464
  }
464
465
  catch { }
465
466
  }
466
467
  // SNF 信封投递
467
468
  try {
468
- const d = db.prepare(`SELECT order_id, initiator_id, defendant_id, assigned_arbitrators FROM disputes WHERE id = ?`)
469
- .get(req.params.id);
469
+ const d = await dbOne(`SELECT order_id, initiator_id, defendant_id, assigned_arbitrators FROM disputes WHERE id = ?`, [req.params.id]);
470
470
  if (d) {
471
471
  const uid = user.id;
472
472
  const recipients = new Set();
@@ -549,8 +549,8 @@ export function registerDisputesWriteRoutes(app, deps) {
549
549
  // Both endpoints require caller is one of dispute.assigned_arbitrators.
550
550
  // Repause(extend) allowed — each pause writes an audit_log entry.
551
551
  // No Iron-Rule Passkey: routine arbitrator action, fully audit-traceable.
552
- function isAssignedArbitrator(disputeId, userId) {
553
- const row = db.prepare(`SELECT assigned_arbitrators FROM disputes WHERE id = ?`).get(disputeId);
552
+ async function isAssignedArbitrator(disputeId, userId) {
553
+ const row = await dbOne(`SELECT assigned_arbitrators FROM disputes WHERE id = ?`, [disputeId]);
554
554
  if (!row)
555
555
  return false;
556
556
  let arr = [];
@@ -585,7 +585,7 @@ export function registerDisputesWriteRoutes(app, deps) {
585
585
  return text; // unparseable — leave as is
586
586
  return new Date(ms + secondsToAdd * 1000).toISOString();
587
587
  }
588
- app.post('/api/disputes/:id/arbitrator-pause-auto-judge', (req, res) => {
588
+ app.post('/api/disputes/:id/arbitrator-pause-auto-judge', async (req, res) => {
589
589
  const user = auth(req, res);
590
590
  if (!user)
591
591
  return;
@@ -605,7 +605,7 @@ export function registerDisputesWriteRoutes(app, deps) {
605
605
  if (untilTs > maxAllowed) {
606
606
  return void errorRes(res, 400, 'EXCEEDS_MAX_HOURS', `until_ts 超过最大暂停窗口 ${maxHours}h(playbook §2.1)`);
607
607
  }
608
- const dispute = db.prepare(`SELECT id, status, ruling_type, assigned_arbitrators, auto_judge_paused_until, respond_deadline, arbitrate_deadline FROM disputes WHERE id = ?`).get(disputeId);
608
+ const dispute = await dbOne(`SELECT id, status, ruling_type, assigned_arbitrators, auto_judge_paused_until, respond_deadline, arbitrate_deadline FROM disputes WHERE id = ?`, [disputeId]);
609
609
  if (!dispute)
610
610
  return void errorRes(res, 404, 'NOT_FOUND', 'dispute 不存在');
611
611
  if (dispute.ruling_type) {
@@ -614,7 +614,7 @@ export function registerDisputesWriteRoutes(app, deps) {
614
614
  if (dispute.status !== 'open' && dispute.status !== 'in_review') {
615
615
  return void errorRes(res, 409, 'WRONG_STATUS', `status='${dispute.status}',只能 pause open / in_review`);
616
616
  }
617
- if (!isAssignedArbitrator(disputeId, userId)) {
617
+ if (!await isAssignedArbitrator(disputeId, userId)) {
618
618
  return void errorRes(res, 403, 'NOT_ASSIGNED_ARBITRATOR', '仅 assigned_arbitrators 可暂停自动判定时钟');
619
619
  }
620
620
  // P0 fix:计算 deadline 扩展秒数
@@ -622,32 +622,64 @@ export function registerDisputesWriteRoutes(app, deps) {
622
622
  // - repause(已经 paused):increment = untilTs - existing_paused_until(可能 < 0,clamp 0)
623
623
  // 这样多次 pause 累加正确;repause 缩短无效果(只 audit_log 记)
624
624
  const nowSec = Math.floor(Date.now() / 1000);
625
- const baseline = dispute.auto_judge_paused_until && dispute.auto_judge_paused_until > nowSec
626
- ? dispute.auto_judge_paused_until
627
- : nowSec;
628
- const incrementSec = Math.max(0, untilTs - baseline);
629
- db.transaction(() => {
630
- // 扩展 deadline(若 increment > 0)
631
- let newRespondDeadline = dispute.respond_deadline;
632
- let newArbitrateDeadline = dispute.arbitrate_deadline;
633
- if (incrementSec > 0) {
634
- newRespondDeadline = extendIsoDeadlineBySeconds(dispute.respond_deadline, incrementSec);
635
- newArbitrateDeadline = extendIsoDeadlineBySeconds(dispute.arbitrate_deadline, incrementSec);
636
- db.prepare(`UPDATE disputes SET respond_deadline = ?, arbitrate_deadline = ? WHERE id = ?`)
637
- .run(newRespondDeadline, newArbitrateDeadline, disputeId);
638
- }
639
- db.prepare(`UPDATE disputes SET auto_judge_paused_until = ?, auto_judge_pause_reason = ? WHERE id = ?`)
640
- .run(untilTs, reason, disputeId);
641
- appendAuditLog(disputeId, {
642
- event: 'arbitrator_pause_auto_judge',
643
- actor: userId,
644
- reason,
645
- until_ts: untilTs,
646
- deadline_extended_seconds: incrementSec,
647
- is_repause: dispute.auto_judge_paused_until !== null && dispute.auto_judge_paused_until > nowSec,
648
- spec_ref: 'playbook §2.1',
649
- });
650
- })();
625
+ // Codex #229 P1:上面的 await 预检与同步 tx 之间有 yield,dispute status/ruling/
626
+ // assignment 可能已变。所有授权+状态判定 + baseline/increment 计算必须基于【tx 内重读】的行,
627
+ // 先于任何写抛回滚;预检仅作友好 fast-fail。
628
+ let incrementSec = 0;
629
+ let isRepause = false;
630
+ try {
631
+ db.transaction(() => {
632
+ const d = db.prepare(`SELECT status, ruling_type, assigned_arbitrators, auto_judge_paused_until, respond_deadline, arbitrate_deadline FROM disputes WHERE id = ?`).get(disputeId);
633
+ if (!d)
634
+ throw new Error('DW_NOT_FOUND');
635
+ if (d.ruling_type)
636
+ throw new Error('DW_ALREADY_RULED');
637
+ if (d.status !== 'open' && d.status !== 'in_review')
638
+ throw new Error('DW_WRONG_STATUS');
639
+ let assigned = [];
640
+ try {
641
+ assigned = JSON.parse(d.assigned_arbitrators || '[]');
642
+ }
643
+ catch {
644
+ assigned = [];
645
+ }
646
+ if (!assigned.includes(userId))
647
+ throw new Error('DW_NOT_ASSIGNED');
648
+ const baseline = d.auto_judge_paused_until && d.auto_judge_paused_until > nowSec ? d.auto_judge_paused_until : nowSec;
649
+ incrementSec = Math.max(0, untilTs - baseline);
650
+ isRepause = d.auto_judge_paused_until !== null && d.auto_judge_paused_until > nowSec;
651
+ // 扩展 deadline(若 increment > 0)— 基于 tx 内重读的 deadline,非陈旧预检值
652
+ if (incrementSec > 0) {
653
+ const newRespondDeadline = extendIsoDeadlineBySeconds(d.respond_deadline, incrementSec);
654
+ const newArbitrateDeadline = extendIsoDeadlineBySeconds(d.arbitrate_deadline, incrementSec);
655
+ db.prepare(`UPDATE disputes SET respond_deadline = ?, arbitrate_deadline = ? WHERE id = ?`)
656
+ .run(newRespondDeadline, newArbitrateDeadline, disputeId);
657
+ }
658
+ db.prepare(`UPDATE disputes SET auto_judge_paused_until = ?, auto_judge_pause_reason = ? WHERE id = ?`)
659
+ .run(untilTs, reason, disputeId);
660
+ appendAuditLog(disputeId, {
661
+ event: 'arbitrator_pause_auto_judge',
662
+ actor: userId,
663
+ reason,
664
+ until_ts: untilTs,
665
+ deadline_extended_seconds: incrementSec,
666
+ is_repause: isRepause,
667
+ spec_ref: 'playbook §2.1',
668
+ });
669
+ })();
670
+ }
671
+ catch (e) {
672
+ const msg = e.message;
673
+ if (msg === 'DW_NOT_FOUND')
674
+ return void errorRes(res, 404, 'NOT_FOUND', 'dispute 不存在');
675
+ if (msg === 'DW_ALREADY_RULED')
676
+ return void errorRes(res, 409, 'ALREADY_RULED', '已裁决的 dispute 不能暂停自动判定时钟');
677
+ if (msg === 'DW_WRONG_STATUS')
678
+ return void errorRes(res, 409, 'WRONG_STATUS', 'dispute 状态已变更,只能 pause open / in_review');
679
+ if (msg === 'DW_NOT_ASSIGNED')
680
+ return void errorRes(res, 403, 'NOT_ASSIGNED_ARBITRATOR', '仅 assigned_arbitrators 可暂停自动判定时钟');
681
+ throw e;
682
+ }
651
683
  res.json({
652
684
  success: true,
653
685
  dispute_id: disputeId,
@@ -660,13 +692,13 @@ export function registerDisputesWriteRoutes(app, deps) {
660
692
  : 'pause 已记录(repause 缩短无 deadline 变化)。',
661
693
  });
662
694
  });
663
- app.post('/api/disputes/:id/arbitrator-resume-auto-judge', (req, res) => {
695
+ app.post('/api/disputes/:id/arbitrator-resume-auto-judge', async (req, res) => {
664
696
  const user = auth(req, res);
665
697
  if (!user)
666
698
  return;
667
699
  const userId = user.id;
668
700
  const disputeId = req.params.id;
669
- const dispute = db.prepare(`SELECT id, ruling_type, auto_judge_paused_until FROM disputes WHERE id = ?`).get(disputeId);
701
+ const dispute = await dbOne(`SELECT id, ruling_type, auto_judge_paused_until FROM disputes WHERE id = ?`, [disputeId]);
670
702
  if (!dispute)
671
703
  return void errorRes(res, 404, 'NOT_FOUND', 'dispute 不存在');
672
704
  if (dispute.ruling_type) {
@@ -675,18 +707,49 @@ export function registerDisputesWriteRoutes(app, deps) {
675
707
  if (!dispute.auto_judge_paused_until) {
676
708
  return void errorRes(res, 409, 'NOT_PAUSED', '当前未暂停,无需 resume');
677
709
  }
678
- if (!isAssignedArbitrator(disputeId, userId)) {
710
+ if (!await isAssignedArbitrator(disputeId, userId)) {
679
711
  return void errorRes(res, 403, 'NOT_ASSIGNED_ARBITRATOR', '仅 assigned_arbitrators 可 resume');
680
712
  }
681
- db.transaction(() => {
682
- db.prepare(`UPDATE disputes SET auto_judge_paused_until = NULL, auto_judge_pause_reason = NULL WHERE id = ?`)
683
- .run(disputeId);
684
- appendAuditLog(disputeId, {
685
- event: 'arbitrator_resume_auto_judge',
686
- actor: userId,
687
- spec_ref: 'playbook §2.1',
688
- });
689
- })();
713
+ // Codex #229 P1:tx 内重读 + 重判授权/状态,先于任何写抛回滚;上面 await 预检仅友好 fast-fail。
714
+ try {
715
+ db.transaction(() => {
716
+ const d = db.prepare(`SELECT ruling_type, assigned_arbitrators, auto_judge_paused_until FROM disputes WHERE id = ?`).get(disputeId);
717
+ if (!d)
718
+ throw new Error('DW_NOT_FOUND');
719
+ if (d.ruling_type)
720
+ throw new Error('DW_ALREADY_RULED');
721
+ if (!d.auto_judge_paused_until)
722
+ throw new Error('DW_NOT_PAUSED');
723
+ let assigned = [];
724
+ try {
725
+ assigned = JSON.parse(d.assigned_arbitrators || '[]');
726
+ }
727
+ catch {
728
+ assigned = [];
729
+ }
730
+ if (!assigned.includes(userId))
731
+ throw new Error('DW_NOT_ASSIGNED');
732
+ db.prepare(`UPDATE disputes SET auto_judge_paused_until = NULL, auto_judge_pause_reason = NULL WHERE id = ?`)
733
+ .run(disputeId);
734
+ appendAuditLog(disputeId, {
735
+ event: 'arbitrator_resume_auto_judge',
736
+ actor: userId,
737
+ spec_ref: 'playbook §2.1',
738
+ });
739
+ })();
740
+ }
741
+ catch (e) {
742
+ const msg = e.message;
743
+ if (msg === 'DW_NOT_FOUND')
744
+ return void errorRes(res, 404, 'NOT_FOUND', 'dispute 不存在');
745
+ if (msg === 'DW_ALREADY_RULED')
746
+ return void errorRes(res, 409, 'ALREADY_RULED', '已裁决的 dispute 不需 resume');
747
+ if (msg === 'DW_NOT_PAUSED')
748
+ return void errorRes(res, 409, 'NOT_PAUSED', '当前未暂停,无需 resume');
749
+ if (msg === 'DW_NOT_ASSIGNED')
750
+ return void errorRes(res, 403, 'NOT_ASSIGNED_ARBITRATOR', '仅 assigned_arbitrators 可 resume');
751
+ throw e;
752
+ }
690
753
  res.json({ success: true, dispute_id: disputeId, note: '自动判定时钟已解冻' });
691
754
  });
692
755
  }
@@ -1,14 +1,15 @@
1
1
  import { readEvidenceBlob, withdrawEvidence, verifyEvidenceSig, listEvidence as listEvidenceFiles } from '../../layer3-trust/L3-1-dispute-engine/evidence-storage.js';
2
2
  import { submitEvidenceForRequest } from '../../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
3
+ import { dbOne, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
3
4
  export function registerEvidenceRoutes(app, deps) {
4
5
  const { db, auth, detectFraud } = deps;
5
6
  // 下载证据 blob(仅参与方/仲裁员)
6
- app.get('/api/evidence/:id/blob', (req, res) => {
7
+ app.get('/api/evidence/:id/blob', async (req, res) => {
7
8
  const user = auth(req, res);
8
9
  if (!user)
9
10
  return;
10
11
  try {
11
- const out = readEvidenceBlob(db, req.params.id, user.id);
12
+ const out = await readEvidenceBlob(db, req.params.id, user.id);
12
13
  res.setHeader('Content-Type', out.mime);
13
14
  res.setHeader('X-Content-Hash', out.hash);
14
15
  res.setHeader('Cache-Control', 'private, max-age=300');
@@ -45,23 +46,23 @@ export function registerEvidenceRoutes(app, deps) {
45
46
  }
46
47
  });
47
48
  // 验签 — 任意参与方
48
- app.get('/api/evidence/:id/verify', (req, res) => {
49
+ app.get('/api/evidence/:id/verify', async (req, res) => {
49
50
  const user = auth(req, res);
50
51
  if (!user)
51
52
  return;
52
- const ev = db.prepare('SELECT dispute_id FROM evidence WHERE id = ?').get(req.params.id);
53
+ const ev = await dbOne('SELECT dispute_id FROM evidence WHERE id = ?', [req.params.id]);
53
54
  if (!ev)
54
55
  return void res.status(404).json({ error: 'evidence_not_found' });
55
56
  try {
56
- listEvidenceFiles(db, ev.dispute_id, user.id);
57
+ await listEvidenceFiles(db, ev.dispute_id, user.id);
57
58
  } // 复用鉴权
58
59
  catch {
59
60
  return void res.status(403).json({ error: 'not_dispute_party' });
60
61
  }
61
- res.json(verifyEvidenceSig(db, req.params.id));
62
+ res.json(await verifyEvidenceSig(db, req.params.id));
62
63
  });
63
64
  // 当事人提交补充证据响应(仲裁员 request 后用)
64
- app.post('/api/evidence-requests/:requestId/submit', (req, res) => {
65
+ app.post('/api/evidence-requests/:requestId/submit', async (req, res) => {
65
66
  const user = auth(req, res);
66
67
  if (!user)
67
68
  return;
@@ -76,8 +77,7 @@ export function registerEvidenceRoutes(app, deps) {
76
77
  const evReasons = detectFraud(rawDesc);
77
78
  if (evReasons.length > 0 && result.evidenceId) {
78
79
  try {
79
- db.prepare(`UPDATE evidence SET flag_reasons = ? WHERE id = ?`)
80
- .run(JSON.stringify(evReasons), result.evidenceId);
80
+ await dbRun(`UPDATE evidence SET flag_reasons = ? WHERE id = ?`, [JSON.stringify(evReasons), result.evidenceId]);
81
81
  }
82
82
  catch { }
83
83
  }
@@ -1,3 +1,4 @@
1
+ import { dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  import { createAnchor, verifyAnchorSignature, revokeAnchor, issueOwnershipToken, submitVerification, getAnchor, listAnchorsByProduct, listAnchorsBySeller, distributeAnchorRewards, ANCHOR_VERIFICATION_FEE_RECOMMENDED, } from '../../layer1-agent/L1-2-external-anchor/anchor-engine.js';
2
3
  export function registerExternalAnchorsRoutes(app, deps) {
3
4
  const { db, auth } = deps;
@@ -24,14 +25,14 @@ export function registerExternalAnchorsRoutes(app, deps) {
24
25
  }
25
26
  });
26
27
  // 透出推荐 fee + anchor 的奖励情况
27
- app.get('/api/external-anchors/:id/rewards', (req, res) => {
28
- const a = getAnchor(db, req.params.id);
28
+ app.get('/api/external-anchors/:id/rewards', async (req, res) => {
29
+ const a = await getAnchor(db, req.params.id);
29
30
  if (!a)
30
31
  return void res.status(404).json({ error: 'anchor 不存在' });
31
- const verifications = db.prepare(`
32
+ const verifications = await dbAll(`
32
33
  SELECT verifier_id, verifier_role, content_matches, token_found, reward_amount, verified_at
33
34
  FROM external_anchor_verifications WHERE anchor_id = ? ORDER BY verified_at ASC
34
- `).all(req.params.id);
35
+ `, [req.params.id]);
35
36
  res.json({
36
37
  verification_fee: a.verification_fee || 0,
37
38
  fee_paid_out: !!a.fee_paid_out,
@@ -51,20 +52,20 @@ export function registerExternalAnchorsRoutes(app, deps) {
51
52
  const paid = distributeAnchorRewards(db, req.params.id);
52
53
  res.json({ ok: true, paid });
53
54
  });
54
- app.get('/api/external-anchors/by-product/:id', (req, res) => {
55
- res.json({ items: listAnchorsByProduct(db, req.params.id) });
55
+ app.get('/api/external-anchors/by-product/:id', async (req, res) => {
56
+ res.json({ items: await listAnchorsByProduct(db, req.params.id) });
56
57
  });
57
- app.get('/api/external-anchors/by-seller/:id', (req, res) => {
58
- res.json({ items: listAnchorsBySeller(db, req.params.id) });
58
+ app.get('/api/external-anchors/by-seller/:id', async (req, res) => {
59
+ res.json({ items: await listAnchorsBySeller(db, req.params.id) });
59
60
  });
60
- app.get('/api/external-anchors/:id', (req, res) => {
61
- const a = getAnchor(db, req.params.id);
61
+ app.get('/api/external-anchors/:id', async (req, res) => {
62
+ const a = await getAnchor(db, req.params.id);
62
63
  if (!a)
63
64
  return void res.status(404).json({ error: 'anchor 不存在' });
64
65
  res.json(a);
65
66
  });
66
- app.get('/api/external-anchors/:id/verify-sig', (req, res) => {
67
- res.json(verifyAnchorSignature(db, req.params.id));
67
+ app.get('/api/external-anchors/:id/verify-sig', async (req, res) => {
68
+ res.json(await verifyAnchorSignature(db, req.params.id));
68
69
  });
69
70
  app.post('/api/external-anchors/:id/revoke', (req, res) => {
70
71
  const user = auth(req, res);
@@ -1,8 +1,9 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  const VALID_FEEDBACK_CAT = new Set(['bug', 'abuse', 'feature', 'account', 'other']);
2
3
  const VALID_FEEDBACK_SEV = new Set(['low', 'medium', 'high']);
3
4
  export function registerFeedbackRoutes(app, deps) {
4
5
  const { db, generateId, auth, broadcastSystemEvent, detectFraud, anthropic } = deps;
5
- app.post('/api/feedback', (req, res) => {
6
+ app.post('/api/feedback', async (req, res) => {
6
7
  const user = auth(req, res);
7
8
  if (!user)
8
9
  return;
@@ -16,12 +17,11 @@ export function registerFeedbackRoutes(app, deps) {
16
17
  return void res.status(400).json({ error: '标题 4-80 字' });
17
18
  if (bod.length < 10 || bod.length > 2000)
18
19
  return void res.status(400).json({ error: '正文 10-2000 字' });
19
- const recent = db.prepare(`SELECT COUNT(*) as n FROM feedback_tickets WHERE user_id = ? AND created_at > datetime('now', '-1 hour')`).get(user.id).n;
20
+ const recent = (await dbOne(`SELECT COUNT(*) as n FROM feedback_tickets WHERE user_id = ? AND created_at > datetime('now', '-1 hour')`, [user.id])).n;
20
21
  if (recent >= 5)
21
22
  return void res.status(429).json({ error: '提交过于频繁,请稍后再试' });
22
23
  const id = generateId('fbk');
23
- db.prepare(`INSERT INTO feedback_tickets (id, user_id, category, severity, subject, body) VALUES (?,?,?,?,?,?)`)
24
- .run(id, user.id, String(category), sev, sub, bod);
24
+ await dbRun(`INSERT INTO feedback_tickets (id, user_id, category, severity, subject, body) VALUES (?,?,?,?,?,?)`, [id, user.id, String(category), sev, sub, bod]);
25
25
  try {
26
26
  broadcastSystemEvent('feedback', '💬', `反馈工单 ${id} · ${category}/${sev}`, id);
27
27
  }
@@ -43,8 +43,7 @@ export function registerFeedbackRoutes(app, deps) {
43
43
  }],
44
44
  });
45
45
  const text = message.content[0]?.text || '';
46
- db.prepare(`UPDATE feedback_tickets SET ai_suggested_reply = ?, ai_generated_at = datetime('now') WHERE id = ?`)
47
- .run(text.trim(), id);
46
+ await dbRun(`UPDATE feedback_tickets SET ai_suggested_reply = ?, ai_generated_at = datetime('now') WHERE id = ?`, [text.trim(), id]);
48
47
  }
49
48
  catch (e) {
50
49
  console.error('[ai feedback draft]', e.message);
@@ -52,12 +51,12 @@ export function registerFeedbackRoutes(app, deps) {
52
51
  })();
53
52
  res.json({ success: true, id });
54
53
  });
55
- app.get('/api/feedback/mine', (req, res) => {
54
+ app.get('/api/feedback/mine', async (req, res) => {
56
55
  const user = auth(req, res);
57
56
  if (!user)
58
57
  return;
59
- const rows = db.prepare(`SELECT id, category, severity, subject, body, status, admin_reply, replied_at, user_seen_reply_at, created_at, updated_at
60
- FROM feedback_tickets WHERE user_id = ? ORDER BY created_at DESC LIMIT 100`).all(user.id);
58
+ const rows = await dbAll(`SELECT id, category, severity, subject, body, status, admin_reply, replied_at, user_seen_reply_at, created_at, updated_at
59
+ FROM feedback_tickets WHERE user_id = ? ORDER BY created_at DESC LIMIT 100`, [user.id]);
61
60
  // 派生 has_unread_reply
62
61
  let unreadReplyCount = 0;
63
62
  for (const r of rows) {
@@ -71,16 +70,16 @@ export function registerFeedbackRoutes(app, deps) {
71
70
  }
72
71
  res.json({ items: rows, unread_reply_count: unreadReplyCount });
73
72
  });
74
- app.post('/api/feedback/seen', (req, res) => {
73
+ app.post('/api/feedback/seen', async (req, res) => {
75
74
  const user = auth(req, res);
76
75
  if (!user)
77
76
  return;
78
- db.prepare(`UPDATE feedback_tickets SET user_seen_reply_at = datetime('now')
79
- WHERE user_id = ? AND admin_reply IS NOT NULL AND (user_seen_reply_at IS NULL OR replied_at > user_seen_reply_at)`).run(user.id);
77
+ await dbRun(`UPDATE feedback_tickets SET user_seen_reply_at = datetime('now')
78
+ WHERE user_id = ? AND admin_reply IS NOT NULL AND (user_seen_reply_at IS NULL OR replied_at > user_seen_reply_at)`, [user.id]);
80
79
  res.json({ success: true });
81
80
  });
82
81
  // admin 列出工单
83
- app.get('/api/admin/feedback', (req, res) => {
82
+ app.get('/api/admin/feedback', async (req, res) => {
84
83
  const user = auth(req, res);
85
84
  if (!user)
86
85
  return;
@@ -99,7 +98,7 @@ export function registerFeedbackRoutes(app, deps) {
99
98
  params.push(category);
100
99
  }
101
100
  const whereClause = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
102
- const rows = db.prepare(`
101
+ const rows = await dbAll(`
103
102
  SELECT f.*, u.name as user_name, u.handle as user_handle, u.role as user_role
104
103
  FROM feedback_tickets f
105
104
  JOIN users u ON u.id = f.user_id
@@ -109,17 +108,17 @@ export function registerFeedbackRoutes(app, deps) {
109
108
  CASE f.severity WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END,
110
109
  f.created_at DESC
111
110
  LIMIT 200
112
- `).all(...params);
111
+ `, params);
113
112
  res.json({ items: rows });
114
113
  });
115
114
  // admin 回复 + 切状态
116
- app.post('/api/admin/feedback/:id/reply', (req, res) => {
115
+ app.post('/api/admin/feedback/:id/reply', async (req, res) => {
117
116
  const user = auth(req, res);
118
117
  if (!user)
119
118
  return;
120
119
  if (user.role !== 'admin')
121
120
  return void res.status(403).json({ error: '仅 admin 可回复' });
122
- const ticket = db.prepare('SELECT user_id, status FROM feedback_tickets WHERE id = ?').get(req.params.id);
121
+ const ticket = await dbOne('SELECT user_id, status FROM feedback_tickets WHERE id = ?', [req.params.id]);
123
122
  if (!ticket)
124
123
  return void res.status(404).json({ error: '工单不存在' });
125
124
  const { reply, status } = req.body || {};
@@ -137,8 +136,7 @@ export function registerFeedbackRoutes(app, deps) {
137
136
  })();
138
137
  try {
139
138
  const actions = JSON.stringify([{ kind: 'navigate', label: '查看工单', href: `#ticket/${req.params.id}`, style: 'primary' }]);
140
- db.prepare(`INSERT INTO notifications (id, user_id, type, title, body, order_id, actions) VALUES (?,?,?,?,?,?,?)`)
141
- .run(generateId('ntf'), ticket.user_id, 'ticket_reply', `💬 客服回复了你的工单`, replyStr.slice(0, 100), null, actions);
139
+ await dbRun(`INSERT INTO notifications (id, user_id, type, title, body, order_id, actions) VALUES (?,?,?,?,?,?,?)`, [generateId('ntf'), ticket.user_id, 'ticket_reply', `💬 客服回复了你的工单`, replyStr.slice(0, 100), null, actions]);
142
140
  }
143
141
  catch (e) {
144
142
  console.warn('[notif ticket_reply]', e.message);
@@ -147,25 +145,25 @@ export function registerFeedbackRoutes(app, deps) {
147
145
  });
148
146
  // ─── W7 ticket-thread ────────────────────────────────────
149
147
  // 工单详情 + timeline
150
- app.get('/api/feedback/:id', (req, res) => {
148
+ app.get('/api/feedback/:id', async (req, res) => {
151
149
  const user = auth(req, res);
152
150
  if (!user)
153
151
  return;
154
- const t = db.prepare(`
152
+ const t = await dbOne(`
155
153
  SELECT f.*, u.name as user_name, u.handle as user_handle, u.role as user_role
156
154
  FROM feedback_tickets f JOIN users u ON u.id = f.user_id WHERE f.id = ?
157
- `).get(req.params.id);
155
+ `, [req.params.id]);
158
156
  if (!t)
159
157
  return void res.status(404).json({ error: '工单不存在' });
160
158
  const isOwner = t.user_id === user.id;
161
159
  const isAdmin = user.role === 'admin';
162
160
  if (!isOwner && !isAdmin)
163
161
  return void res.status(403).json({ error: '无权查看' });
164
- const messages = db.prepare(`
162
+ const messages = await dbAll(`
165
163
  SELECT m.*, u.name as sender_name, u.handle as sender_handle
166
164
  FROM feedback_messages m LEFT JOIN users u ON u.id = m.sender_id
167
165
  WHERE m.ticket_id = ? ORDER BY m.created_at ASC
168
- `).all(t.id);
166
+ `, [t.id]);
169
167
  const events = [];
170
168
  events.push({
171
169
  id: `create-${t.id}`,
@@ -206,24 +204,24 @@ export function registerFeedbackRoutes(app, deps) {
206
204
  events.sort((a, b) => a.ts.localeCompare(b.ts));
207
205
  if (isOwner) {
208
206
  try {
209
- db.prepare(`UPDATE feedback_tickets SET user_seen_reply_at = datetime('now') WHERE id = ? AND admin_reply IS NOT NULL`).run(t.id);
207
+ await dbRun(`UPDATE feedback_tickets SET user_seen_reply_at = datetime('now') WHERE id = ? AND admin_reply IS NOT NULL`, [t.id]);
210
208
  }
211
209
  catch { }
212
210
  }
213
211
  if (isAdmin) {
214
212
  try {
215
- db.prepare(`UPDATE feedback_tickets SET admin_seen_at = datetime('now') WHERE id = ?`).run(t.id);
213
+ await dbRun(`UPDATE feedback_tickets SET admin_seen_at = datetime('now') WHERE id = ?`, [t.id]);
216
214
  }
217
215
  catch { }
218
216
  }
219
217
  res.json({ item: t, timeline: events, is_admin: isAdmin });
220
218
  });
221
219
  // 工单内追加消息(user 或 admin)
222
- app.post('/api/feedback/:id/messages', (req, res) => {
220
+ app.post('/api/feedback/:id/messages', async (req, res) => {
223
221
  const user = auth(req, res);
224
222
  if (!user)
225
223
  return;
226
- const t = db.prepare(`SELECT id, user_id, status FROM feedback_tickets WHERE id = ?`).get(req.params.id);
224
+ const t = await dbOne(`SELECT id, user_id, status FROM feedback_tickets WHERE id = ?`, [req.params.id]);
227
225
  if (!t)
228
226
  return void res.status(404).json({ error: '工单不存在' });
229
227
  const isOwner = t.user_id === user.id;
@@ -251,15 +249,13 @@ export function registerFeedbackRoutes(app, deps) {
251
249
  try {
252
250
  const tktAction = JSON.stringify([{ kind: 'navigate', label: '查看工单', href: `#ticket/${t.id}`, style: 'primary' }]);
253
251
  if (isOwner) {
254
- const admins = db.prepare(`SELECT id FROM users WHERE role = 'admin'`).all();
252
+ const admins = await dbAll(`SELECT id FROM users WHERE role = 'admin'`, []);
255
253
  for (const a of admins) {
256
- db.prepare(`INSERT INTO notifications (id, user_id, type, title, body, order_id, actions) VALUES (?,?,?,?,?,?,?)`)
257
- .run(generateId('ntf'), a.id, 'ticket_followup', '💬 用户追问了工单', body.slice(0, 100), null, tktAction);
254
+ await dbRun(`INSERT INTO notifications (id, user_id, type, title, body, order_id, actions) VALUES (?,?,?,?,?,?,?)`, [generateId('ntf'), a.id, 'ticket_followup', '💬 用户追问了工单', body.slice(0, 100), null, tktAction]);
258
255
  }
259
256
  }
260
257
  else {
261
- db.prepare(`INSERT INTO notifications (id, user_id, type, title, body, order_id, actions) VALUES (?,?,?,?,?,?,?)`)
262
- .run(generateId('ntf'), t.user_id, 'ticket_reply', '💬 客服回复了你的工单', body.slice(0, 100), null, tktAction);
258
+ await dbRun(`INSERT INTO notifications (id, user_id, type, title, body, order_id, actions) VALUES (?,?,?,?,?,?,?)`, [generateId('ntf'), t.user_id, 'ticket_reply', '💬 客服回复了你的工单', body.slice(0, 100), null, tktAction]);
263
259
  }
264
260
  }
265
261
  catch (e) {