@seasonkoh/webaz 0.1.23 → 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 +198 -83
  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
@@ -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';
@@ -43,10 +44,16 @@ const TELEMETRY_ENABLED = (process.env.WEBAZ_TELEMETRY ?? 'off').toLowerCase() =
43
44
  const WEBAZ_API_URL = (process.env.WEBAZ_API_URL ?? 'https://webaz.xyz').replace(/\/+$/, '');
44
45
  const WEBAZ_API_KEY = process.env.WEBAZ_API_KEY ?? '';
45
46
  const WEBAZ_MODE_ENV = (process.env.WEBAZ_MODE ?? '').toLowerCase();
46
- // 模式:显式 WEBAZ_MODE 优先;否则有 api_key → network,无 → sandbox
47
+ // 模式:显式 WEBAZ_MODE 优先;否则有 api_key → network,无 key network_readonly(装完即见真网络)。
48
+ // network_readonly(L1 onboarding,2026-06-08):无 key 默认。公共读匿名打 webaz.xyz(真 catalog/协议),
49
+ // 需身份的写/读返回"设 WEBAZ_API_KEY(到 #welcome 申请邀请)"。离线本地 playground 改为【显式】 WEBAZ_MODE=sandbox。
50
+ // —— 治"装完=空沙盒劝退"的死首体验;route/guard 与 network 同路(见 isNetworkMode),只是无 Bearer + 文案不同。
47
51
  const MODE = WEBAZ_MODE_ENV === 'network' ? 'network'
48
52
  : WEBAZ_MODE_ENV === 'sandbox' ? 'sandbox'
49
- : (WEBAZ_API_KEY ? 'network' : 'sandbox');
53
+ : WEBAZ_MODE_ENV === 'network_readonly' ? 'network_readonly'
54
+ : (WEBAZ_API_KEY ? 'network' : 'network_readonly');
55
+ // network 或 network_readonly 都"走真网络"(后者无 Bearer)。sandbox 才是本地。
56
+ const isNetworkMode = () => MODE === 'network' || MODE === 'network_readonly';
50
57
  // 已迁移到 NETWORK 的工具名。P1/P2 逐个加入;未在集合里的工具仍走 sandbox(本地)。
51
58
  // P1(读工具): 纯公开读,无写无 Passkey,作"MCP 连得上生产网络"的首验证。
