@seasonkoh/webaz 0.1.24 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +2 -0
  2. package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
  3. package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
  4. package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
  5. package/dist/layer0-foundation/L0-1-database/db.js +65 -0
  6. package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
  7. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
  8. package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
  9. package/dist/layer1-agent/L1-1-mcp-server/server.js +165 -64
  10. package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
  11. package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
  12. package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
  13. package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
  14. package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
  15. package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +173 -0
  16. package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
  17. package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
  18. package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +10 -2
  19. package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
  20. package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
  21. package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
  22. package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
  23. package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
  24. package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
  25. package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
  26. package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
  27. package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
  28. package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
  29. package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
  30. package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
  31. package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
  32. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
  33. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
  34. package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
  35. package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
  36. package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
  37. package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
  38. package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
  39. package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
  40. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
  41. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
  42. package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
  43. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
  44. package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
  45. package/dist/pwa/acp-feed.js +13 -1
  46. package/dist/pwa/contract-fingerprint.js +2 -0
  47. package/dist/pwa/endpoint-actions.js +5 -1
  48. package/dist/pwa/goal-index.js +8 -8
  49. package/dist/pwa/human-presence.js +62 -0
  50. package/dist/pwa/public/app.js +575 -68
  51. package/dist/pwa/public/i18n.js +29 -20
  52. package/dist/pwa/public/index.html +1 -0
  53. package/dist/pwa/public/openapi.json +2 -2
  54. package/dist/pwa/rate-limit.js +22 -0
  55. package/dist/pwa/routes/account-deletion.js +15 -13
  56. package/dist/pwa/routes/addresses.js +10 -9
  57. package/dist/pwa/routes/admin-admins.js +13 -14
  58. package/dist/pwa/routes/admin-analytics.js +109 -69
  59. package/dist/pwa/routes/admin-catalog.js +13 -11
  60. package/dist/pwa/routes/admin-editor-picks.js +15 -10
  61. package/dist/pwa/routes/admin-events.js +5 -3
  62. package/dist/pwa/routes/admin-health.js +2 -1
  63. package/dist/pwa/routes/admin-moderation.js +26 -29
  64. package/dist/pwa/routes/admin-ops.js +22 -21
  65. package/dist/pwa/routes/admin-protocol-params.js +16 -19
  66. package/dist/pwa/routes/admin-reports.js +23 -21
  67. package/dist/pwa/routes/admin-tokenomics.js +26 -25
  68. package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
  69. package/dist/pwa/routes/admin-users-query.js +54 -53
  70. package/dist/pwa/routes/admin-verifier-flow.js +82 -41
  71. package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
  72. package/dist/pwa/routes/admin-wallet-ops.js +7 -5
  73. package/dist/pwa/routes/agent-buy.js +46 -22
  74. package/dist/pwa/routes/agent-governance.js +52 -56
  75. package/dist/pwa/routes/ai.js +7 -5
  76. package/dist/pwa/routes/analytics.js +43 -41
  77. package/dist/pwa/routes/anchors.js +19 -20
  78. package/dist/pwa/routes/announcements.js +13 -13
  79. package/dist/pwa/routes/arbitrator.js +97 -31
  80. package/dist/pwa/routes/auction.js +153 -114
  81. package/dist/pwa/routes/auth-login.js +6 -4
  82. package/dist/pwa/routes/auth-read.js +11 -9
  83. package/dist/pwa/routes/auth-register.js +35 -20
  84. package/dist/pwa/routes/auth-sessions.js +12 -11
  85. package/dist/pwa/routes/blocklist.js +16 -15
  86. package/dist/pwa/routes/build-feedback.js +10 -9
  87. package/dist/pwa/routes/build-reputation.js +6 -2
  88. package/dist/pwa/routes/build-tasks.js +45 -13
  89. package/dist/pwa/routes/buyer-feeds.js +27 -25
  90. package/dist/pwa/routes/cart.js +16 -15
  91. package/dist/pwa/routes/charity.js +212 -150
  92. package/dist/pwa/routes/chat.js +42 -43
  93. package/dist/pwa/routes/checkin-tasks.js +10 -9
  94. package/dist/pwa/routes/checkout-helpers.js +12 -10
  95. package/dist/pwa/routes/claim-initiators.js +34 -14
  96. package/dist/pwa/routes/claim-verify.js +86 -53
  97. package/dist/pwa/routes/claim-voting.js +43 -18
  98. package/dist/pwa/routes/contribution-identity.js +147 -0
  99. package/dist/pwa/routes/contribution-score.js +19 -0
  100. package/dist/pwa/routes/coupons.js +19 -16
  101. package/dist/pwa/routes/dashboards.js +18 -16
  102. package/dist/pwa/routes/dispute-cases.js +25 -24
  103. package/dist/pwa/routes/disputes-read.js +45 -51
  104. package/dist/pwa/routes/disputes-write.js +124 -61
  105. package/dist/pwa/routes/evidence.js +9 -9
  106. package/dist/pwa/routes/external-anchors.js +13 -12
  107. package/dist/pwa/routes/feedback.js +29 -33
  108. package/dist/pwa/routes/flash-sales.js +18 -16
  109. package/dist/pwa/routes/follows.js +25 -24
  110. package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
  111. package/dist/pwa/routes/governance-onboarding.js +70 -59
  112. package/dist/pwa/routes/group-buys.js +22 -22
  113. package/dist/pwa/routes/growth.js +33 -30
  114. package/dist/pwa/routes/import-product.js +12 -10
  115. package/dist/pwa/routes/kyc.js +9 -8
  116. package/dist/pwa/routes/leaderboard.js +20 -18
  117. package/dist/pwa/routes/listings.js +23 -22
  118. package/dist/pwa/routes/logistics.js +10 -8
  119. package/dist/pwa/routes/manifests.js +27 -27
  120. package/dist/pwa/routes/me-data.js +23 -21
  121. package/dist/pwa/routes/notifications.js +7 -6
  122. package/dist/pwa/routes/offers.js +30 -12
  123. package/dist/pwa/routes/orders-action.js +33 -17
  124. package/dist/pwa/routes/orders-create.js +75 -20
  125. package/dist/pwa/routes/orders-read.js +21 -20
  126. package/dist/pwa/routes/p2p-products.js +30 -18
  127. package/dist/pwa/routes/payments-governance.js +61 -56
  128. package/dist/pwa/routes/peers.js +9 -8
  129. package/dist/pwa/routes/pin-receipts.js +13 -13
  130. package/dist/pwa/routes/products-aliases.js +12 -10
  131. package/dist/pwa/routes/products-claims.js +36 -17
  132. package/dist/pwa/routes/products-create.js +53 -38
  133. package/dist/pwa/routes/products-crud.js +17 -16
  134. package/dist/pwa/routes/products-links.js +49 -26
  135. package/dist/pwa/routes/products-list.js +6 -4
  136. package/dist/pwa/routes/products-meta.js +40 -39
  137. package/dist/pwa/routes/products-update.js +19 -5
  138. package/dist/pwa/routes/profile-credentials.js +14 -16
  139. package/dist/pwa/routes/profile-identity.js +14 -13
  140. package/dist/pwa/routes/profile-location.js +7 -6
  141. package/dist/pwa/routes/profile-placement.js +19 -17
  142. package/dist/pwa/routes/profile-prefs.js +11 -11
  143. package/dist/pwa/routes/promoter.js +55 -49
  144. package/dist/pwa/routes/public-build-tasks.js +19 -0
  145. package/dist/pwa/routes/public-utils.js +108 -46
  146. package/dist/pwa/routes/push.js +16 -15
  147. package/dist/pwa/routes/ratings.js +30 -30
  148. package/dist/pwa/routes/recover-key.js +13 -12
  149. package/dist/pwa/routes/referral.js +37 -32
  150. package/dist/pwa/routes/reputation.js +3 -2
  151. package/dist/pwa/routes/returns.js +76 -73
  152. package/dist/pwa/routes/reviews.js +41 -18
  153. package/dist/pwa/routes/rewards-apply.js +16 -15
  154. package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
  155. package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
  156. package/dist/pwa/routes/rfqs.js +163 -85
  157. package/dist/pwa/routes/search.js +16 -14
  158. package/dist/pwa/routes/secondhand.js +25 -22
  159. package/dist/pwa/routes/seller-quota.js +24 -26
  160. package/dist/pwa/routes/share-redirects.js +59 -55
  161. package/dist/pwa/routes/shareables-interactions.js +34 -35
  162. package/dist/pwa/routes/shareables.js +55 -51
  163. package/dist/pwa/routes/shop-referral.js +57 -0
  164. package/dist/pwa/routes/shops.js +20 -18
  165. package/dist/pwa/routes/signaling.js +10 -9
  166. package/dist/pwa/routes/skill-market.js +16 -16
  167. package/dist/pwa/routes/skills.js +15 -14
  168. package/dist/pwa/routes/snf.js +14 -13
  169. package/dist/pwa/routes/tags.js +10 -9
  170. package/dist/pwa/routes/task-proposals.js +45 -0
  171. package/dist/pwa/routes/trial.js +69 -51
  172. package/dist/pwa/routes/trusted-kpi.js +20 -18
  173. package/dist/pwa/routes/url-claim.js +67 -28
  174. package/dist/pwa/routes/users-public.js +62 -60
  175. package/dist/pwa/routes/variants.js +12 -13
  176. package/dist/pwa/routes/verifier-user.js +61 -21
  177. package/dist/pwa/routes/verify-tasks.js +49 -25
  178. package/dist/pwa/routes/waitlist.js +16 -15
  179. package/dist/pwa/routes/wallet-read.js +74 -36
  180. package/dist/pwa/routes/wallet-write.js +12 -9
  181. package/dist/pwa/routes/webauthn.js +25 -26
  182. package/dist/pwa/routes/webhooks.js +26 -26
  183. package/dist/pwa/routes/welcome.js +45 -50
  184. package/dist/pwa/routes/wishlist-qa.js +29 -32
  185. package/dist/pwa/server.js +237 -81
  186. package/dist/version.js +1 -1
  187. package/package.json +47 -2
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import crypto from 'crypto';
16
16
  import { generateId } from '../L0-1-database/schema.js';
