@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
@@ -2,8 +2,55 @@ import { buildCartMandate, buildPaymentMandate, signMandate } from './ap2-mandat
2
2
  // RFC-014 PR3 — 金额走整数 base-units;钱包写绝对值(防 REAL 浮点加法 dust)。
3
3
  import { toUnits, toDecimal, mulQty, mulRate } from '../../money.js';
4
4
  import { applyWalletDelta } from '../../ledger.js';
5
+ import { dbOne } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam(仅下单事务外的预检查;事务内 escrow/INSERT 保持同步)
6
+ // 店铺推荐 → 商品三级归因的【懒升级】(sync,跑在下单事务内、getProductShareChain 之前)。
7
+ // 严格门槛(任一不满足则不升级,绝不覆盖已有有效直接归因):
8
+ // ① 无未过期的现有商品归因(direct share 优先,不被店铺推荐覆盖)
9
+ // ② 该 seller 下有未过期的 shop_referral_attribution(recipient=buyer)
10
+ // ③ referrer ≠ buyer、referrer ≠ seller、referrer 非 sys/internal
11
+ // ④ referrer rewards_opted_in=1 且通过 isAllowedSponsor(经济边界)
12
+ // ⑤ referrer 自己 completed 买过【同一个】商品,且该订单的【完成时间 ≤ 店铺推荐锚定时间】——
13
+ // 必须"先真实成交同款、后分享店铺",不允许先 touch 旧店铺锚点、事后补购同款再反向升级。
14
+ // 完成时间取 order_state_history 中 to_status='completed' 的 MIN(created_at);无 history 行时
15
+ // 兼容回退 orders.updated_at(绝不用 orders.created_at 当完成时间)。
16
+ // 通过后写 product_share_attribution(shareable_id=NULL,带 shop_referral_verified_purchase provenance);
17
+ // 已有但过期的行可被刷新。不改任何结算数学,只新增一条归因来源。模块级导出供测试直测。
18
+ export function maybePromoteShopReferralToProductAttribution(db, opts, productId, sellerId, buyerId) {
19
+ const liveDirect = db.prepare("SELECT 1 FROM product_share_attribution WHERE product_id = ? AND recipient_id = ? AND expires_at > datetime('now')").get(productId, buyerId);
20
+ if (liveDirect)
21
+ return; // ① 已有有效归因(含直接分享)→ 绝不覆盖
22
+ const referral = db.prepare("SELECT referrer_id, ref_code, created_at FROM shop_referral_attribution WHERE seller_id = ? AND recipient_id = ? AND expires_at > datetime('now')").get(sellerId, buyerId);
23
+ if (!referral)
24
+ return; // ②
25
+ const r = referral.referrer_id;
26
+ if (r === buyerId || r === sellerId || r === 'sys_protocol' || r === opts.internalAuditorId)
27
+ return; // ③ (referrer===seller → 记录关系但不升级分润)
28
+ const optedIn = db.prepare("SELECT rewards_opted_in FROM users WHERE id = ?").get(r)?.rewards_opted_in === 1;
29
+ if (!optedIn || !opts.isAllowedSponsor(r))
30
+ return; // ④
31
+ const qual = db.prepare(`
32
+ SELECT o.id FROM orders o
33
+ WHERE o.buyer_id = ? AND o.product_id = ? AND o.status = 'completed'
34
+ AND COALESCE(
35
+ (SELECT MIN(h.created_at) FROM order_state_history h WHERE h.order_id = o.id AND h.to_status = 'completed'),
36
+ o.updated_at
37
+ ) <= ?
38
+ ORDER BY o.created_at ASC LIMIT 1
39
+ `).get(r, productId, referral.created_at);
40
+ if (!qual)
41
+ return; // ⑤ 推荐人没在【锚定店铺之前】真实成交过同款 → 不升级
42
+ const hadRow = db.prepare("SELECT 1 FROM product_share_attribution WHERE product_id = ? AND recipient_id = ?").get(productId, buyerId);
43
+ if (hadRow) {
44
+ db.prepare("UPDATE product_share_attribution SET sharer_id = ?, shareable_id = NULL, created_at = datetime('now'), expires_at = datetime('now','+30 days'), source_type = 'shop_referral_verified_purchase', source_ref = ?, source_shop_seller_id = ?, source_qualified_order_id = ? WHERE product_id = ? AND recipient_id = ?")
45
+ .run(r, referral.ref_code, sellerId, qual.id, productId, buyerId);
46
+ }
47
+ else {
48
+ db.prepare("INSERT INTO product_share_attribution (product_id, recipient_id, sharer_id, shareable_id, expires_at, source_type, source_ref, source_shop_seller_id, source_qualified_order_id) VALUES (?,?,?,NULL,datetime('now','+30 days'),'shop_referral_verified_purchase',?,?,?)")
49
+ .run(productId, buyerId, r, referral.ref_code, sellerId, qual.id);
50
+ }
51
+ }
5
52
  export function registerOrdersCreateRoutes(app, deps) {
6
- const { db, auth, isTrustedRole, generateId, generateRecipientCode, DONATION_VALID_PCTS, INTERNAL_AUDITOR_ID, addHours, getActiveFlashSale, applyCouponToOrder, getProtocolParam, getProductShareChain, isAllowedSponsor, checkStockAndMaybeDelist, auditSponsorChainCross, appendOrderEvent, transition, notifyTransition, shouldAutoAccept, ensureCharityRep, broadcastSystemEvent, signPassport, issuerAddress } = deps;
53
+ const { db, auth, isTrustedRole, generateId, generateRecipientCode, DONATION_VALID_PCTS, INTERNAL_AUDITOR_ID, addHours, getActiveFlashSale, applyCouponToOrder, getProtocolParam, getProductShareChain, isAllowedSponsor, resolveInviteCodeRef, checkStockAndMaybeDelist, auditSponsorChainCross, appendOrderEvent, transition, notifyTransition, shouldAutoAccept, ensureCharityRep, broadcastSystemEvent, signPassport, issuerAddress } = deps;
7
54
  app.post('/api/orders', async (req, res) => {
8
55
  const user = auth(req, res);
9
56
  if (!user)
@@ -16,8 +63,8 @@ export function registerOrdersCreateRoutes(app, deps) {
16
63
  // 2026-05-23 P0 audit fix 2.1:agent_attestations spend_cap 强制
17
64
  const apiKey = req.headers.authorization?.replace('Bearer ', '');
18
65
  if (apiKey) {
19
- const cap = db.prepare(`SELECT spend_cap_per_order, spend_cap_daily FROM agent_attestations
20
- WHERE api_key = ? AND user_id = ? AND revoked_at IS NULL`).get(apiKey, user.id);
66
+ const cap = await dbOne(`SELECT spend_cap_per_order, spend_cap_daily FROM agent_attestations
67
+ WHERE api_key = ? AND user_id = ? AND revoked_at IS NULL`, [apiKey, user.id]);
21
68
  if (cap) {
22
69
  const estQty = Math.max(1, Math.floor(Number(req.body?.quantity ?? 1)));
23
70
  const estPrice = Number(req.body?.expected_price ?? 0);
@@ -30,9 +77,8 @@ export function registerOrdersCreateRoutes(app, deps) {
30
77
  });
31
78
  }
32
79
  if (cap.spend_cap_daily != null) {
33
- const todaySpent = db.prepare(`SELECT COALESCE(SUM(total_amount), 0) as t
34
- FROM orders WHERE buyer_id = ? AND created_at > datetime('now', '-24 hours') AND status != 'cancelled'`)
35
- .get(user.id).t;
80
+ const todaySpent = (await dbOne(`SELECT COALESCE(SUM(total_amount), 0) as t
81
+ FROM orders WHERE buyer_id = ? AND created_at > datetime('now', '-24 hours') AND status != 'cancelled'`, [user.id])).t;
36
82
  if (todaySpent + estTotal > cap.spend_cap_daily) {
37
83
  return void res.status(403).json({
38
84
  error: `24h 累计 ${todaySpent}+${estTotal} 超 agent 日上限 ${cap.spend_cap_daily} WAZ(用户设定)`,
@@ -93,17 +139,16 @@ export function registerOrdersCreateRoutes(app, deps) {
93
139
  deliveryWindowJson = JSON.stringify({ day_type: dt, time_range: tr, flexible: fl });
94
140
  }
95
141
  }
96
- const product = db.prepare(`SELECT p.*, u.id as seller_uid FROM products p
97
- JOIN users u ON p.seller_id = u.id WHERE p.id = ? AND p.status = 'active'`).get(product_id);
142
+ const product = await dbOne(`SELECT p.*, u.id as seller_uid FROM products p
143
+ JOIN users u ON p.seller_id = u.id WHERE p.id = ? AND p.status = 'active'`, [product_id]);
98
144
  if (!product)
99
145
  return void res.json({ error: '商品不存在或已下架' });
100
146
  let variant = null;
101
147
  if (Number(product.has_variants) === 1) {
102
148
  if (!variant_id)
103
149
  return void res.json({ error: '该商品需选择规格', error_code: 'VARIANT_REQUIRED' });
104
- const v = db.prepare(`SELECT id, price_override, stock, options_json
105
- FROM product_variants WHERE id = ? AND product_id = ? AND is_active = 1`)
106
- .get(variant_id, product_id);
150
+ const v = await dbOne(`SELECT id, price_override, stock, options_json
151
+ FROM product_variants WHERE id = ? AND product_id = ? AND is_active = 1`, [variant_id, product_id]);
107
152
  if (!v)
108
153
  return void res.json({ error: '规格不存在或已下架' });
109
154
  if (Number(v.stock) < reqQty)
@@ -151,6 +196,9 @@ export function registerOrdersCreateRoutes(app, deps) {
151
196
  couponDiscount = result.discount || 0;
152
197
  }
153
198
  // 验证 session_token(如果提供)
199
+ // RFC-016: 价格锁是【一次性】消费 — SELECT 校验 → mark used 之间【不能有 await】,
200
+ // 否则两个并发下单会都读到 used_at=NULL 再各自 mark,复用同一 token(Codex #224)。
201
+ // 故整块保持同步 better-sqlite3 调用(Node 单线程内原子,无让步);Phase 3 随订单路径迁 pg 行锁。
154
202
  if (session_token) {
155
203
  const session = db.prepare(`
156
204
  SELECT * FROM price_sessions WHERE token = ? AND product_id = ? AND user_id = ?
@@ -188,7 +236,8 @@ export function registerOrdersCreateRoutes(app, deps) {
188
236
  const insurancePremium = toDecimal(insurancePremiumU);
189
237
  const totalAmount = toDecimal(totalAmountU);
190
238
  const donationAmount = toDecimal(donationAmountU);
191
- const wallet = db.prepare('SELECT balance FROM wallets WHERE user_id = ?').get(user.id);
239
+ // 友好预检查():真正的守恒在下面的同步事务内(applyWalletDelta 绝对值落库)
240
+ const wallet = await dbOne('SELECT balance FROM wallets WHERE user_id = ?', [user.id]);
192
241
  if (!wallet)
193
242
  return void res.status(500).json({ error: '钱包记录缺失', error_code: 'WALLET_MISSING' });
194
243
  if (toUnits(wallet.balance) < totalAmountU + donationAmountU)
@@ -203,10 +252,13 @@ export function registerOrdersCreateRoutes(app, deps) {
203
252
  const buyer = db.prepare("SELECT sponsor_id, sponsor_path, region FROM users WHERE id = ?").get(user.id);
204
253
  // 孤儿用户首次绑 sponsor:buyer 无 sponsor + 客户端传 sponsor_hint
205
254
  // 校验:① 非自己 ② 防环路 ③ hint 必须是 verified buyer
206
- const sponsorHint = (typeof req.body.sponsor_hint === 'string' && req.body.sponsor_hint) ? String(req.body.sponsor_hint) : null;
207
- if (!buyer.sponsor_id && sponsorHint && sponsorHint !== user.id) {
255
+ // sponsor_hint from the client is now an invite code (permanent_code [+ -L/-R]) resolve it to a
256
+ // user id first; usr_xxx / @handle / handle no longer bind a sponsor (matches the narrowed surface).
257
+ const sponsorHintRaw = (typeof req.body.sponsor_hint === 'string' && req.body.sponsor_hint) ? String(req.body.sponsor_hint) : null;
258
+ const sponsorHintRef = sponsorHintRaw ? resolveInviteCodeRef(sponsorHintRaw) : null;
259
+ if (!buyer.sponsor_id && sponsorHintRef && sponsorHintRef.userId !== user.id) {
208
260
  const hint = db.prepare("SELECT id, sponsor_path FROM users WHERE id = ? AND id NOT IN ('sys_protocol', ?)")
209
- .get(sponsorHint, INTERNAL_AUDITOR_ID);
261
+ .get(sponsorHintRef.userId, INTERNAL_AUDITOR_ID);
210
262
  if (hint && isAllowedSponsor(hint.id)) {
211
263
  const hintPath = hint.sponsor_path || '';
212
264
  if (!hintPath.split('>').includes(user.id)) {
@@ -222,6 +274,9 @@ export function registerOrdersCreateRoutes(app, deps) {
222
274
  const r = db.prepare("SELECT max_levels FROM region_config WHERE region = ? AND active = 1").get(buyer?.region || 'global');
223
275
  return r?.max_levels ?? 3;
224
276
  })();
277
+ // 店铺推荐懒升级:在反推商品链【之前】尝试把"店铺推荐 + 推荐人真实成交过同款"升级为本商品归因,
278
+ // 这样本订单快照就能拿到 L1/L2/L3(不覆盖已有有效直接归因)。
279
+ maybePromoteShopReferralToProductAttribution(db, { internalAuditorId: INTERNAL_AUDITOR_ID, isAllowedSponsor }, product.id, product.seller_uid, user.id);
225
280
  // 商品分享奖励链(per-product),与 PV 系统 sponsor_path 完全解耦
226
281
  // 反推方向:谁分享了 product 给 buyer? → 该 sharer 是 L1
227
282
  const productChain = getProductShareChain(product.id, user.id, 3);
@@ -233,11 +288,11 @@ export function registerOrdersCreateRoutes(app, deps) {
233
288
  const buyerRegionSnapshot = buyer?.region || 'global';
234
289
  // P2P:若为 P2P 商品,下单时快照 content_hash(争议时凭买家所见 hash 判定)
235
290
  const contentHashSnapshot = (Number(product.p2p_mode) === 1 && product.content_hash) ? String(product.content_hash) : null;
236
- // RFC-008 stage 1:赔付背书快照。起步免赔付阶段(require_seller_stake=0)= 0 → 违约只退款不没收、不印钱、零门槛。
237
- // ⚠️ "要求质押"档(=1,下单锁 stake、backing=total×stake_rate(信誉))属后续【收紧】阶段,届时在此计算并锁定。
238
- const stakeBacking = Number(getProtocolParam('require_seller_stake', 0)) === 1
239
- ? 0 // TODO(tighten stage): backing = total × stakeRate(seller reputation) + 在此 lock balance→staked
240
- : 0;
291
+ // RFC-008 stage 1:赔付背书快照恒为 0 → 违约只退款不没收、不印钱、零门槛(起步免赔付)。
292
+ // stake-required 模式(require_seller_stake=1)【尚未实现】且该 param 已被 governance 锁在 0(max=0,
293
+ // 见 server.ts DEFAULT_PARAMS + 迁移,Codex #111)—— 故不读它(读了也只会是 0),避免"假开关"误导。
294
+ // Phase 3 钱路径迁移时在此按信誉算 backing = total×stake_rate 并原子锁 balance→staked、放开该 param。
295
+ const stakeBacking = 0;
241
296
  db.prepare(`INSERT INTO orders (
242
297
  id, product_id, buyer_id, seller_id, quantity, unit_price, total_amount, escrow_amount,
243
298
  status, shipping_address, notes, pay_deadline, accept_deadline, ship_deadline,
@@ -1,12 +1,13 @@
1
1
  // RFC-011 §⑥ 事件游标流(纯 db 函数,party-gated)
2
2
  import { listOrderEventsSince } from '../../layer0-foundation/L0-2-state-machine/order-chain.js';
3
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
3
4
  export function registerOrdersReadRoutes(app, deps) {
4
5
  const { db, auth, getOrderStatus, getOrderChain, verifyOrderChain, getOrderDispute } = deps;
5
- app.get('/api/orders', (req, res) => {
6
+ app.get('/api/orders', async (req, res) => {
6
7
  const user = auth(req, res);
7
8
  if (!user)
8
9
  return;
9
- const orders = db.prepare(`
10
+ const orders = await dbAll(`
10
11
  SELECT o.*, p.title as product_title, p.images,
11
12
  ub.name as buyer_name, us.name as seller_name
12
13
  FROM orders o
@@ -15,7 +16,7 @@ export function registerOrdersReadRoutes(app, deps) {
15
16
  JOIN users us ON o.seller_id = us.id
16
17
  WHERE o.buyer_id = ? OR o.seller_id = ? OR o.logistics_id = ?
17
18
  ORDER BY o.created_at DESC LIMIT 50
18
- `).all(user.id, user.id, user.id);
19
+ `, [user.id, user.id, user.id]);
19
20
  // B2 隐私购物:列表里也做相同 mask(防 seller/logistics 通过列表绕过详情 mask)
20
21
  for (const o of orders) {
21
22
  if (Number(o.anonymous_recipient) === 1 && o.buyer_id !== user.id) {
@@ -28,7 +29,7 @@ export function registerOrdersReadRoutes(app, deps) {
28
29
  res.json(orders);
29
30
  });
30
31
  // Wave D-2: 订单导出 CSV
31
- app.get('/api/orders/export', (req, res) => {
32
+ app.get('/api/orders/export', async (req, res) => {
32
33
  const user = auth(req, res);
33
34
  if (!user)
34
35
  return;
@@ -47,7 +48,7 @@ export function registerOrdersReadRoutes(app, deps) {
47
48
  params.push(to);
48
49
  }
49
50
  const EXPORT_LIMIT = 5000;
50
- const rows = db.prepare(`
51
+ const rows = await dbAll(`
51
52
  SELECT o.id, o.created_at, o.status, o.quantity, o.unit_price, o.total_amount,
52
53
  o.coupon_discount, o.variant_options_snapshot, o.shipping_address,
53
54
  p.title as product_title, p.category,
@@ -59,7 +60,7 @@ export function registerOrdersReadRoutes(app, deps) {
59
60
  JOIN users us ON us.id = o.seller_id
60
61
  WHERE ${where.join(' AND ')}
61
62
  ORDER BY o.created_at DESC LIMIT ?
62
- `).all(...params, EXPORT_LIMIT + 1);
63
+ `, [...params, EXPORT_LIMIT + 1]);
63
64
  // P1-4: 触达上限 → X-Truncated 头
64
65
  const truncated = rows.length > EXPORT_LIMIT;
65
66
  if (truncated)
@@ -99,38 +100,38 @@ export function registerOrdersReadRoutes(app, deps) {
99
100
  res.send('' + lines.join('\n'));
100
101
  });
101
102
  // 订单签名链 — 当事人 + arbitrator + admin 可查
102
- app.get('/api/orders/:id/chain', (req, res) => {
103
+ app.get('/api/orders/:id/chain', async (req, res) => {
103
104
  const user = auth(req, res);
104
105
  if (!user)
105
106
  return;
106
- const order = db.prepare('SELECT buyer_id, seller_id, logistics_id FROM orders WHERE id = ?').get(req.params.id);
107
+ const order = await dbOne('SELECT buyer_id, seller_id, logistics_id FROM orders WHERE id = ?', [req.params.id]);
107
108
  if (!order)
108
109
  return void res.status(404).json({ error: '订单不存在' });
109
110
  const uid = user.id;
110
111
  const isParty = uid === order.buyer_id || uid === order.seller_id || uid === order.logistics_id || user.role === 'arbitrator' || user.role === 'admin';
111
112
  if (!isParty)
112
113
  return void res.status(403).json({ error: '无权查看此订单链' });
113
- const chain = getOrderChain(db, req.params.id);
114
- const verification = verifyOrderChain(db, req.params.id);
114
+ const chain = await getOrderChain(db, req.params.id);
115
+ const verification = await verifyOrderChain(db, req.params.id);
115
116
  res.json({ chain, verification });
116
117
  });
117
118
  // RFC-011 §⑥:事件游标流 —— 集成方 agent 拉"自 cursor 以来与我相关的订单变化"(agent 拉,非 webhook)。
118
119
  // party-gated(只见自己当事的订单事件,= /chain 同口径,不变量 2:活性 ≤ 读边界);
119
120
  // 结构性事件 + 哈希链字段(验链防篡改),完整 payload 仍走 party-gated /chain。
120
- app.get('/api/agent/events', (req, res) => {
121
+ app.get('/api/agent/events', async (req, res) => {
121
122
  const user = auth(req, res);
122
123
  if (!user)
123
124
  return;
124
125
  const since = typeof req.query.since === 'string' ? req.query.since : undefined;
125
126
  const limit = Number(req.query.limit) || 50;
126
- const r = listOrderEventsSince(db, user.id, since, limit);
127
+ const r = await listOrderEventsSince(db, user.id, since, limit);
127
128
  res.setHeader('Cache-Control', 'no-store'); // 事件流不缓存
128
129
  res.json({
129
130
  ...r,
130
131
  note: 'Cursor stream of order events you are party to. Pass ?since=<next_cursor> to page. Pull, not push. event_hash+prev_event_hash verify chain integrity; full payload via GET /api/orders/:id/chain.',
131
132
  });
132
133
  });
133
- app.get('/api/orders/:id', (req, res) => {
134
+ app.get('/api/orders/:id', async (req, res) => {
134
135
  const user = auth(req, res);
135
136
  if (!user)
136
137
  return;
@@ -145,8 +146,8 @@ export function registerOrdersReadRoutes(app, deps) {
145
146
  }
146
147
  // M8: 二手订单从 secondhand_items 查;商家订单从 products 查
147
148
  const product = order.source === 'secondhand'
148
- ? (() => {
149
- const si = db.prepare('SELECT title, price, images FROM secondhand_items WHERE id = ?').get(order.product_id);
149
+ ? await (async () => {
150
+ const si = await dbOne('SELECT title, price, images FROM secondhand_items WHERE id = ?', [order.product_id]);
150
151
  if (!si)
151
152
  return null;
152
153
  try {
@@ -156,10 +157,10 @@ export function registerOrdersReadRoutes(app, deps) {
156
157
  return { title: si.title, price: si.price, images: [] };
157
158
  }
158
159
  })()
159
- : db.prepare('SELECT id, title, price, images, return_days FROM products WHERE id = ?').get(order.product_id);
160
- const dispute = getOrderDispute(db, req.params.id);
160
+ : await dbOne('SELECT id, title, price, images, return_days FROM products WHERE id = ?', [order.product_id]);
161
+ const dispute = await getOrderDispute(db, req.params.id);
161
162
  // 为每条历史记录附上证据描述
162
- const history = statusInfo.history.map(h => {
163
+ const history = await Promise.all(statusInfo.history.map(async (h) => {
163
164
  // P1 fix: 单条脏 evidence_ids 不应封死整个 order 详情
164
165
  let ids = [];
165
166
  try {
@@ -169,10 +170,10 @@ export function registerOrdersReadRoutes(app, deps) {
169
170
  }
170
171
  catch { }
171
172
  const evidenceItems = ids.length
172
- ? db.prepare(`SELECT description, type FROM evidence WHERE id IN (${ids.map(() => '?').join(',')})`).all(...ids)
173
+ ? await dbAll(`SELECT description, type FROM evidence WHERE id IN (${ids.map(() => '?').join(',')})`, ids)
173
174
  : [];
174
175
  return { ...h, evidence_items: evidenceItems };
175
- });
176
+ }));
176
177
  // 物流跟踪摘要:从历史中提取所有物流操作的证据
177
178
  const LOGISTICS_STEPS = ['shipped', 'picked_up', 'in_transit', 'delivered'];
178
179
  const trackingInfo = history
@@ -1,7 +1,9 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerP2pProductsRoutes(app, deps) {
2
- const { db, auth, generateId, verifyP2pSig, isValidPeerEndpoint, isFreshSignedAt, P2P_TITLE_MAX, P2P_THUMB_MAX, P2P_DAILY_CAP, RFQ_MAX_PRICE, RFQ_MAX_QTY } = deps;
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
4
+ const { auth, generateId, verifyP2pSig, isValidPeerEndpoint, isFreshSignedAt, P2P_TITLE_MAX, P2P_THUMB_MAX, P2P_DAILY_CAP, RFQ_MAX_PRICE, RFQ_MAX_QTY } = deps;
3
5
  // 发布 / 重发 P2P 商品
4
- app.post('/api/p2p-products', (req, res) => {
6
+ app.post('/api/p2p-products', async (req, res) => {
5
7
  const user = auth(req, res);
6
8
  if (!user)
7
9
  return;
@@ -40,26 +42,36 @@ export function registerP2pProductsRoutes(app, deps) {
40
42
  if (thumbnail && thumbnail.length > P2P_THUMB_MAX)
41
43
  return void res.json({ error: `thumbnail 超过 ${P2P_THUMB_MAX} 字节` });
42
44
  // 频率限制
43
- const today = db.prepare("SELECT COUNT(1) as n FROM products WHERE seller_id = ? AND p2p_mode = 1 AND created_at > datetime('now','-1 day')").get(user.id).n;
45
+ const today = (await dbOne("SELECT COUNT(1) as n FROM products WHERE seller_id = ? AND p2p_mode = 1 AND created_at > datetime('now','-1 day')", [user.id])).n;
44
46
  if (today >= P2P_DAILY_CAP)
45
47
  return void res.json({ error: `今日 P2P 上架已达上限 ${P2P_DAILY_CAP}` });
46
48
  const category = String(body.category || 'general');
47
49
  const region = String(body.region || user.region || '全国');
48
50
  const id = generateId('p');
49
- db.prepare(`
51
+ await dbRun(`
50
52
  INSERT INTO products (id, seller_id, title, description, price, stock, status, images, ship_regions,
51
53
  handling_hours, commission_rate, category_id, stake_amount, p2p_mode, content_hash, peer_endpoint,
52
54
  content_signature, content_signed_at)
53
55
  VALUES (?,?,?,?,?,?,'active',?,?,24,0.10,'cat_default',0,1,?,?,?,?)
54
- `).run(id, user.id, title, `[P2P] ${title}(完整详情见卖家节点)`, price, stock, thumbnail ? JSON.stringify([thumbnail]) : '[]', region, contentHash, peerEndpoint || null, signature, signedAt);
56
+ `, [
57
+ id, user.id, title,
58
+ `[P2P] ${title}(完整详情见卖家节点)`,
59
+ price, stock,
60
+ thumbnail ? JSON.stringify([thumbnail]) : '[]',
61
+ region,
62
+ contentHash,
63
+ peerEndpoint || null,
64
+ signature,
65
+ signedAt,
66
+ ]);
55
67
  res.json({ id, content_hash: contentHash });
56
68
  });
57
69
  // 更新(重发 hash + signature,价格/库存/标题可改;旧 hash 给在途订单保留)
58
- app.patch('/api/p2p-products/:id', (req, res) => {
70
+ app.patch('/api/p2p-products/:id', async (req, res) => {
59
71
  const user = auth(req, res);
60
72
  if (!user)
61
73
  return;
62
- const product = db.prepare("SELECT * FROM products WHERE id = ? AND p2p_mode = 1").get(req.params.id);
74
+ const product = await dbOne("SELECT * FROM products WHERE id = ? AND p2p_mode = 1", [req.params.id]);
63
75
  if (!product)
64
76
  return void res.status(404).json({ error: 'P2P 商品不存在' });
65
77
  if (product.seller_id !== user.id)
@@ -128,28 +140,28 @@ export function registerP2pProductsRoutes(app, deps) {
128
140
  return void res.json({ error: '无任何修改' });
129
141
  updates.push("updated_at = datetime('now')");
130
142
  args.push(req.params.id);
131
- db.prepare(`UPDATE products SET ${updates.join(', ')} WHERE id = ?`).run(...args);
143
+ await dbRun(`UPDATE products SET ${updates.join(', ')} WHERE id = ?`, args);
132
144
  res.json({ success: true });
133
145
  });
134
146
  // 下架(保留行 + status='warehouse',在途订单 hash 仍可证)
135
- app.delete('/api/p2p-products/:id', (req, res) => {
147
+ app.delete('/api/p2p-products/:id', async (req, res) => {
136
148
  const user = auth(req, res);
137
149
  if (!user)
138
150
  return;
139
- const product = db.prepare("SELECT seller_id, status FROM products WHERE id = ? AND p2p_mode = 1").get(req.params.id);
151
+ const product = await dbOne("SELECT seller_id, status FROM products WHERE id = ? AND p2p_mode = 1", [req.params.id]);
140
152
  if (!product)
141
153
  return void res.status(404).json({ error: 'P2P 商品不存在' });
142
154
  if (product.seller_id !== user.id)
143
155
  return void res.status(403).json({ error: '仅卖家本人可下架' });
144
- const pendingOrders = db.prepare("SELECT COUNT(1) as n FROM orders WHERE product_id = ? AND status NOT IN ('completed','cancelled','refunded','expired')").get(req.params.id).n;
156
+ const pendingOrders = (await dbOne("SELECT COUNT(1) as n FROM orders WHERE product_id = ? AND status NOT IN ('completed','cancelled','refunded','expired')", [req.params.id])).n;
145
157
  if (pendingOrders > 0)
146
158
  return void res.json({ error: `该商品有 ${pendingOrders} 个进行中订单,无法下架` });
147
- db.prepare("UPDATE products SET status = 'warehouse', updated_at = datetime('now') WHERE id = ?").run(req.params.id);
159
+ await dbRun("UPDATE products SET status = 'warehouse', updated_at = datetime('now') WHERE id = ?", [req.params.id]);
148
160
  res.json({ success: true });
149
161
  });
150
162
  // 公开:列表
151
- app.get('/api/p2p-products', (_req, res) => {
152
- const rows = db.prepare(`
163
+ app.get('/api/p2p-products', async (_req, res) => {
164
+ const rows = await dbAll(`
153
165
  SELECT p.id, p.seller_id, p.title, p.price, p.stock, p.images as thumbnail_json,
154
166
  p.ship_regions as region, p.content_hash, p.peer_endpoint, p.content_signed_at,
155
167
  u.handle as seller_handle, u.region as seller_region
@@ -158,19 +170,19 @@ export function registerP2pProductsRoutes(app, deps) {
158
170
  WHERE p.status = 'active' AND p.stock > 0 AND p.p2p_mode = 1
159
171
  ORDER BY p.created_at DESC
160
172
  LIMIT 50
161
- `).all();
173
+ `);
162
174
  res.json({ items: rows });
163
175
  });
164
176
  // 公开:详情(含 hash + peer_endpoint)
165
- app.get('/api/p2p-products/:id', (req, res) => {
166
- const row = db.prepare(`
177
+ app.get('/api/p2p-products/:id', async (req, res) => {
178
+ const row = await dbOne(`
167
179
  SELECT p.id, p.seller_id, p.title, p.price, p.stock, p.images as thumbnail_json,
168
180
  p.ship_regions as region, p.content_hash, p.peer_endpoint, p.content_signature, p.content_signed_at,
169
181
  u.handle as seller_handle, u.region as seller_region, u.permanent_code as seller_code
170
182
  FROM products p
171
183
  LEFT JOIN users u ON u.id = p.seller_id
172
184
  WHERE p.id = ? AND p.p2p_mode = 1
173
- `).get(req.params.id);
185
+ `, [req.params.id]);
174
186
  if (!row)
175
187
  return void res.status(404).json({ error: 'P2P 商品不存在' });
176
188
  res.json({ product: row });