52
59
  const NETWORK_TOOLS = new Set([
@@ -103,9 +110,10 @@ function pushRecentCall(c) {
103
110
  if (recentCalls.length > 8)
104
111
  recentCalls.shift(); // 只留最近 8 条
105
112
  }
106
- // 单个工具实际后端:仅当全局 network 且该工具已迁移,才走网络;否则 sandbox。
113
+ // 单个工具实际后端:network network_readonly 下、且该工具已迁移,才走网络;否则 sandbox。
114
+ // readonly 无 Bearer:公共读拿真数据,需身份的端点服务端返 401(诚实)→ 不会静默落本地。
107
115
  function toolBackend(tool) {
108
- return (MODE === 'network' && NETWORK_TOOLS.has(tool)) ? 'network' : 'sandbox';
116
+ return (isNetworkMode() && NETWORK_TOOLS.has(tool)) ? 'network' : 'sandbox';
109
117
  }
110
118
  // 未在 NETWORK_TOOLS 名单、但 NETWORK 模式下仍可本地运行的"自省/引导"工具(非数据操作)。
111
119
  // info = 本地自省(并拉 live 网络状态);register = 引导真人去 webaz.xyz。其余未迁工具一律硬失败。
@@ -166,14 +174,18 @@ async function apiCall(path, opts = {}) {
166
174
  // 启动 banner(stderr)+ status 声明用 —— 让用户/agent 一眼知道现在是真网络还是沙盒
167
175
  function modeBanner() {
168
176
  if (MODE === 'network') {
169
- return `🟢 NETWORK mode — webaz.xyz (${WEBAZ_API_URL}). Migrated tools: ${NETWORK_TOOLS.size}/${TOOLS.length}`
170
- + (NETWORK_TOOLS.size === 0 ? ' ⚠️ network client not active yet (P0 scaffold) — all tools still SANDBOX/local.' : '');
177
+ return `🟢 NETWORK mode — webaz.xyz (${WEBAZ_API_URL}), authenticated. Migrated tools: ${NETWORK_TOOLS.size}/${TOOLS.length}`;
171
178
  }
172
- return `🟡 SANDBOX mode — local-only (~/.webaz/webaz.db), NOT the live network. Data is private to this machine.`
173
- + (WEBAZ_API_KEY ? '' : ' Set WEBAZ_API_KEY (register at webaz.xyz) to join the network.');
179
+ if (MODE === 'network_readonly') {
180
+ return `🟢 NETWORK (read-only) no api_key: public reads (search / leaderboard / price history / browse) hit the LIVE webaz.xyz network. `
181
+ + `To transact (register/order/list/etc.), set WEBAZ_API_KEY — request an invite at ${WEBAZ_API_URL}/#welcome.`;
182
+ }
183
+ return `🟡 SANDBOX mode — local-only (~/.webaz/webaz.db), NOT the live network. Data is private to this machine. `
184
+ + `(Explicit dev/demo mode; unset WEBAZ_MODE to use the live network read-only by default.)`;
174
185
  }
175
186
  // ─── 初始化 ──────────────────────────────────────────────────
176
187
  const db = initDatabase();
188
+ setSeamDb(db); // RFC-016 Phase 1:注入异步 DB seam(本进程)—— 共享引擎迁 seam 后 MCP 进程也能用,否则 dbOne/dbAll 抛"未初始化"
177
189
  initSystemUser(db);
178
190
  initDisputeSchema(db);
179
191
  initNotificationSchema(db);
@@ -313,7 +325,7 @@ No auth required, no parameters needed.
313
325
 
314
326
  Roles: buyer (browse/order/confirm) | seller (list/accept/ship) | logistics (pickup/transit/deliver) | reviewer (reviews) | arbitrator (disputes/rulings).
315
327
 
316
- ⚠️ **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.`,
317
329
  inputSchema: {
318
330
  type: 'object',
319
331
  properties: {
@@ -854,7 +866,7 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
854
866
  name: 'webaz_referral',
855
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)**.
856
868
 
857
- ⚠️ **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).
858
870
 
859
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.`,
860
872
  inputSchema: {
@@ -869,7 +881,7 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
869
881
  name: 'webaz_share_link',
870
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.
871
883
 
872
- ⚠️ **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.
873
885
 
874
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.`,
875
887
  inputSchema: {
@@ -1416,28 +1428,44 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
1416
1428
  },
1417
1429
  {
1418
1430
  name: 'webaz_contribute',
1419
- 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).
1420
1434
 
1421
1435
  Actions:
1422
- - list_open (default): open tasks (opt. area filter)
1423
- - 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.
1424
- - submit: mark in_review with pr_ref
1425
- - status: tasks you hold (claimed/in_review)
1426
- - 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).
1427
1443
 
1428
- 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).`,
1429
1445
  inputSchema: {
1430
1446
  type: 'object',
1431
1447
  properties: {
1432
- action: { type: 'string', enum: ['list_open', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | claim | submit | status | profile' },
1433
- api_key: { type: 'string', description: "User's api_key (accountable identity)" },
1434
- task_id: { type: 'string', description: 'claim / submit: the task id' },
1435
- 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)' },
1436
1459
  provenance: { type: 'string', enum: ['human', 'ai_assisted', 'ai_authored'], description: 'claim: self-declared authorship (default human)' },
1437
- 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' },
1438
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' },
1439
1468
  },
1440
- required: ['api_key'],
1441
1469
  },
1442
1470
  },
1443
1471
  ];
@@ -1479,19 +1507,100 @@ async function handleFeedback(args) {
1479
1507
  },
1480
1508
  });
1481
1509
  }