17
+ import { dbOne, dbAll } from '../L0-1-database/db.js'; // RFC-016 异步 seam(纯读)
17
18
  export function initOrderChainSchema(db) {
18
19
  db.exec(`
19
20
  CREATE TABLE IF NOT EXISTS order_events (
@@ -42,10 +43,11 @@ export function initOrderChainSchema(db) {
42
43
  }
43
44
  catch { }
44
45
  }
45
- export function listOrderEventsSince(db, userId, since, limit) {
46
+ // RFC-016 Phase 1:纯读 → 异步 seam(db 参数保留签名兼容;调用点 orders-read.ts 均 inTx=false,非状态机写路径)
47
+ export async function listOrderEventsSince(_db, userId, since, limit) {
46
48
  const lim = Math.min(200, Math.max(1, Math.floor(limit) || 50));
47
49
  const sinceRid = since && /^\d+$/.test(since) ? Number(since) : 0;
48
- const rows = db.prepare(`
50
+ const rows = await dbAll(`
49
51
  SELECT e.rowid AS rid, e.order_id, e.seq, e.event_type, e.from_status, e.to_status, e.actor_role,
50
52
  e.event_hash, e.prev_event_hash, e.signed_at, e.created_at
51
53
  FROM order_events e
@@ -54,7 +56,7 @@ export function listOrderEventsSince(db, userId, since, limit) {
54
56
  AND e.rowid > ?
55
57
  ORDER BY e.rowid ASC
56
58
  LIMIT ?
57
- `).all(userId, userId, userId, sinceRid, lim);
59
+ `, [userId, userId, userId, sinceRid, lim]);
58
60
  const events = rows.map(r => ({
59
61
  cursor: String(r.rid),
60
62
  order_id: r.order_id, seq: r.seq, event_type: r.event_type,
@@ -118,16 +120,16 @@ export function appendOrderEvent(db, args) {
118
120
  return { id, seq, event_hash: eventHash };
119
121
  }
120
122
  // 验证整条链 — 仲裁时或任何审计场景可调
121
- export function verifyOrderChain(db, orderId) {
122
- const rows = db.prepare(`SELECT seq, prev_event_hash, event_hash, payload_json, signature, actor_id
123
- FROM order_events WHERE order_id = ? ORDER BY seq ASC`).all(orderId);
123
+ export async function verifyOrderChain(_db, orderId) {
124
+ const rows = await dbAll(`SELECT seq, prev_event_hash, event_hash, payload_json, signature, actor_id
125
+ FROM order_events WHERE order_id = ? ORDER BY seq ASC`, [orderId]);
124
126
  if (rows.length === 0)
125
127
  return { ok: false, total: 0, verified: 0, reason: 'empty_chain' };
126
128
  // 修复 ultrareview bug_007:transition() 的 appendOrderEvent 是 try-catch 软失败
127
129
  // (legacy actor 缺 api_key 等场景),但 order_state_history 仍会 commit。
128
130
  // 检测:chain 行数应该至少等于 history 行数(chain ≥ history,因为可能有 open genesis 事件无 history)
129
131
  // 不等就说明有 silent drop,链不完整 — UI 不应再显示"验证通过"
130
- const histCount = db.prepare(`SELECT COUNT(*) as n FROM order_state_history WHERE order_id = ?`).get(orderId);
132
+ const histCount = await dbOne(`SELECT COUNT(*) as n FROM order_state_history WHERE order_id = ?`, [orderId]);
131
133
  if (histCount && rows.length < histCount.n) {
132
134
  return { ok: false, total: rows.length, verified: 0, reason: 'chain_incomplete', history_count: histCount.n };
133
135
  }
@@ -143,7 +145,7 @@ export function verifyOrderChain(db, orderId) {
143
145
  return { ok: false, total: rows.length, verified: r.seq, firstBrokenSeq: r.seq, reason: 'event_hash_mismatch' };
144
146
  }
145
147
  // 3. signature 用 actor api_key 验
146
- const actor = db.prepare('SELECT api_key FROM users WHERE id = ?').get(r.actor_id);
148
+ const actor = await dbOne('SELECT api_key FROM users WHERE id = ?', [r.actor_id]);
147
149
  if (!actor)
148
150
  return { ok: false, total: rows.length, verified: r.seq, firstBrokenSeq: r.seq, reason: 'actor_not_found' };
149
151
  const reSig = computeEventSignature(r.payload_json, actor.api_key);
@@ -154,10 +156,10 @@ export function verifyOrderChain(db, orderId) {
154
156
  }
155
157
  return { ok: true, total: rows.length, verified: rows.length };
156
158
  }
157
- export function getOrderChain(db, orderId) {
158
- const rows = db.prepare(`SELECT seq, event_type, from_status, to_status, actor_id, actor_role, signed_at,
159
+ export async function getOrderChain(_db, orderId) {
160
+ const rows = await dbAll(`SELECT seq, event_type, from_status, to_status, actor_id, actor_role, signed_at,
159
161
  event_hash, prev_event_hash, signature, payload_json
160
- FROM order_events WHERE order_id = ? ORDER BY seq ASC`).all(orderId);
162
+ FROM order_events WHERE order_id = ? ORDER BY seq ASC`, [orderId]);
161
163
  return rows.map(r => ({
162
164
  seq: r.seq,
163
165
  event_type: r.event_type,
@@ -269,7 +269,7 @@ export const ORDER_STATE_MEANINGS = {
269
269
  fault_buyer: { zh: '买家违约(超时未付,终态)', en: 'buyer fault (payment timeout, terminal)' },
270
270
  fault_seller: { zh: '卖家违约(超时未接/发 或 主动拒单)', en: 'seller fault (accept/ship timeout or active decline)' },
271
271
  fault_logistics: { zh: '物流违约', en: 'logistics fault' },
272
- declined_nofault: { zh: '卖家无责拒单(仲裁认定客观)→ 全退买家+退卖家质押,零罚没(终态)', en: 'seller no-fault decline (arbitration-cleared) → full refund + stake returned, no forfeit (terminal)' },
272
+ declined_nofault: { zh: '卖家无责拒单裁定(仲裁认定客观),待系统结算 completed;全退买家+退卖家质押,零罚没', en: 'seller no-fault decline (arbitration-cleared), pending system settlement completed; full refund + stake returned, no forfeit' },
273
273
  resolved_for_seller: { zh: '仲裁裁卖家胜诉,资金释放(终态)', en: 'arbitration ruled for seller, funds released (terminal)' },
274
274
  refunded_partial: { zh: '仲裁裁部分退款(终态)', en: 'arbitration partial refund (terminal)' },
275
275
  refunded_full: { zh: '仲裁裁全额退款,订单作废(终态)', en: 'arbitration full refund, order voided (terminal)' },
@@ -10,6 +10,7 @@
10
10
  * 2. HTTP GET:<server>/api/manifest
11
11
  * 3. webaz_info 工具返回值中的 manifest 字段
12
12
  */
13
+ import { dbOne } from '../L0-1-database/db.js'; // RFC-016 异步 seam
13
14
  export const MANIFEST_VERSION = '0.1.0';
14
15
  export const MANIFEST_URI = 'webaz://protocol/manifest';
15
16
  // ─── 协议常量(与状态机保持同步)────────────────────────────
@@ -256,9 +257,9 @@ const AGENT_GUIDE = {
256
257
  },
257
258
  };
258
259
  // ─── 生成 Manifest ────────────────────────────────────────────
259
- export function generateManifest(db) {
260
- // 如果传入 db,可以附加实时统计数据
261
- const stats = db ? getLiveStats(db) : null;
260
+ export async function generateManifest(db) {
261
+ // 如果传入 db(=要求附加实时统计),走异步 seam 读取。db 仅作"是否含 live_stats"开关,实际连接来自 setSeamDb。
262
+ const stats = db ? await getLiveStats() : null;
262
263
  return {
263
264
  $schema: 'https://dcp-protocol.io/schema/manifest/v1',
264
265
  $uri: MANIFEST_URI,
@@ -288,15 +289,16 @@ export function generateManifest(db) {
288
289
  live_stats: stats,
289
290
  };
290
291
  }
291
- function getLiveStats(db) {
292
+ async function getLiveStats() {
292
293
  try {
293
- const users = db.prepare('SELECT COUNT(*) as n FROM users WHERE role != ?').get('system').n;
294
- const products = db.prepare("SELECT COUNT(*) as n FROM products WHERE status = 'active'").get().n;
295
- const orders = db.prepare('SELECT COUNT(*) as n FROM orders').get().n;
296
- const completed = db.prepare("SELECT COUNT(*) as n FROM orders WHERE status = 'completed'").get().n;
297
- const disputes = db.prepare('SELECT COUNT(*) as n FROM disputes').get().n;
298
- const skills = db.prepare("SELECT COUNT(*) as n FROM skills WHERE active = 1").get().n;
299
- const totalVolume = db.prepare("SELECT COALESCE(SUM(total_amount),0) as v FROM orders WHERE status = 'completed'").get().v;
294
+ const n = async (sql, params = []) => ((await dbOne(sql, params))?.n ?? 0);
295
+ const users = await n('SELECT COUNT(*) as n FROM users WHERE role != ?', ['system']);
296
+ const products = await n("SELECT COUNT(*) as n FROM products WHERE status = 'active'");
297
+ const orders = await n('SELECT COUNT(*) as n FROM orders');
298
+ const completed = await n("SELECT COUNT(*) as n FROM orders WHERE status = 'completed'");
299
+ const disputes = await n('SELECT COUNT(*) as n FROM disputes');
300
+ const skills = await n("SELECT COUNT(*) as n FROM skills WHERE active = 1");
301
+ const totalVolume = ((await dbOne("SELECT COALESCE(SUM(total_amount),0) as v FROM orders WHERE status = 'completed'"))?.v ?? 0);
300
302
  return { users, active_products: products, total_orders: orders, completed_orders: completed, total_disputes: disputes, active_skills: skills, total_volume_dcp: totalVolume };
301
303
  }
302
304
  catch {
@@ -22,6 +22,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
22
22
  import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, // #B.1 a — MCP 三大原语之 Prompts
23
23
  GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
24
24
  import { initDatabase, generateId } from '../../layer0-foundation/L0-1-database/schema.js';
25
+ import { setSeamDb } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam(本进程注入)
25
26
  import { transition, getOrderStatus, initSystemUser, } from '../../layer0-foundation/L0-2-state-machine/engine.js';
26
27
  import { initDisputeSchema, createDispute, respondToDispute, getDisputeDetails, getOrderDispute, getOpenDisputes, } from '../../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
27
28
  import { initNotificationSchema, notifyTransition, getNotifications, getUnreadCount, markRead, } from '../../layer2-business/L2-6-notifications/notification-engine.js';
@@ -184,6 +185,7 @@ function modeBanner() {
184
185
  }
185
186
  // ─── 初始化 ──────────────────────────────────────────────────
186
187
  const db = initDatabase();
188
+ setSeamDb(db); // RFC-016 Phase 1:注入异步 DB seam(本进程)—— 共享引擎迁 seam 后 MCP 进程也能用,否则 dbOne/dbAll 抛"未初始化"
187
189
  initSystemUser(db);
188
190
  initDisputeSchema(db);
189
191
  initNotificationSchema(db);
@@ -323,7 +325,7 @@ No auth required, no parameters needed.
323
325
 
324
326
  Roles: buyer (browse/order/confirm) | seller (list/accept/ship) | logistics (pickup/transit/deliver) | reviewer (reviews) | arbitrator (disputes/rulings).
325
327
 
326
- ⚠️ **MCP register limitations (anti-bot, by design)**: does NOT set placement_id/sponsor_id — referral/PV chain NOT built via MCP. To build chain: user must arrive via webaz_share_link \`?ref=<uid>\` URL clicked in browser (PWA flow). region defaults 'global'; valid: singapore/china/usa/malaysia/indonesia/thailand/vietnam/taiwan/hk/global.`,
328
+ ⚠️ **MCP register limitations (anti-bot, by design)**: does NOT set placement_id/sponsor_id — referral/PV chain NOT built via MCP. To build chain: user must arrive via webaz_share_link \`/i/<permanent_code>\` (or \`?ref=<permanent_code>\`) URL clicked in browser (PWA flow). region defaults 'global'; valid: singapore/china/usa/malaysia/indonesia/thailand/vietnam/taiwan/hk/global.`,
327
329
  inputSchema: {
328
330
  type: 'object',
329
331
  properties: {
@@ -864,7 +866,7 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
864
866
  name: 'webaz_referral',
865
867
  description: `View your referral status: 3-tier commission team + earnings + invite links + points-matching tier progress + L1 share permission gate + **rewards_status (RFC-002 §3.5 opt-in state + pending escrow)**.
866
868
 
867
- ⚠️ **Opt-in required (RFC-002)**: rewards default = off. \`rewards_status\` field returns 4-state {opted_in | never_activated | auto_downgraded | deactivated} + pending_escrow tally. Opted-out users still see attribution + tree, but commission held in escrow until activation via PWA #me.
869
+ ⚠️ **Opt-in required (RFC-002)**: rewards default = off. \`rewards_status\` field returns 4-state {opted_in | never_activated | auto_downgraded | deactivated} + pending_escrow tally. Opted-out users still see attribution + tree. Commission destination differs by state: **never_activated / auto_downgraded** → held in pending_commission_escrow, recoverable by (re-)activating within the window via PWA #me; **deactivated** (active opt-out) → future L1/L2/L3 commission goes to commission_reserve / protocol reserve, NOT escrow and NOT recoverable (re-applying only affects future commission).
868
870
 
869
871
  ⚠️ **Consent required**: generating referral links / promoting on a human user's behalf needs the user's explicit authorization. Agent **MUST get explicit consent** before generating referral links / promoting. Do NOT auto-recruit.`,
870
872
  inputSchema: {
@@ -879,7 +881,7 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
879
881
  name: 'webaz_share_link',
880
882
  description: `Generate product share link with your referral attached. Open in any social platform (TikTok/WeChat/Telegram). Clicker registers/buys → counts toward your 3-tier commission (if verified buyer) + points-matching.
881
883
 
882
- ⚠️ **Opt-in required (RFC-002 §3.5)**: this is a valuation-layer action. Caller must have \`rewards_opted_in=1\` (builder-identity opt-in). Opted-out users get \`{error: 'rewards_opt_in_required', missing_requirements, next_steps}\` — direct user to PWA #me to apply.
884
+ ⚠️ **Opt-in required (RFC-002 §3.5)**: this is a valuation-layer (rewards) action, not a contribution gate. Caller must have \`rewards_opted_in=1\` (rewards / share-commission opt-in). Opted-out users get \`{error: 'rewards_opt_in_required', missing_requirements, next_steps}\` — direct user to PWA #me to apply.
883
885
 
884
886
  ⚠️ **Consent required**: this builds a referral chain on the user's behalf. Agent acting for a human user **MUST get explicit consent**. Do NOT auto-generate. See webaz_info.commission_model.`,
885
887
  inputSchema: {
@@ -1426,28 +1428,44 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
1426
1428
  },
1427
1429
  {
1428
1430
  name: 'webaz_contribute',
1429
- description: `Coordinate building WebAZ itself (RFC-006) — a claim board so contributors don't collide. Check BEFORE starting work on an area. Day-to-day small changes; large ones go via RFC. 协调"谁在做什么"防撞车.
1431
+ description: `Coordinate building WebAZ itself (RFC-006 / RFC-017) — a public task board so contributors don't collide. Check BEFORE starting work on an area. 协调"谁在做什么"防撞车.
1432
+
1433
+ Discovery + suggesting need NO api_key (anyone / any agent can browse and propose). Claiming + submitting need an api_key (a real, accountable identity).
1430
1434
 
1431
1435
  Actions:
1432
- - list_open (default): open tasks (opt. area filter)
1433
- - claim: take an open task; provenance=human|ai_assisted|ai_authored (self-declared, not detected); auto-expires ~7d if not submitted. Returns a **handoff** (repo + AGENTS.md + PR flow) point a coding agent at it to actually do the work; the human needn't know git.
1434
- - submit: mark in_review with pr_ref
1435
- - status: tasks you hold (claimed/in_review)
1436
- - profile: your build dashboard KPI/tier/restrictions+appeal, self-view (private, no public leaderboard)
1436
+ - list_open (default): open public tasks (opt. filters: area / risk_level / auto_claimable / required_capabilities / agent_capabilities / max_duration_minutes / estimated_context_size / estimated_agent_budget — estimated_agent_budget is a resource/effort estimate, NOT a payment). Each task carries its execution boundary + the trusted canonical contribution target. NO api_key needed.
1437
+ - detail: one task's full execution boundary (allowed/forbidden paths, prohibited actions, acceptance criteria, verification commands, deliverables, definition_of_done) + the canonical repo to PR to + a copy-ready agent_handoff. NO api_key needed.
1438
+ - suggest: propose a NEW task (title + summary/reason; opt. area/expected_outcome/source_ref/github_login). It enters the maintainer inbox — it is a suggestion, NOT a contribution fact / reward / participation, and never auto-becomes a task. NO api_key needed.
1439
+ - claim: take an open task (api_key); provenance=human|ai_assisted|ai_authored (self-declared, not detected); auto-expires ~7d if not submitted. Returns a handoff — point a coding agent at it; the human needn't know git but stays accountable (Passkey).
1440
+ - submit: mark in_review with pr_ref + verification_summary (api_key). The PR's base repo MUST be the canonical WebAZ repo, and a verification_summary (what you ran/verified) is REQUIRED — both server-enforced. A human maintainer reviews next; done ≠ merge.
1441
+ - status: tasks you hold (api_key).
1442
+ - profile: your build dashboard — KPI/tier/restrictions+appeal, private self-view (api_key).
1437
1443
 
1438
- Coordinates + records only — NO merge/reward; acceptance (done) = human maintainer. build_reputation is a SEPARATE pool, never gates verifier/arbitrator. Login required; NETWORK only.`,
1444
+ Coordinates + records only — NO merge/reward; acceptance (done) = human maintainer. Contribution value is uncommitted (RFC-017 I-12). build_reputation is a SEPARATE pool, never gates verifier/arbitrator. NETWORK only (contribution is a real-network act; sandbox has nothing to coordinate with).`,
1439
1445
  inputSchema: {
1440
1446
  type: 'object',
1441
1447
  properties: {
1442
- action: { type: 'string', enum: ['list_open', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | claim | submit | status | profile' },
1443
- api_key: { type: 'string', description: "User's api_key (accountable identity)" },
1444
- task_id: { type: 'string', description: 'claim / submit: the task id' },
1445
- area: { type: 'string', description: 'list_open: optional area filter (e.g. search / docs / mcp)' },
1448
+ action: { type: 'string', enum: ['list_open', 'detail', 'suggest', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | detail | suggest | claim | submit | status | profile' },
1449
+ api_key: { type: 'string', description: 'claim/submit/status/profile: your api_key (accountable identity). NOT needed for list_open/detail/suggest.' },
1450
+ task_id: { type: 'string', description: 'detail / claim / submit: the task id' },
1451
+ area: { type: 'string', description: 'list_open: area filter / suggest: suggested area (e.g. search / docs / mcp)' },
1452
+ risk_level: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'list_open: optional risk filter' },
1453
+ auto_claimable: { type: 'boolean', description: 'list_open: optional filter — only auto-claimable (true) or manual-claim (false) tasks' },
1454
+ required_capabilities: { type: 'string', description: 'list_open: optional filter — comma-separated; matches tasks that REQUIRE ALL of the listed capabilities (superset/AND match on the task requirement). For "tasks my agent can do", use agent_capabilities instead.' },
1455
+ agent_capabilities: { type: 'string', description: 'list_open: optional filter — capabilities your agent HAS (comma-separated); matches tasks whose required_capabilities are a SUBSET of these, i.e. tasks your agent can actually do' },
1456
+ max_duration_minutes: { type: 'number', description: 'list_open: optional filter — only tasks whose estimated max duration fits within this many minutes (your idle time)' },
1457
+ estimated_context_size: { type: 'string', enum: ['small', 'medium', 'large'], description: 'list_open: optional filter — task estimated context size' },
1458
+ estimated_agent_budget: { type: 'string', enum: ['minimal', 'small', 'moderate', 'large', 'xlarge'], description: 'list_open: optional filter — task estimated agent budget (resource/effort estimate, not a payment)' },
1446
1459
  provenance: { type: 'string', enum: ['human', 'ai_assisted', 'ai_authored'], description: 'claim: self-declared authorship (default human)' },
1447
- pr_ref: { type: 'string', description: 'submit: your PR link or number' },
1460
+ pr_ref: { type: 'string', description: 'submit: your PR link or number (must target the canonical repo)' },
1461
+ verification_summary: { type: 'string', description: 'submit (REQUIRED): summarize what you ran/verified — the task verification_commands you ran and their results' },
1448
1462
  note: { type: 'string', description: 'submit: optional note' },
1463
+ title: { type: 'string', description: 'suggest: task title (≥3 chars)' },
1464
+ summary: { type: 'string', description: 'suggest: why it is worth doing / what it solves (the reason)' },
1465
+ expected_outcome: { type: 'string', description: 'suggest: optional — what should be true when done' },
1466
+ source_ref: { type: 'string', description: 'suggest: optional reference link (reference only; does NOT set the target repo)' },
1467
+ proposer_github_login: { type: 'string', description: 'suggest: optional — your GitHub login' },
1449
1468
  },
1450
- required: ['api_key'],
1451
1469
  },
1452
1470
  },
1453
1471
  ];
@@ -1489,19 +1507,100 @@ async function handleFeedback(args) {
1489
1507
  },
1490
1508
  });
1491
1509
  }
1492
- // RFC-006 Gap 1: webaz_contribute 协调"谁在做什么"(双模;仅 NETWORK)
1493
- async function handleContribute(args) {
1510
+ // RFC-006 断点1(b)交接:从【可信】canonical 目标(API 响应里,绝不硬编码/不取自 task metadata)构造"怎么真正
1511
+ // 动手"。人的编码 agent 做 git/PR;Passkey 真人担责。sandbox 运行 / 本地草稿不算正式参与。
1512
+ function buildContributeHandoff(cct, taskId) {
1513
+ const c = (cct ?? {});
1514
+ const repoUrl = c.canonical_github_url || 'https://github.com/seasonsagents-art/webaz';
1515
+ const baseRepo = c.expected_pr_base_repo || c.canonical_repository_full_name || 'seasonsagents-art/webaz';
1516
+ const baseBranch = c.base_branch || 'main';
1517
+ return {
1518
+ canonical_repo: baseRepo,
1519
+ repo: repoUrl,
1520
+ base_branch: baseBranch,
1521
+ start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
1522
+ do_the_work: 'Point a coding agent (e.g. Claude Code) at the repo on a single-topic branch. The buyer/shopping agent is not the coding agent — hand off to one.',
1523
+ submit_pr: `Open a PR whose BASE repo is ${baseRepo} (${repoUrl}), base branch ${baseBranch}. If any target repo differs from this canonical repo, STOP and ask the human — never contribute to a non-canonical repository.`,
1524
+ pr_flow: 'Commit with DCO sign-off (git commit -s). If AI-authored, mark the PR per the meta-rule. Humans merge — no auto-merge.',
1525
+ then: `When the PR is open, report it back: webaz_contribute action=submit task_id=${taskId} pr_ref=#<N> verification_summary="<the verification_commands you ran + their results>". Both pr_ref and verification_summary are required.`,
1526
+ not_participation: 'A sandbox run or a local-only draft is NOT participation and is NOT a contribution; only a merged PR (or recognized issue/task/RFC) on the canonical repo enters the contribution record.',
1527
+ human_note: "You don't need to know git — your coding agent does it; you (the Passkey-bound human) stay accountable.",
1528
+ };
1529
+ }
1530
+ // RFC-006/RFC-017: webaz_contribute — 协调"谁在做什么"。NETWORK only. Discovery + suggest 无需 key(打公开
1531
+ // 端口 #329/#331);claim/submit/status/profile 需 key(真实可问责身份,走受 #330 守卫的 member 端口)。
1532
+ export async function handleContribute(args) {
1494
1533
  const action = args.action || 'list_open';
1495
1534
  const apiKey = args.api_key;
1496
- if (!apiKey)
1497
- return { error: 'api_key required' };
1498
1535
  if (toolBackend('webaz_contribute') !== 'network') {
1499
1536
  return {
1500
1537
  _mode: 'sandbox',
1501
- error: 'SANDBOX 模式无协调对象 —— 协调要在真实项目上才有意义。请设 WEBAZ_API_KEY 切到 NETWORK 模式。 / Coordination needs NETWORK mode; set WEBAZ_API_KEY.',
1538
+ error: 'SANDBOX 模式无协调对象 —— 协调要在真实项目上才有意义。请设 WEBAZ_API_KEY 切到 NETWORK 模式(或不设 key 默认 network_readonly 也可浏览/建议)。 / Coordination needs the live network; sandbox has nothing to coordinate with.',
1502
1539
  error_code: 'CONTRIBUTE_NEEDS_NETWORK',
1503
1540
  };
1504
1541
  }
1542
+ // ── keyless discovery + suggest (public surface; same trusted canonical target as the PWA) ──
1543
+ if (action === 'list_open') {
1544
+ // public endpoint already restricts to audience=public + status=open; only pass the optional filters.
1545
+ const qs = new URLSearchParams();
1546
+ if (args.area)
1547
+ qs.set('area', String(args.area));
1548
+ if (args.risk_level)
1549
+ qs.set('risk_level', String(args.risk_level));
1550
+ if (args.auto_claimable !== undefined)
1551
+ qs.set('auto_claimable', String(Boolean(args.auto_claimable)));
1552
+ if (args.required_capabilities)
1553
+ qs.set('required_capabilities', String(args.required_capabilities));
1554
+ if (args.agent_capabilities !== undefined)
1555
+ qs.set('agent_capabilities', String(args.agent_capabilities)); // forward even '' so the route fail-closes (typed 400), never silently returns the full list
1556
+ if (args.max_duration_minutes !== undefined)
1557
+ qs.set('max_duration_minutes', String(args.max_duration_minutes));
1558
+ if (args.estimated_context_size)
1559
+ qs.set('estimated_context_size', String(args.estimated_context_size));
1560
+ if (args.estimated_agent_budget)
1561
+ qs.set('estimated_agent_budget', String(args.estimated_agent_budget));
1562
+ const q = qs.toString();
1563
+ const r = await apiCall('/api/public/build-tasks' + (q ? '?' + q : ''));
1564
+ if (!r.error)
1565
+ r._next = 'Pick a task, then: webaz_contribute action=detail task_id=<id> for its full execution boundary + the canonical repo to PR to; then action=claim task_id=<id> api_key=<key> to take it (claiming needs an account).';
1566
+ return r;
1567
+ }
1568
+ if (action === 'detail') {
1569
+ const tid = args.task_id;
1570
+ if (!tid)
1571
+ return { error: 'task_id required for action=detail' };
1572
+ const r = await apiCall('/api/public/build-tasks/' + encodeURIComponent(tid));
1573
+ if (!r.error && r.task)
1574
+ r.agent_handoff = buildContributeHandoff(r.canonical_contribution_target, tid);
1575
+ return r;
1576
+ }
1577
+ if (action === 'suggest') {
1578
+ const title = (args.title ?? '').trim();
1579
+ const summary = (args.summary ?? args.note ?? '').trim();
1580
+ if (title.length < 3)
1581
+ return { error: 'title required (≥3 chars) for action=suggest' };
1582
+ if (summary.length < 1)
1583
+ return { error: 'summary (the reason) required for action=suggest' };
1584
+ const r = await apiCall('/api/public/task-proposals', {
1585
+ method: 'POST',
1586
+ body: {
1587
+ title, summary,
1588
+ suggested_area: args.area ?? args.suggested_area,
1589
+ expected_outcome: args.expected_outcome,
1590
+ source_ref: args.source_ref,
1591
+ proposer_github_login: args.proposer_github_login,
1592
+ },
1593
+ });
1594
+ // typed errors (RATE_LIMITED / DUPLICATE_PROPOSAL / validation) are already mapped by apiCall; the
1595
+ // success response already carries the route-level `proposal_notice` (suggestion ≠ contribution/reward).
1596
+ return r;
1597
+ }
1598
+ // ── participation: a real, accountable identity is required ──
1599
+ if (!apiKey)
1600
+ return {
1601
+ error: `api_key required for action=${action} — set WEBAZ_API_KEY (request an invite at ${WEBAZ_API_URL}/#welcome). Discovery (list_open / detail) and suggest work WITHOUT a key.`,
1602
+ error_code: 'API_KEY_REQUIRED',
1603
+ };
1505
1604
  if (action === 'status')
1506
1605
  return apiCall('/api/build-tasks?mine=1', { apiKey });
1507
1606
  if (action === 'profile')
@@ -1513,31 +1612,25 @@ async function handleContribute(args) {
1513
1612
  const r = await apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/claim', {
1514
1613
  method: 'POST', apiKey, body: { provenance: args.provenance },
1515
1614
  });
1516
- // RFC-006 断点1(b)交接:认领成功后直接下发"怎么真正动手",让贡献者的【编码 agent】接手 git/PR。
1517
- // 关键:人不必会 git——人的编码 agent( Claude Code)做;人(Passkey 真人)担责。
1518
- if (!r.error) {
1519
- r.handoff = {
1520
- repo: 'https://github.com/seasonsagents-art/webaz',
1521
- start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
1522
- do_the_work: 'Point a coding agent (e.g. Claude Code) at the repo; work on a single-topic branch. The buyer/shopping agent is not the coding agent — hand off to one.',
1523
- pr_flow: 'Commit with DCO sign-off (git commit -s). If AI-authored, add 🤖🤖🤖 to the PR title + a meta-rule trace. Humans merge — no auto-merge.',
1524
- then: `When the PR is open, report it back: webaz_contribute action=submit task_id=${tid} pr_ref=#<N>.`,
1525
- human_note: "You don't need to know git — your coding agent does it; you (the Passkey-bound human) stay accountable.",
1526
- };
1527
- }
1615
+ if (!r.error)
1616
+ r.handoff = buildContributeHandoff(r.canonical_contribution_target, tid);
1528
1617
  return r;
1529
1618
  }
1530
1619
  if (action === 'submit') {
1531
1620
  const tid = args.task_id;
1532
1621
  if (!tid)
1533
1622
  return { error: 'task_id required for action=submit' };
1534
- return apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/submit', {
1535
- method: 'POST', apiKey, body: { pr_ref: args.pr_ref, note: args.note },
1623
+ const vs = (args.verification_summary ?? '').trim();
1624
+ if (vs.length < 1)
1625
+ return { error: 'verification_summary required for action=submit — summarize what you ran/verified (the task verification_commands and their results)', error_code: 'VERIFICATION_SUMMARY_REQUIRED' };
1626
+ const r = await apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/submit', {
1627
+ method: 'POST', apiKey, body: { pr_ref: args.pr_ref, note: args.note, verification_summary: vs },
1536
1628
  });
1629
+ if (!r.error)
1630
+ r._next = 'A human maintainer reviews next — acceptance (done) is manual and done ≠ merge. Track it with webaz_contribute action=status.';
1631
+ return r;
1537
1632
  }
1538
- // list_open(默认)
1539
- const q = args.area ? '?status=open&area=' + encodeURIComponent(String(args.area)) : '?status=open';
1540
- return apiCall('/api/build-tasks' + q, { apiKey });
1633
+ return { error: 'unknown action: ' + action };
1541
1634
  }
1542
1635
  async function handleInfo() {
1543
1636
  const summary = getManifestSummary();
@@ -1641,10 +1734,10 @@ async function handleInfo() {
1641
1734
  split: '7:2:1 — L1 70% / L2 20% / L3 10% of an order\'s commission_pool',
1642
1735
  jurisdiction_tiers: 'Tiers are graded by the order region\'s max_levels — NOT a uniform 3 tiers everywhere. e.g. global region max_levels=1 → L1 only; singapore (etc.) max_levels=3 → up to L3. A region may also be 0 (no commission tiers; pool → community fund).',
1643
1736
  attribution: 'EXPLICIT per-order — commission goes to the promoter attributed at purchase time, not derived from the buyer\'s sponsor chain.',
1644
- how_to_attribute: 'L1: webaz_place_order(promoter_api_key) records the direct promoter. Full L2/L3 chain requires the buyer to arrive via a webaz_share_link ?ref= URL clicked in a browser (builds product_share_attribution).',
1737
+ how_to_attribute: 'L1: webaz_place_order(promoter_api_key) records the direct promoter. Full L2/L3 chain requires the buyer to arrive via a webaz_share_link /i/<permanent_code> (?ref=<permanent_code>) URL clicked in a browser (builds product_share_attribution).',
1645
1738
  redirect_rules: 'chain_gap (no L / invalid sponsor) → charity_fund; level beyond the region cap → global_fund.',
1646
1739
  l1_gate: 'the promoter must be a verified buyer (≥1 completed order) to receive commission, otherwise that share redirects.',
1647
- opt_in: 'Participation is opt-in (RFC-002): default = off. A user applies (Passkey + ≥1 completed order); attribution is always recorded, but commission settlement is gated until opt-in (pending held in pending_commission_escrow, 30d grace). See docs/rfcs/RFC-002-rewards-opt-in.md.',
1740
+ opt_in: 'Participation is opt-in (RFC-002): default = off. A user applies (Passkey + ≥1 completed order); attribution is always recorded. Commission destination is state-dependent: never_activated / auto_downgraded held in pending_commission_escrow (30d grace), recoverable by (re-)activating within the window, else swept to commission_reserve; deactivated (active opt-out) → future commission goes directly to commission_reserve, NOT escrow and NOT recoverable. Never to charity_fund. See docs/rfcs/RFC-002-rewards-opt-in.md.',
1648
1741
  },
1649
1742
  // QA 轮 3 FAIL:roles 漏 reviewer。register 工具支持 5 个角色,info 必须列全。
1650
1743
  roles: {
@@ -1957,8 +2050,8 @@ async function handleSearch(args) {
1957
2050
  : '没有找到匹配的商品';
1958
2051
  return { found: 0, message: hint, products: [], matched_by: 'strict_no_match' };
1959
2052
  }
1960
- const enriched = products.map((p) => {
1961
- const boost = getSearchBoost(db, p.seller_id);
2053
+ const enriched = await Promise.all(products.map(async (p) => {
2054
+ const boost = await getSearchBoost(db, p.seller_id);
1962
2055
  const rep_level = p.rep_level || 'new';
1963
2056
  const rep_points = Number(p.rep_points) || 0;
1964
2057
  const completion = Number(p.completion_count) || 0;
@@ -1983,7 +2076,7 @@ async function handleSearch(args) {
1983
2076
  const firstSaleBoost = firstSold && (Date.now() - new Date(firstSold.replace(' ', 'T') + 'Z').getTime()) < 14 * 86400_000 ? 5 : 0;
1984
2077
  const score = completion * 0.5 + rep_points * 0.1 + sharer * 2.0 + freshness + firstSaleBoost - dispute * 5.0;
1985
2078
  return { ...p, _boost: boost, _rep_level: rep_level, _rep_points: rep_points, _score: score, _freshness: freshness, _first_sale_boost: firstSaleBoost };
1986
- });
2079
+ }));
1987
2080
  const sorted = sortMode === 'trending'
1988
2081
  ? enriched.sort((a, b) => b._score - a._score || b._boost - a._boost).slice(0, limit)
1989
2082
  : enriched.slice(0, limit);
@@ -2212,7 +2305,7 @@ async function handleListProduct(args) {
2212
2305
  // stake 在 place_order 那一刻按"该订单总额 × stake_rate"现锁,订单结算时退该笔,违约时扣该笔。
2213
2306
  // 这样每个 active 订单都有独立 stake 担保,多 stock 商品也不会被空头薅。
2214
2307
  // product.stake_amount 字段保留为"indicative rate × price"(前端展示用),不强制 lock。
2215
- const stakeDiscount = getStakeDiscount(db, user.id);
2308
+ const stakeDiscount = await getStakeDiscount(db, user.id);
2216
2309
  const stakeRate = Math.max(0.05, 0.15 - stakeDiscount); // 最低 5%,声誉越高折扣越大
2217
2310
  const stakeAmount = Math.round(price * stakeRate * 100) / 100; // indicative only; actual lock per-order
2218
2311
  const id = generateId('prd');
@@ -2694,8 +2787,8 @@ async function handleNotifications(args) {
2694
2787
  return auth;
2695
2788
  const { user } = auth;
2696
2789
  const onlyUnread = args.unread === true;
2697
- const notifs = getNotifications(db, user.id, onlyUnread, 30);
2698
- const unread = getUnreadCount(db, user.id);
2790
+ const notifs = await getNotifications(db, user.id, onlyUnread, 30);
2791
+ const unread = await getUnreadCount(db, user.id);
2699
2792
  if (args.mark_read) {
2700
2793
  markRead(db, user.id);
2701
2794
  }
@@ -2782,9 +2875,9 @@ async function handleDispute(args) {
2782
2875
  // ── 查看争议详情 ────────────────────────────────────────────
2783
2876
  if (action === 'view') {
2784
2877
  let dispute = args.dispute_id
2785
- ? getDisputeDetails(db, args.dispute_id)
2878
+ ? await getDisputeDetails(db, args.dispute_id)
2786
2879
  : args.order_id
2787
- ? getOrderDispute(db, args.order_id)
2880
+ ? await getOrderDispute(db, args.order_id)
2788
2881
  : null;
2789
2882
  if (!dispute)
2790
2883
  return { error: '找不到争议记录,请提供 dispute_id 或 order_id' };
@@ -2824,7 +2917,7 @@ async function handleDispute(args) {
2824
2917
  if (user.role !== 'arbitrator') {
2825
2918
  return { error: '只有仲裁员可以查看所有待处理争议' };
2826
2919
  }
2827
- const disputes = getOpenDisputes(db);
2920
+ const disputes = await getOpenDisputes(db);
2828
2921
  return {
2829
2922
  open_count: disputes.length,
2830
2923
  disputes: disputes.map(d => ({
@@ -2848,7 +2941,7 @@ async function handleDispute(args) {
2848
2941
  // 如有证据描述,先创建证据记录
2849
2942
  const evidenceIds = [];
2850
2943
  if (args.evidence_description) {
2851
- const dispute = getDisputeDetails(db, args.dispute_id);
2944
+ const dispute = await getDisputeDetails(db, args.dispute_id);
2852
2945
  if (dispute) {
2853
2946
  const eid = generateId('evt');
2854
2947
  db.prepare(`
@@ -3006,7 +3099,7 @@ async function handleSkill(args) {
3006
3099
  if (!('error' in a))
3007
3100
  userId = a.user.id;
3008
3101
  }
3009
- const skills = listSkills(db, {
3102
+ const skills = await listSkills(db, {
3010
3103
  skillType: args.skill_type,
3011
3104
  query: args.query,
3012
3105
  subscriberId: userId,
@@ -3064,7 +3157,7 @@ async function handleSkill(args) {
3064
3157
  }
3065
3158
  // ── 我发布的 Skill ────────────────────────────────────────
3066
3159
  if (action === 'my_skills') {
3067
- const skills = getMySkills(db, user.id);
3160
+ const skills = await getMySkills(db, user.id);
3068
3161
  return {
3069
3162
  total: skills.length,
3070
3163
  skills: skills.map(formatSkillForAgent),
@@ -3073,7 +3166,7 @@ async function handleSkill(args) {
3073
3166
  }
3074
3167
  // ── 我订阅的 Skill ────────────────────────────────────────
3075
3168
  if (action === 'my_subs') {
3076
- const skills = getMySubscriptions(db, user.id);
3169
+ const skills = await getMySubscriptions(db, user.id);
3077
3170
  return {
3078
3171
  total: skills.length,
3079
3172
  subscriptions: skills.map(formatSkillForAgent),
@@ -3373,10 +3466,14 @@ async function handleReferral(args) {
3373
3466
  const tiers = db.prepare("SELECT tier, pv_threshold, score_per_hit FROM binary_tier_config WHERE active=1 ORDER BY tier ASC").all();
3374
3467
  const pair = Math.min(Number(me?.total_left_pv ?? 0), Number(me?.total_right_pv ?? 0));
3375
3468
  const nextTier = tiers.find(t => t.pv_threshold > pair);
3469
+ // invite / share links use permanent_code ONLY — never usr_xxx. (sandbox users have one from register.)
3470
+ const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
3376
3471
  return {
3377
3472
  user_id: userId,
3378
3473
  name: user.name,
3379
- base_referral_link: `/?ref=${userId}`, // 仅推土机
3474
+ invite_code: permaCode,
3475
+ invite_unavailable_reason: permaCode ? null : 'permanent_code_missing — re-register or contact support',
3476
+ base_referral_link: permaCode ? `/i/${permaCode}` : null, // 仅推土机
3380
3477
  region: user.region ?? 'global',
3381
3478
  permissions: {
3382
3479
  can_earn_l1_commission: canL1,
@@ -3391,10 +3488,10 @@ async function handleReferral(args) {
3391
3488
  grand_total: byLevel[1].total + byLevel[2].total + byLevel[3].total,
3392
3489
  },
3393
3490
  binary: {
3394
- left_invite_link: `/?ref=${userId}&side=left`,
3395
- right_invite_link: `/?ref=${userId}&side=right`,
3396
- platform_left: `/?placement=${userId}&side=left`, // 仅 PV 条线
3397
- platform_right: `/?placement=${userId}&side=right`,
3491
+ left_invite_link: permaCode ? `/i/${permaCode}-L` : null,
3492
+ right_invite_link: permaCode ? `/i/${permaCode}-R` : null,
3493
+ platform_left: permaCode ? `/?placement=${permaCode}&side=left` : null, // 仅 PV 条线
3494
+ platform_right: permaCode ? `/?placement=${permaCode}&side=right` : null,
3398
3495
  total_left_pv: Number(me?.total_left_pv ?? 0),
3399
3496
  total_right_pv: Number(me?.total_right_pv ?? 0),
3400
3497
  pair_volume: pair,
@@ -3414,7 +3511,7 @@ async function handleReferral(args) {
3414
3511
  }
3415
3512
  else if (lastAction === 'deactivate') {
3416
3513
  state = 'deactivated';
3417
- note = 'You actively deactivated rewards. Future commissions redirect directly to charity_fund (no escrow). You can re-apply via PWA #me.';
3514
+ note = 'You actively deactivated rewards. Future L1/L2/L3 commissions go to commission_reserve / protocol reserve, not charity_fund and not pending escrow. Re-applying only affects future commissions.';
3418
3515
  }
3419
3516
  else if (lastAction === 'auto_downgrade') {
3420
3517
  state = 'auto_downgraded';
@@ -3432,7 +3529,7 @@ async function handleReferral(args) {
3432
3529
  note,
3433
3530
  pending_escrow: { count: pending.n, total_amount: pending.total },
3434
3531
  expired_to_charity: { count: expired.n, total_amount: expired.total },
3435
- spec: 'RFC-002 §3.5 — rewards opt-in / 共建身份申请制',
3532
+ spec: 'RFC-002 §3.5 — rewards / share-commission opt-in (RFC-002)',
3436
3533
  };
3437
3534
  })(),
3438
3535
  tip: canL1
@@ -3480,10 +3577,10 @@ async function handleShareLink(args) {
3480
3577
  missing.push('application_not_submitted');
3481
3578
  return {
3482
3579
  error: 'rewards_opt_in_required',
3483
- message: 'Share-link generation is a valuation-layer action — requires builder-identity opt-in (RFC-002 §3.5)',
3580
+ message: 'Share-link generation is a valuation-layer (rewards / share-link) action, NOT a contribution gate — requires rewards / share-commission opt-in (RFC-002 §3.5)',
3484
3581
  missing_requirements: missing,
3485
3582
  next_steps: [
3486
- 'Open PWA #me → tap "申请共建身份 / Apply for builder identity"',
3583
+ 'Open PWA #me → tap "申请分享分润 / Enable share-commission opt-in"',
3487
3584
  'Read the 8-second disclosure (cannot skip)',
3488
3585
  'Submit application — pre-checks run server-side',
3489
3586
  ],
@@ -3522,7 +3619,11 @@ async function handleShareLink(args) {
3522
3619
  const override = db.prepare("SELECT l1_share_override FROM users WHERE id = ?").get(userId)?.l1_share_override ?? 0;
3523
3620
  const canL1 = override === 1 || (override === 0 && completed > 0);
3524
3621
  const rate = Number(product.commission_rate ?? 0);
3525
- const link = `/?ref=${userId}&side=${side}#order-product/${productId}`;
3622
+ // share ref uses permanent_code ONLY — never usr_xxx
3623
+ const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
3624
+ if (!permaCode)
3625
+ return { error: 'permanent_code_missing — cannot build a share link; re-register or contact support', error_code: 'PERMANENT_CODE_MISSING' };
3626
+ const link = `/?ref=${permaCode}&side=${side}#order-product/${productId}`;
3526
3627
  return {
3527
3628
  product: { id: product.id, title: product.title, price: product.price, commission_rate: rate },
3528
3629
  share_link: link,
@@ -4706,7 +4807,7 @@ export async function startMCPServer() {
4706
4807
  if (request.params.uri !== MANIFEST_URI) {
4707
4808
  throw new Error(`未知资源:${request.params.uri}`);
4708
4809
  }
4709
- const manifest = generateManifest(db);
4810
+ const manifest = await generateManifest(db);
4710
4811
  return {
4711
4812
  contents: [
4712
4813
  {