1482
- // RFC-006 Gap 1: webaz_contribute 协调"谁在做什么"(双模;仅 NETWORK)
1483
- 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) {
1484
1533
  const action = args.action || 'list_open';
1485
1534
  const apiKey = args.api_key;
1486
- if (!apiKey)
1487
- return { error: 'api_key required' };
1488
1535
  if (toolBackend('webaz_contribute') !== 'network') {
1489
1536
  return {
1490
1537
  _mode: 'sandbox',
1491
- 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.',
1492
1539
  error_code: 'CONTRIBUTE_NEEDS_NETWORK',
1493
1540
  };
1494
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
+ };
1495
1604
  if (action === 'status')
1496
1605
  return apiCall('/api/build-tasks?mine=1', { apiKey });
1497
1606
  if (action === 'profile')
@@ -1503,38 +1612,32 @@ async function handleContribute(args) {
1503
1612
  const r = await apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/claim', {
1504
1613
  method: 'POST', apiKey, body: { provenance: args.provenance },
1505
1614
  });
1506
- // RFC-006 断点1(b)交接:认领成功后直接下发"怎么真正动手",让贡献者的【编码 agent】接手 git/PR。
1507
- // 关键:人不必会 git——人的编码 agent( Claude Code)做;人(Passkey 真人)担责。
1508
- if (!r.error) {
1509
- r.handoff = {
1510
- repo: 'https://github.com/seasonsagents-art/webaz',
1511
- start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
1512
- 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.',
1513
- 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.',
1514
- then: `When the PR is open, report it back: webaz_contribute action=submit task_id=${tid} pr_ref=#<N>.`,
1515
- human_note: "You don't need to know git — your coding agent does it; you (the Passkey-bound human) stay accountable.",
1516
- };
1517
- }
1615
+ if (!r.error)
1616
+ r.handoff = buildContributeHandoff(r.canonical_contribution_target, tid);
1518
1617
  return r;
1519
1618
  }
1520
1619
  if (action === 'submit') {
1521
1620
  const tid = args.task_id;
1522
1621
  if (!tid)
1523
1622
  return { error: 'task_id required for action=submit' };
1524
- return apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/submit', {
1525
- 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 },
1526
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;
1527
1632
  }
1528
- // list_open(默认)
1529
- const q = args.area ? '?status=open&area=' + encodeURIComponent(String(args.area)) : '?status=open';
1530
- return apiCall('/api/build-tasks' + q, { apiKey });
1633
+ return { error: 'unknown action: ' + action };
1531
1634
  }
1532
1635
  async function handleInfo() {
1533
1636
  const summary = getManifestSummary();
1534
1637
  // RFC-003 Batch 0:NETWORK 模式下,best-effort 拉 webaz.xyz 的 live 协议状态,
1535
1638
  // 让带 key 的 agent 拿到【真网络】数字,而非只看本机本地 live_stats(下方仍保留并标注为本地)。
1536
1639
  let network_live = null;
1537
- if (MODE === 'network') {
1640
+ if (isNetworkMode()) {
1538
1641
  try {
1539
1642
  const ps = await apiCall('/api/protocol-status');
1540
1643
  network_live = { source: `${WEBAZ_API_URL}/api/protocol-status (live, fetched this call)`, ...ps };
@@ -1587,11 +1690,13 @@ async function handleInfo() {
1587
1690
  // 佣金按【功能】中性描述(commission_model),不做"自证不是X"的辩护——正常机制无需自证。
1588
1691
  network_state: {
1589
1692
  // RFC-003 P3:显式声明当前客户端模式,让 agent 一眼分清"真网络 vs 本机沙盒"
1590
- mode: MODE, // 'network' | 'sandbox'
1693
+ mode: MODE, // 'network' | 'network_readonly' | 'sandbox'
1591
1694
  mode_banner: modeBanner(),
1592
1695
  mode_meaning: MODE === 'network'
1593
1696
  ? '🟢 NETWORK:核心交易工具(下单/上架/履约/比价等)走 webaz.xyz 共享生产网络。真网络规模见下方 network_live(本次实时拉取);live_stats 仍是本机本地缓存,仅供参考。'
1594
- : '🟡 SANDBOX:所有工具都在本机本地 SQLite 运行,与 webaz.xyz 全网隔离。任何计数 / 账号 / 订单仅本机有效,不是真实网络状态。设 WEBAZ_API_KEY 可切到 NETWORK。',
1697
+ : MODE === 'network_readonly'
1698
+ ? '🟢 NETWORK(只读):无 api_key。公共读(搜索/榜单/价格史/浏览)打 webaz.xyz 真网络(见 network_live)。要交易(注册/下单/上架等)请设 WEBAZ_API_KEY —— 到 ' + WEBAZ_API_URL + '/#welcome 申请邀请。'
1699
+ : '🟡 SANDBOX:所有工具都在本机本地 SQLite 运行,与 webaz.xyz 全网隔离(显式 dev/demo 模式)。任何计数 / 账号 / 订单仅本机有效。不设 WEBAZ_MODE 则默认走真网络只读。',
1595
1700
  phase: 'pre_launch',
1596
1701
  real_users_on_canonical: 0,
1597
1702
  canonical_endpoint: 'https://webaz.xyz',
@@ -1629,10 +1734,10 @@ async function handleInfo() {
1629
1734
  split: '7:2:1 — L1 70% / L2 20% / L3 10% of an order\'s commission_pool',
1630
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).',
1631
1736
  attribution: 'EXPLICIT per-order — commission goes to the promoter attributed at purchase time, not derived from the buyer\'s sponsor chain.',
1632
- 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).',
1633
1738
  redirect_rules: 'chain_gap (no L / invalid sponsor) → charity_fund; level beyond the region cap → global_fund.',
1634
1739
  l1_gate: 'the promoter must be a verified buyer (≥1 completed order) to receive commission, otherwise that share redirects.',
1635
- 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.',
1636
1741
  },
1637
1742
  // QA 轮 3 FAIL:roles 漏 reviewer。register 工具支持 5 个角色,info 必须列全。
1638
1743
  roles: {
@@ -1674,10 +1779,10 @@ async function handleInfo() {
1674
1779
  function handleRegister(args) {
1675
1780
  // ─── RFC-003 P3:NETWORK 模式不自助建号 ────────────────────────
1676
1781
  // 自助注册会绕过邀请码 / captcha / 责任制;且 CHARTER §4 I-5 要求账号必须由已绑 Passkey
1677
- // 的真人创建("每个 agent 背后有可问责的真人")。NETWORK 模式下改为引导真人去 webaz.xyz 拿 key。
1678
- if (MODE === 'network') {
1782
+ // 的真人创建("每个 agent 背后有可问责的真人")。NETWORK / 无 key 只读模式下都引导真人去 webaz.xyz 拿 key。
1783
+ if (isNetworkMode()) {
1679
1784
  return {
1680
- _mode: 'network',
1785
+ _mode: MODE,
1681
1786
  registration: 'must_be_done_by_human_at_webaz_xyz',
1682
1787
  message: '🟢 NETWORK 模式下不支持 agent 自助注册。开放协议的信任来自"每个 agent 背后有可问责的真人",所以注册这一步刻意留给真人在 webaz.xyz 完成。请按三步加入共享网络:',
1683
1788
  steps: [
@@ -1945,8 +2050,8 @@ async function handleSearch(args) {
1945
2050
  : '没有找到匹配的商品';
1946
2051
  return { found: 0, message: hint, products: [], matched_by: 'strict_no_match' };
1947
2052
  }
1948
- const enriched = products.map((p) => {
1949
- 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);
1950
2055
  const rep_level = p.rep_level || 'new';
1951
2056
  const rep_points = Number(p.rep_points) || 0;
1952
2057
  const completion = Number(p.completion_count) || 0;
@@ -1971,7 +2076,7 @@ async function handleSearch(args) {
1971
2076
  const firstSaleBoost = firstSold && (Date.now() - new Date(firstSold.replace(' ', 'T') + 'Z').getTime()) < 14 * 86400_000 ? 5 : 0;
1972
2077
  const score = completion * 0.5 + rep_points * 0.1 + sharer * 2.0 + freshness + firstSaleBoost - dispute * 5.0;
1973
2078
  return { ...p, _boost: boost, _rep_level: rep_level, _rep_points: rep_points, _score: score, _freshness: freshness, _first_sale_boost: firstSaleBoost };
1974
- });
2079
+ }));
1975
2080
  const sorted = sortMode === 'trending'
1976
2081
  ? enriched.sort((a, b) => b._score - a._score || b._boost - a._boost).slice(0, limit)
1977
2082
  : enriched.slice(0, limit);
@@ -2200,7 +2305,7 @@ async function handleListProduct(args) {
2200
2305
  // stake 在 place_order 那一刻按"该订单总额 × stake_rate"现锁,订单结算时退该笔,违约时扣该笔。
2201
2306
  // 这样每个 active 订单都有独立 stake 担保,多 stock 商品也不会被空头薅。
2202
2307
  // product.stake_amount 字段保留为"indicative rate × price"(前端展示用),不强制 lock。
2203
- const stakeDiscount = getStakeDiscount(db, user.id);
2308
+ const stakeDiscount = await getStakeDiscount(db, user.id);
2204
2309
  const stakeRate = Math.max(0.05, 0.15 - stakeDiscount); // 最低 5%,声誉越高折扣越大
2205
2310
  const stakeAmount = Math.round(price * stakeRate * 100) / 100; // indicative only; actual lock per-order
2206
2311
  const id = generateId('prd');
@@ -2682,8 +2787,8 @@ async function handleNotifications(args) {
2682
2787
  return auth;
2683
2788
  const { user } = auth;
2684
2789
  const onlyUnread = args.unread === true;
2685
- const notifs = getNotifications(db, user.id, onlyUnread, 30);
2686
- const unread = getUnreadCount(db, user.id);
2790
+ const notifs = await getNotifications(db, user.id, onlyUnread, 30);
2791
+ const unread = await getUnreadCount(db, user.id);
2687
2792
  if (args.mark_read) {
2688
2793
  markRead(db, user.id);
2689
2794
  }
@@ -2770,9 +2875,9 @@ async function handleDispute(args) {
2770
2875
  // ── 查看争议详情 ────────────────────────────────────────────
2771
2876
  if (action === 'view') {
2772
2877
  let dispute = args.dispute_id
2773
- ? getDisputeDetails(db, args.dispute_id)
2878
+ ? await getDisputeDetails(db, args.dispute_id)
2774
2879
  : args.order_id
2775
- ? getOrderDispute(db, args.order_id)
2880
+ ? await getOrderDispute(db, args.order_id)
2776
2881
  : null;
2777
2882
  if (!dispute)
2778
2883
  return { error: '找不到争议记录,请提供 dispute_id 或 order_id' };
@@ -2812,7 +2917,7 @@ async function handleDispute(args) {
2812
2917
  if (user.role !== 'arbitrator') {
2813
2918
  return { error: '只有仲裁员可以查看所有待处理争议' };
2814
2919
  }
2815
- const disputes = getOpenDisputes(db);
2920
+ const disputes = await getOpenDisputes(db);
2816
2921
  return {
2817
2922
  open_count: disputes.length,
2818
2923
  disputes: disputes.map(d => ({
@@ -2836,7 +2941,7 @@ async function handleDispute(args) {
2836
2941
  // 如有证据描述,先创建证据记录
2837
2942
  const evidenceIds = [];
2838
2943
  if (args.evidence_description) {
2839
- const dispute = getDisputeDetails(db, args.dispute_id);
2944
+ const dispute = await getDisputeDetails(db, args.dispute_id);
2840
2945
  if (dispute) {
2841
2946
  const eid = generateId('evt');
2842
2947
  db.prepare(`
@@ -2994,7 +3099,7 @@ async function handleSkill(args) {
2994
3099
  if (!('error' in a))
2995
3100
  userId = a.user.id;
2996
3101
  }
2997
- const skills = listSkills(db, {
3102
+ const skills = await listSkills(db, {
2998
3103
  skillType: args.skill_type,
2999
3104
  query: args.query,
3000
3105
  subscriberId: userId,
@@ -3052,7 +3157,7 @@ async function handleSkill(args) {
3052
3157
  }
3053
3158
  // ── 我发布的 Skill ────────────────────────────────────────
3054
3159
  if (action === 'my_skills') {
3055
- const skills = getMySkills(db, user.id);
3160
+ const skills = await getMySkills(db, user.id);
3056
3161
  return {
3057
3162
  total: skills.length,
3058
3163
  skills: skills.map(formatSkillForAgent),
@@ -3061,7 +3166,7 @@ async function handleSkill(args) {
3061
3166
  }
3062
3167
  // ── 我订阅的 Skill ────────────────────────────────────────
3063
3168
  if (action === 'my_subs') {
3064
- const skills = getMySubscriptions(db, user.id);
3169
+ const skills = await getMySubscriptions(db, user.id);
3065
3170
  return {
3066
3171
  total: skills.length,
3067
3172
  subscriptions: skills.map(formatSkillForAgent),
@@ -3361,10 +3466,14 @@ async function handleReferral(args) {
3361
3466
  const tiers = db.prepare("SELECT tier, pv_threshold, score_per_hit FROM binary_tier_config WHERE active=1 ORDER BY tier ASC").all();
3362
3467
  const pair = Math.min(Number(me?.total_left_pv ?? 0), Number(me?.total_right_pv ?? 0));
3363
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;
3364
3471
  return {
3365
3472
  user_id: userId,
3366
3473
  name: user.name,
3367
- 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, // 仅推土机
3368
3477
  region: user.region ?? 'global',
3369
3478
  permissions: {
3370
3479
  can_earn_l1_commission: canL1,
@@ -3379,10 +3488,10 @@ async function handleReferral(args) {
3379
3488
  grand_total: byLevel[1].total + byLevel[2].total + byLevel[3].total,
3380
3489
  },
3381
3490
  binary: {
3382
- left_invite_link: `/?ref=${userId}&side=left`,
3383
- right_invite_link: `/?ref=${userId}&side=right`,
3384
- platform_left: `/?placement=${userId}&side=left`, // 仅 PV 条线
3385
- 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,
3386
3495
  total_left_pv: Number(me?.total_left_pv ?? 0),
3387
3496
  total_right_pv: Number(me?.total_right_pv ?? 0),
3388
3497
  pair_volume: pair,
@@ -3402,7 +3511,7 @@ async function handleReferral(args) {
3402
3511
  }
3403
3512
  else if (lastAction === 'deactivate') {
3404
3513
  state = 'deactivated';
3405
- 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.';
3406
3515
  }
3407
3516
  else if (lastAction === 'auto_downgrade') {
3408
3517
  state = 'auto_downgraded';
@@ -3420,7 +3529,7 @@ async function handleReferral(args) {
3420
3529
  note,
3421
3530
  pending_escrow: { count: pending.n, total_amount: pending.total },
3422
3531
  expired_to_charity: { count: expired.n, total_amount: expired.total },
3423
- spec: 'RFC-002 §3.5 — rewards opt-in / 共建身份申请制',
3532
+ spec: 'RFC-002 §3.5 — rewards / share-commission opt-in (RFC-002)',
3424
3533
  };
3425
3534
  })(),
3426
3535
  tip: canL1
@@ -3468,10 +3577,10 @@ async function handleShareLink(args) {
3468
3577
  missing.push('application_not_submitted');
3469
3578
  return {
3470
3579
  error: 'rewards_opt_in_required',
3471
- 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)',
3472
3581
  missing_requirements: missing,
3473
3582
  next_steps: [
3474
- 'Open PWA #me → tap "申请共建身份 / Apply for builder identity"',
3583
+ 'Open PWA #me → tap "申请分享分润 / Enable share-commission opt-in"',
3475
3584
  'Read the 8-second disclosure (cannot skip)',
3476
3585
  'Submit application — pre-checks run server-side',
3477
3586
  ],
@@ -3510,7 +3619,11 @@ async function handleShareLink(args) {
3510
3619
  const override = db.prepare("SELECT l1_share_override FROM users WHERE id = ?").get(userId)?.l1_share_override ?? 0;
3511
3620
  const canL1 = override === 1 || (override === 0 && completed > 0);
3512
3621
  const rate = Number(product.commission_rate ?? 0);
3513
- 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}`;
3514
3627
  return {
3515
3628
  product: { id: product.id, title: product.title, price: product.price, commission_rate: rate },
3516
3629
  share_link: link,
@@ -3919,9 +4032,9 @@ async function readEndpoint(tool, subpath) {
3919
4032
  }
3920
4033
  }
3921
4034
  async function pwaApi(method, path, apiKey, body) {
3922
- // RFC-003:NETWORK 模式 → 走 webaz.xyz 共享网络(Bearer api_key)。仅 NETWORK_TOOLS 里的工具会到这里
3923
- // (其余未迁工具在 dispatch 被 Batch 0 守卫拦下);SANDBOX 模式仍转发本地 PWA(localhost)。
3924
- if (MODE === 'network') {
4035
+ // RFC-003:NETWORK / network_readonly → 走 webaz.xyz(Bearer 可空)。仅 NETWORK_TOOLS 里的工具会到这里
4036
+ // (其余未迁工具在 dispatch 被 Batch 0 守卫拦下);SANDBOX 才转发本地 PWA(localhost)。
4037
+ if (isNetworkMode()) {
3925
4038
  return apiCall(path.startsWith('/api') ? path : '/api' + path, { method, apiKey, body });
3926
4039
  }
3927
4040
  const opts = {
@@ -4694,7 +4807,7 @@ export async function startMCPServer() {
4694
4807
  if (request.params.uri !== MANIFEST_URI) {
4695
4808
  throw new Error(`未知资源:${request.params.uri}`);
4696
4809
  }
4697
- const manifest = generateManifest(db);
4810
+ const manifest = await generateManifest(db);
4698
4811
  return {
4699
4812
  contents: [
4700
4813
  {
@@ -4848,7 +4961,7 @@ export async function startMCPServer() {
4848
4961
  // ─── RFC-003 Batch 0 安全网:NETWORK 模式下未迁移的工具【硬失败】,不静默落本地沙盒 ───
4849
4962
  // 例外:info / register(NETWORK_SELF_AWARE)有专门 network-aware 处理,照常放行。
4850
4963
  let handled = false;
4851
- if (MODE === 'network' && !NETWORK_TOOLS.has(name) && !NETWORK_SELF_AWARE.has(name)) {
4964
+ if (isNetworkMode() && !NETWORK_TOOLS.has(name) && !NETWORK_SELF_AWARE.has(name)) {
4852
4965
  result = networkMigrationPending(name);
4853
4966
  handled = true;
4854
4967
  }
@@ -4991,7 +5104,9 @@ export async function startMCPServer() {
4991
5104
  }
4992
5105
  // RFC-003 P0: 给每个工具结果盖模式戳(诚实可见,防把 sandbox 当 live 网络)
4993
5106
  // P3: handler 可自行预设 _mode(如 register 在 network 模式返回引导,不是 sandbox 结果)→ 不覆盖。
4994
- const backend = toolBackend(name);
5107
+ // self-aware 工具(info/register)按全局 MODE 盖戳,不按 toolBackend(它们不在 NETWORK_TOOLS 但本就网络感知),
5108
+ // 否则 network_readonly/network 下 info 会被误盖 sandbox 戳,与其自身 network_state 矛盾。
5109
+ const backend = NETWORK_SELF_AWARE.has(name) ? (isNetworkMode() ? 'network' : 'sandbox') : toolBackend(name);
4995
5110
  if (result && typeof result === 'object' && !Array.isArray(result)) {
4996
5111
  const r = result;
4997
5112
  if (!('_mode' in r))