@seasonkoh/webaz 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/README.md +5 -1
  2. package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
  3. package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
  4. package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
  5. package/dist/layer0-foundation/L0-1-database/db.js +65 -0
  6. package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
  7. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
  8. package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
  9. package/dist/layer1-agent/L1-1-mcp-server/server.js +288 -208
  10. package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
  11. package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
  12. package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
  13. package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
  14. package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
  15. package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +182 -0
  16. package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
  17. package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
  18. package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +11 -3
  19. package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
  20. package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
  21. package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
  22. package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
  23. package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
  24. package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
  25. package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
  26. package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
  27. package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
  28. package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
  29. package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
  30. package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
  31. package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
  32. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
  33. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
  34. package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
  35. package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
  36. package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
  37. package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
  38. package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
  39. package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
  40. package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +191 -0
  41. package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
  42. package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
  43. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
  44. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
  45. package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
  46. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
  47. package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
  48. package/dist/pwa/acp-feed.js +13 -1
  49. package/dist/pwa/admin-bearer-auth.js +21 -0
  50. package/dist/pwa/contract-fingerprint.js +2 -0
  51. package/dist/pwa/email-delivery.js +127 -0
  52. package/dist/pwa/endpoint-actions.js +5 -1
  53. package/dist/pwa/goal-index.js +8 -8
  54. package/dist/pwa/human-presence.js +62 -0
  55. package/dist/pwa/public/app.js +1485 -283
  56. package/dist/pwa/public/i18n.js +297 -59
  57. package/dist/pwa/public/index.html +1 -0
  58. package/dist/pwa/public/openapi.json +5 -5
  59. package/dist/pwa/public/whitepaper/en/index.html +153 -0
  60. package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
  61. package/dist/pwa/rate-limit.js +22 -0
  62. package/dist/pwa/routes/account-deletion.js +15 -13
  63. package/dist/pwa/routes/addresses.js +10 -9
  64. package/dist/pwa/routes/admin-admins.js +13 -14
  65. package/dist/pwa/routes/admin-analytics.js +109 -69
  66. package/dist/pwa/routes/admin-atomic.js +10 -4
  67. package/dist/pwa/routes/admin-catalog.js +13 -11
  68. package/dist/pwa/routes/admin-editor-picks.js +15 -10
  69. package/dist/pwa/routes/admin-events.js +5 -3
  70. package/dist/pwa/routes/admin-health.js +2 -1
  71. package/dist/pwa/routes/admin-moderation.js +50 -29
  72. package/dist/pwa/routes/admin-ops.js +35 -23
  73. package/dist/pwa/routes/admin-protocol-params.js +16 -19
  74. package/dist/pwa/routes/admin-reports.js +23 -21
  75. package/dist/pwa/routes/admin-tokenomics.js +26 -25
  76. package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
  77. package/dist/pwa/routes/admin-users-query.js +65 -53
  78. package/dist/pwa/routes/admin-verifier-flow.js +82 -41
  79. package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
  80. package/dist/pwa/routes/admin-wallet-ops.js +32 -7
  81. package/dist/pwa/routes/agent-buy.js +46 -22
  82. package/dist/pwa/routes/agent-governance.js +52 -56
  83. package/dist/pwa/routes/ai.js +7 -5
  84. package/dist/pwa/routes/analytics.js +43 -41
  85. package/dist/pwa/routes/anchors.js +19 -20
  86. package/dist/pwa/routes/announcements.js +13 -13
  87. package/dist/pwa/routes/arbitrator.js +97 -31
  88. package/dist/pwa/routes/auction.js +157 -116
  89. package/dist/pwa/routes/auth-login.js +6 -4
  90. package/dist/pwa/routes/auth-read.js +21 -10
  91. package/dist/pwa/routes/auth-register.js +111 -26
  92. package/dist/pwa/routes/auth-sessions.js +12 -11
  93. package/dist/pwa/routes/blocklist.js +16 -15
  94. package/dist/pwa/routes/build-feedback.js +10 -9
  95. package/dist/pwa/routes/build-reputation.js +6 -2
  96. package/dist/pwa/routes/build-tasks.js +45 -13
  97. package/dist/pwa/routes/buyer-feeds.js +27 -25
  98. package/dist/pwa/routes/cart.js +16 -15
  99. package/dist/pwa/routes/charity.js +212 -150
  100. package/dist/pwa/routes/chat.js +42 -43
  101. package/dist/pwa/routes/checkin-tasks.js +10 -9
  102. package/dist/pwa/routes/checkout-helpers.js +12 -10
  103. package/dist/pwa/routes/claim-initiators.js +34 -14
  104. package/dist/pwa/routes/claim-verify.js +86 -53
  105. package/dist/pwa/routes/claim-voting.js +43 -18
  106. package/dist/pwa/routes/contribution-identity.js +164 -0
  107. package/dist/pwa/routes/contribution-score.js +19 -0
  108. package/dist/pwa/routes/coupons.js +19 -16
  109. package/dist/pwa/routes/dashboards.js +18 -16
  110. package/dist/pwa/routes/dispute-cases.js +25 -24
  111. package/dist/pwa/routes/disputes-read.js +45 -51
  112. package/dist/pwa/routes/disputes-write.js +124 -61
  113. package/dist/pwa/routes/evidence.js +9 -9
  114. package/dist/pwa/routes/external-anchors.js +13 -12
  115. package/dist/pwa/routes/feedback.js +29 -33
  116. package/dist/pwa/routes/flash-sales.js +18 -16
  117. package/dist/pwa/routes/follows.js +25 -24
  118. package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
  119. package/dist/pwa/routes/governance-onboarding.js +70 -59
  120. package/dist/pwa/routes/group-buys.js +22 -22
  121. package/dist/pwa/routes/growth.js +34 -31
  122. package/dist/pwa/routes/import-product.js +12 -10
  123. package/dist/pwa/routes/kyc.js +9 -8
  124. package/dist/pwa/routes/leaderboard.js +20 -18
  125. package/dist/pwa/routes/listings.js +23 -22
  126. package/dist/pwa/routes/logistics.js +10 -8
  127. package/dist/pwa/routes/manifests.js +27 -27
  128. package/dist/pwa/routes/me-data.js +23 -21
  129. package/dist/pwa/routes/notifications.js +7 -6
  130. package/dist/pwa/routes/offers.js +30 -12
  131. package/dist/pwa/routes/orders-action.js +51 -29
  132. package/dist/pwa/routes/orders-create.js +75 -20
  133. package/dist/pwa/routes/orders-read.js +21 -20
  134. package/dist/pwa/routes/p2p-products.js +30 -18
  135. package/dist/pwa/routes/payments-governance.js +61 -56
  136. package/dist/pwa/routes/peers.js +9 -8
  137. package/dist/pwa/routes/pin-receipts.js +13 -13
  138. package/dist/pwa/routes/products-aliases.js +12 -10
  139. package/dist/pwa/routes/products-claims.js +36 -17
  140. package/dist/pwa/routes/products-create.js +53 -38
  141. package/dist/pwa/routes/products-crud.js +17 -16
  142. package/dist/pwa/routes/products-links.js +49 -26
  143. package/dist/pwa/routes/products-list.js +6 -4
  144. package/dist/pwa/routes/products-meta.js +40 -39
  145. package/dist/pwa/routes/products-update.js +19 -5
  146. package/dist/pwa/routes/profile-credentials.js +20 -19
  147. package/dist/pwa/routes/profile-identity.js +14 -13
  148. package/dist/pwa/routes/profile-location.js +7 -6
  149. package/dist/pwa/routes/profile-placement.js +20 -19
  150. package/dist/pwa/routes/profile-prefs.js +11 -11
  151. package/dist/pwa/routes/promoter.js +58 -66
  152. package/dist/pwa/routes/public-build-tasks.js +19 -0
  153. package/dist/pwa/routes/public-utils.js +108 -46
  154. package/dist/pwa/routes/push.js +16 -15
  155. package/dist/pwa/routes/ratings.js +92 -32
  156. package/dist/pwa/routes/recover-key.js +66 -26
  157. package/dist/pwa/routes/referral.js +37 -52
  158. package/dist/pwa/routes/reputation.js +3 -2
  159. package/dist/pwa/routes/returns.js +76 -73
  160. package/dist/pwa/routes/reviews.js +41 -18
  161. package/dist/pwa/routes/rewards-apply.js +16 -15
  162. package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
  163. package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
  164. package/dist/pwa/routes/rfqs.js +163 -85
  165. package/dist/pwa/routes/search.js +16 -14
  166. package/dist/pwa/routes/secondhand.js +25 -22
  167. package/dist/pwa/routes/seller-quota.js +24 -26
  168. package/dist/pwa/routes/share-redirects.js +60 -55
  169. package/dist/pwa/routes/shareables-interactions.js +34 -35
  170. package/dist/pwa/routes/shareables.js +55 -51
  171. package/dist/pwa/routes/shop-referral.js +58 -0
  172. package/dist/pwa/routes/shops.js +25 -20
  173. package/dist/pwa/routes/signaling.js +10 -9
  174. package/dist/pwa/routes/skill-market.js +16 -16
  175. package/dist/pwa/routes/skills.js +15 -14
  176. package/dist/pwa/routes/snf.js +14 -13
  177. package/dist/pwa/routes/tags.js +10 -9
  178. package/dist/pwa/routes/task-proposals.js +121 -0
  179. package/dist/pwa/routes/trial.js +72 -52
  180. package/dist/pwa/routes/trusted-kpi.js +20 -18
  181. package/dist/pwa/routes/url-claim.js +67 -28
  182. package/dist/pwa/routes/users-public.js +62 -70
  183. package/dist/pwa/routes/variants.js +12 -13
  184. package/dist/pwa/routes/verifier-user.js +61 -21
  185. package/dist/pwa/routes/verify-tasks.js +49 -25
  186. package/dist/pwa/routes/waitlist.js +16 -15
  187. package/dist/pwa/routes/wallet-read.js +75 -37
  188. package/dist/pwa/routes/wallet-write.js +12 -9
  189. package/dist/pwa/routes/webauthn.js +25 -26
  190. package/dist/pwa/routes/webhooks.js +26 -26
  191. package/dist/pwa/routes/welcome.js +45 -50
  192. package/dist/pwa/routes/wishlist-qa.js +29 -32
  193. package/dist/pwa/server.js +304 -90
  194. package/dist/version.js +1 -1
  195. package/package.json +76 -3
@@ -20,6 +20,7 @@ import path from 'path';
20
20
  import { fileURLToPath } from 'url';
21
21
  import crypto from 'crypto';
22
22
  import { initDatabase, generateId } from '../layer0-foundation/L0-1-database/schema.js';
23
+ import { setSeamDb } from '../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
23
24
  import { initSystemUser, transition, getOrderStatus, checkTimeouts, settleFault } from '../layer0-foundation/L0-2-state-machine/engine.js';
24
25
  import { endpointToAction, endpointToReadAction } from './endpoint-actions.js';
25
26
  import { AGENT_RATE_PER_MIN_DEFAULTS, CROSS_USER_READ_DAILY_CAP, MASS_ACTION_TYPES, MASS_ACTION_DAILY_CAPS } from './limits.js';
@@ -46,8 +47,10 @@ import { createPublicClient, createWalletClient, http, parseAbiItem, parseAbi, p
46
47
  import { baseSepolia, base } from 'viem/chains';
47
48
  import { createHmac, createHash, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
48
49
  import { safeFetch } from './security/ssrf.js';
50
+ import { deliverVerificationCode, emailDeliveryNotConfigured, isVerificationEmailReady, } from './email-delivery.js';
49
51
  // @simplewebauthn/server 已迁出到 src/pwa/routes/webauthn.ts (#1013 Phase 1)
50
52
  import { registerWebauthnRoutes } from './routes/webauthn.js';
53
+ import { createHumanPresence } from './human-presence.js';
51
54
  // welcome 域(#991 /welcome 落地页 + #1005 反 bot)已迁出 (#1013 Phase 2)
52
55
  import { registerWelcomeRoutes } from './routes/welcome.js';
53
56
  // 测评免单 (#978-#988) 域 + 评估 cron 已迁出 (#1013 Phase 3)
@@ -168,6 +171,7 @@ import { registerListingsRoutes } from './routes/listings.js';
168
171
  import { registerEvidenceRoutes } from './routes/evidence.js';
169
172
  // 分享 / 重定向 / QR (#1013 Phase 54) — 4 endpoints
170
173
  import { registerShareRedirectsRoutes } from './routes/share-redirects.js';
174
+ import { registerShopReferralRoutes } from './routes/shop-referral.js';
171
175
  // Profile 凭据 (#1013 Phase 55) — 5 endpoints (密码 + 邮箱绑定)
172
176
  import { registerProfileCredentialsRoutes } from './routes/profile-credentials.js';
173
177
  // Profile 双轨挂靠 (#1013 Phase 56) — 3 endpoints
@@ -198,6 +202,7 @@ import { registerAdminEventsRoutes } from './routes/admin-events.js';
198
202
  import { registerAdminModerationRoutes } from './routes/admin-moderation.js';
199
203
  // Admin 钱包运维 (#1013 Phase 69) — hot-wallet 2 + withdrawals 2 = 4 endpoints
200
204
  import { registerAdminWalletOpsRoutes } from './routes/admin-wallet-ops.js';
205
+ import { resolveBearerProtocolAdmin } from './admin-bearer-auth.js';
201
206
  // Admin Catalog (#1013 Phase 70) — categories 2 + products 2 = 4 endpoints
202
207
  import { registerAdminCatalogRoutes } from './routes/admin-catalog.js';
203
208
  // Reputation 公开查询 (#1013 Phase 71) — 2 endpoints
@@ -303,9 +308,21 @@ import { registerAuthRegisterRoutes } from './routes/auth-register.js';
303
308
  import { registerBuildFeedbackRoutes } from './routes/build-feedback.js';
304
309
  import { initBuildFeedbackSchema } from '../layer2-business/L2-8-feedback/build-feedback-engine.js';
305
310
  import { registerBuildTasksRoutes } from './routes/build-tasks.js';
311
+ import { registerPublicBuildTasksRoutes } from './routes/public-build-tasks.js';
306
312
  import { initBuildTasksSchema } from '../layer2-business/L2-9-contribution/build-tasks-engine.js';
313
+ import { initBuildTaskAgentMetadataSchema } from '../layer2-business/L2-9-contribution/build-task-agent-metadata-store.js';
314
+ import { initTaskProposalSchema } from '../layer2-business/L2-9-contribution/task-proposal-store.js';
315
+ import { initTaskProposalAiSchema } from '../layer2-business/L2-9-contribution/task-proposal-ai-store.js';
316
+ import { initTaskProposalDraftLinkSchema } from '../layer2-business/L2-9-contribution/task-proposal-draft.js';
317
+ import { registerTaskProposalsRoutes } from './routes/task-proposals.js';
318
+ import { createSlidingWindowLimiter } from './rate-limit.js';
307
319
  import { registerBuildReputationRoutes } from './routes/build-reputation.js';
308
320
  import { initBuildReputationSchema } from '../layer2-business/L2-9-contribution/build-reputation-engine.js';
321
+ import { initGithubCredentialStoreSchema } from '../layer2-business/L2-9-contribution/github-credential-store.js';
322
+ import { initIdentityBindingSchema } from '../layer2-business/L2-9-contribution/identity-binding-store.js';
323
+ import { initIdentityClaimChallengeSchema } from '../layer2-business/L2-9-contribution/identity-claim-challenge-store.js';
324
+ import { registerContributionIdentityRoutes } from './routes/contribution-identity.js';
325
+ import { registerContributionScoreRoutes } from './routes/contribution-score.js';
309
326
  const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
310
327
  // ─── 链上地址派生 ──────────────────────────────────────────────
311
328
  const MASTER_SEED = process.env.WALLET_MASTER_SEED ?? 'webaz-dev-seed-changeme';
@@ -344,6 +361,7 @@ function deriveDepositAddress(userId) {
344
361
  }
345
362
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
346
363
  const db = initDatabase();
364
+ setSeamDb(db); // RFC-016 Phase 0:注入异步 DB seam(本进程)
347
365
  // #1013 Phase 9: claim-verify helpers 预绑 db(routes/claim-verify.ts 已 export 多签名版本)
348
366
  // 让 product-claims / review-claims / etc 跨域调用零侵入(无需改 callsite signature)
349
367
  const isEligibleClaimVerifier = (userId) => isEligibleClaimVerifierRaw(db, userId);
@@ -359,7 +377,14 @@ initReputationSchema(db);
359
377
  initOrderChainSchema(db);
360
378
  initBuildFeedbackSchema(db); // RFC-004 build_feedback
361
379
  initBuildTasksSchema(db); // RFC-006 build_tasks(协调层)
380
+ initBuildTaskAgentMetadataSchema(db); // PR9B — agent-ready task metadata satellite(schema only;FUTURE-TASK-BOARD-V1-DESIGN #326)
381
+ initTaskProposalSchema(db); // Task Proposal Inbox v1 — suggestion inbox(maintainer review;never auto build_task)
382
+ initTaskProposalAiSchema(db); // Task Proposal AI-assist — assistant-only recommendation/evidence(human decides)
383
+ initTaskProposalDraftLinkSchema(db); // Task Proposal draft links — source proposal ↔ draft task(converted at publish)
362
384
  initBuildReputationSchema(db); // RFC-006 build_reputation(独立池 + 贡献者看板)
385
+ initGithubCredentialStoreSchema(db); // PR 3B-3a — GitHub credential store + RFC-017 fact layer (schema only)
386
+ initIdentityBindingSchema(db); // PR 4a — GitHub identity → WebAZ account binding (append-only events + active projection)
387
+ initIdentityClaimChallengeSchema(db); // PR-F1 — identity-claim publication-challenge state (server-side nonce hash; schema only)
363
388
  initSnfSchema(db);
364
389
  initExternalAnchorSchema(db);
365
390
  // 启动时检查月衰减(last_decay_at ≥25 天才触发,重启幂等)
@@ -629,6 +654,53 @@ try {
629
654
  db.exec('CREATE INDEX IF NOT EXISTS idx_psa_recipient ON product_share_attribution(recipient_id, product_id)');
630
655
  }
631
656
  catch { }
657
+ // provenance (additive, audit-only — does NOT change commission math): how this attribution was created.
658
+ // source_type = 'direct_share'(商品/笔记直接分享) | 'shop_referral_verified_purchase'(店铺推荐懒升级)
659
+ try {
660
+ db.exec("ALTER TABLE product_share_attribution ADD COLUMN source_type TEXT");
661
+ }
662
+ catch { }
663
+ try {
664
+ db.exec("ALTER TABLE product_share_attribution ADD COLUMN source_ref TEXT");
665
+ }
666
+ catch { }
667
+ try {
668
+ db.exec("ALTER TABLE product_share_attribution ADD COLUMN source_shop_seller_id TEXT");
669
+ }
670
+ catch { }
671
+ try {
672
+ db.exec("ALTER TABLE product_share_attribution ADD COLUMN source_qualified_order_id TEXT");
673
+ }
674
+ catch { }
675
+ // ─── 店铺推荐锚定 (shop_referral_attribution) ─────────────────
676
+ // 店铺推荐【只】锚定推荐关系 + 二叉树位置 + 店铺来源,first-touch 30 天锁;它【不是】全店佣金权。
677
+ // 仅当被推荐人后来真实下单店铺里的某商品、且推荐人自己也 completed 买过同款时,才在下单时被【懒升级】
678
+ // 为该商品的 product_share_attribution(见 orders-create maybePromoteShopReferralToProductAttribution)。
679
+ db.exec(`
680
+ CREATE TABLE IF NOT EXISTS shop_referral_attribution (
681
+ seller_id TEXT NOT NULL,
682
+ recipient_id TEXT NOT NULL,
683
+ referrer_id TEXT NOT NULL,
684
+ ref_code TEXT NOT NULL,
685
+ side TEXT,
686
+ created_at TEXT DEFAULT (datetime('now')),
687
+ expires_at TEXT NOT NULL,
688
+ source TEXT DEFAULT 'shop_referral',
689
+ PRIMARY KEY (seller_id, recipient_id)
690
+ )
691
+ `);
692
+ try {
693
+ db.exec('CREATE INDEX IF NOT EXISTS idx_sra_referrer ON shop_referral_attribution(referrer_id, seller_id)');
694
+ }
695
+ catch { }
696
+ try {
697
+ db.exec('CREATE INDEX IF NOT EXISTS idx_sra_recipient ON shop_referral_attribution(recipient_id)');
698
+ }
699
+ catch { }
700
+ try {
701
+ db.exec('CREATE INDEX IF NOT EXISTS idx_sra_expires ON shop_referral_attribution(expires_at)');
702
+ }
703
+ catch { }
632
704
  // 商品分享链反推:buyer 买商品 P 时 → L1=谁分享 P 给 buyer → L2=谁分享 P 给 L1 → ...
633
705
  // 与 sponsor_path / placement_path 完全无关。某层断链 → 该层 null(佣金回流协议池)。
634
706
  function getProductShareChain(productId, buyerId, depth = 3) {
@@ -792,7 +864,9 @@ const DEFAULT_PARAMS = [
792
864
  // RFC-008:fund_base 硬帽 1%;pre-launch 减免到 0(社区基金按真实 GMV 注入,0 GMV 时是无回报的税)。有真实 GMV 再由治理开启(≤1%)。
793
865
  { key: 'fund_base_rate', value: '0', type: 'number', description: '协议基金池基础费率(RFC-008 硬帽 1%;pre-launch 减免=0,有真实 GMV 再由治理开启 ≤1%)', category: 'fee', min: 0, max: 0.01 },
794
866
  // RFC-008:起步免赔付门槛。0 = bootstrap(新商家零质押、违约免赔付只退款+掉信誉,降进入门槛);1 = 要求卖家质押(下单锁 stake、违约真没收)。上轨道后由治理开启。
795
- { key: 'require_seller_stake', value: '0', type: 'number', description: 'RFC-008 是否要求卖家质押(0=起步免赔付/零门槛,1=要求质押/真没收)', category: 'fee', min: 0, max: 1 },
867
+ // ⚠️ Codex #111:stake-required 模式(=1)【尚未实现】—— 下单不锁 stake、settleFault 仍按 stake_backing=0 不没收,
868
+ // 开启会给出虚假"真没收"协议语义。故 max 锁 0(不可开启);待 Phase 3 钱路径迁移实现真锁(下单原子锁 balance→staked)再放开。
869
+ { key: 'require_seller_stake', value: '0', type: 'number', description: 'RFC-008 是否要求卖家质押(0=起步免赔付/零门槛)。⚠️ stake-required(=1)未实现、暂锁 0 不可开启,见 Phase 3', category: 'fee', min: 0, max: 0 },
796
870
  // RFC-008 stage 2:违约罚没率,【与质押率解耦】(低质押=低摩擦 + 高罚没=强威慑,单一费率做不到)。
797
871
  // 背书订单:penalty = fault_penalty_rate × total,先扣 staked(封顶背书)再扣自由 balance(责任自负,真可执行)。
798
872
  // 起步免赔付(stake_backing=0):仍 0 没收,绝不碰新商家自由余额。settleFault 按订单 stake_backing 判定。
@@ -828,11 +902,14 @@ const DEFAULT_PARAMS = [
828
902
  { key: 'require_human_presence_for_arbitrate', value: '1', type: 'number', description: 'Arbitrator 仲裁需 WebAuthn 一次性 token(1=强制 / 0=不强制)— spec §4 铁律', category: 'security', min: 0, max: 1 },
829
903
  { key: 'require_human_presence_for_agent_revoke', value: '1', type: 'number', description: '用户撤销 agent 需 WebAuthn 一次性 token — spec §4 铁律', category: 'security', min: 0, max: 1 },
830
904
  { key: 'require_human_presence_for_delete_passkey', value: '1', type: 'number', description: '删除 Passkey 自身需 WebAuthn 一次性 token — 防失窃 Passkey 不需 Passkey 就可删它,堵死自我无效化漏洞', category: 'security', min: 0, max: 1 },
905
+ { key: 'require_human_presence_for_identity_claim', value: '1', type: 'number', description: 'GitHub 身份认领绑定(claim commit)需 WebAuthn 一次性 token — 4b 身份认领的真人铁律门(PR-F0 plumbing;claim endpoint 尚未开放)。默认强制,与 vote/arbitrate/agent_revoke/delete_passkey 同级。', category: 'security', min: 0, max: 1 },
831
906
  { key: 'require_human_presence_for_governance_apply', value: '1', type: 'number', description: '治理岗位申请(apply)需 WebAuthn 一次性 token — spec §3.1 Iron-Rule 反诱导 + 真人门', category: 'security', min: 0, max: 1 },
832
907
  { key: 'require_human_presence_for_governance_activate', value: '1', type: 'number', description: 'maintainer 激活治理岗位(activate)需 WebAuthn 一次性 token — spec §4.4 Iron-Rule 真人签发', category: 'security', min: 0, max: 1 },
833
908
  { key: 'require_human_presence_for_governance_resign', value: '1', type: 'number', description: '主动卸任治理岗位(resign)需 WebAuthn 一次性 token — spec §6.1 二次验证', category: 'security', min: 0, max: 1 },
834
909
  { key: 'require_human_presence_for_governance_appeal_resolve', value: '1', type: 'number', description: 'maintainer 裁决申诉(resolve appeal)需 WebAuthn 一次性 token — spec §7.2 Iron-Rule', category: 'security', min: 0, max: 1 },
835
- { key: 'require_human_presence_for_withdraw', value: '1', type: 'number', description: '提现(资金转出)需 WebAuthn 一次性 token 资金转出=真人在场铁律,email-OTP 在 agent 威胁模型下不足(agent 可读收件箱);#1115 全额对齐', category: 'security', min: 0, max: 1 },
910
+ // ⚠️ Codex #100:提现真人在场是【铁律】,锁死 min=max=1 不可关闭( protocol admin PATCH 0 绕过)
911
+ // 且 wallet-write.ts 已【无条件】执行 Passkey gate(不读此 param),此处仅作诚实展示 + PATCH 防线。
912
+ { key: 'require_human_presence_for_withdraw', value: '1', type: 'number', description: '提现(资金转出)需 WebAuthn 一次性 token — 真人在场【铁律,锁死不可关闭】;wallet-write 无条件执行', category: 'security', min: 1, max: 1 },
836
913
  { key: 'governance_resign_cooldown_days', value: '30', type: 'number', description: '主动卸任后冷却期(天)— 防止 farming 切换洗票 / 误操作反复', category: 'governance', min: 7, max: 365 },
837
914
  { key: 'governance_appeal_window_days', value: '14', type: 'number', description: '收到 auto_deactivate 通知后申诉窗口(天)— spec §7.2', category: 'governance', min: 7, max: 90 },
838
915
  { key: 'governance_appeal_min_reason_chars', value: '100', type: 'number', description: '申诉理由最少字符数(防空 appeal)', category: 'governance', min: 30, max: 2000 },
@@ -941,6 +1018,24 @@ try {
941
1018
  WHERE key = 'fund_base_rate' AND max_value > 0.01`).run();
942
1019
  if (fbCap.changes > 0)
943
1020
  console.log(`[migration RFC-008] fund_base 硬帽收紧 → max 1%`);
1021
+ // Codex #112 P1:仅收紧 max_value 不够 —— 历史 value > 新 cap 的行(如曾被治理调到 0.05)在 runtime
1022
+ // getProtocolParam 直接读 value,仍按超帽费率收费,硬帽形同虚设。逐 key 把超帽 value clamp 回 cap,
1023
+ // 并记 protocol_params_log。先于下面 fund_base 的 pre-launch 减免(减免只针对未被治理改过的原始 0.01)。
1024
+ const clampFeeValue = (key, cap) => {
1025
+ const cur = db.prepare('SELECT value FROM protocol_params WHERE key = ? AND CAST(value AS REAL) > ?').get(key, cap);
1026
+ if (!cur)
1027
+ return;
1028
+ db.prepare(`UPDATE protocol_params SET value = ?, updated_at = datetime('now') WHERE key = ? AND CAST(value AS REAL) > ?`).run(String(cap), key, cap);
1029
+ console.log(`[migration RFC-008] ${key} value ${cur.value} → ${cap}(clamp 回硬帽)`);
1030
+ try {
1031
+ db.prepare(`INSERT INTO protocol_params_log (id, key, old_value, new_value, changed_by, action)
1032
+ VALUES (?,?,?,?,?,'migrate')`).run(generateId('ppl'), key, cur.value, String(cap), 'migration_RFC-008');
1033
+ }
1034
+ catch { }
1035
+ };
1036
+ clampFeeValue('protocol_fee_rate_shop', 0.02);
1037
+ clampFeeValue('protocol_fee_rate_secondhand', 0.02);
1038
+ clampFeeValue('fund_base_rate', 0.01);
944
1039
  const fb = db.prepare(`UPDATE protocol_params SET value = '0', default_value = '0', updated_at = datetime('now')
945
1040
  WHERE key = 'fund_base_rate' AND value = '0.01' AND updated_by IS NULL`).run();
946
1041
  if (fb.changes > 0) {
@@ -955,6 +1050,52 @@ try {
955
1050
  catch (e) {
956
1051
  console.error('[migration RFC-008]', e);
957
1052
  }
1053
+ // Codex #111 P1:require_seller_stake 当前是【假开关】—— 即使=1,下单仍写 stake_backing=0、不锁 stake,
1054
+ // settleFault 仍按 backing=0 不没收;开启只会给出虚假"真没收"语义。stake-required 真锁留待 Phase 3。
1055
+ // 在此之前:max 锁 0(不可开启)+ 若历史 DB 被设为 1 则降回 0(中和假开关),并记 protocol_params_log。
1056
+ try {
1057
+ const cap = db.prepare(`UPDATE protocol_params SET max_value = 0, updated_at = datetime('now')
1058
+ WHERE key = 'require_seller_stake' AND max_value > 0`).run();
1059
+ if (cap.changes > 0)
1060
+ console.log(`[migration RFC-008] require_seller_stake max → 0(stake-required 未实现,锁关)`);
1061
+ const rss = db.prepare(`SELECT value FROM protocol_params WHERE key = 'require_seller_stake' AND CAST(value AS REAL) > 0`).get();
1062
+ if (rss) {
1063
+ db.prepare(`UPDATE protocol_params SET value = '0', updated_at = datetime('now') WHERE key = 'require_seller_stake'`).run();
1064
+ console.log(`[migration RFC-008] require_seller_stake value ${rss.value} → 0(假开关中和)`);
1065
+ try {
1066
+ db.prepare(`INSERT INTO protocol_params_log (id, key, old_value, new_value, changed_by, action)
1067
+ VALUES (?,?,?,?,?,'migrate')`).run(generateId('ppl'), 'require_seller_stake', rss.value, '0', 'migration_RFC-008');
1068
+ }
1069
+ catch { }
1070
+ }
1071
+ }
1072
+ catch (e) {
1073
+ console.error('[migration require_seller_stake lock]', e);
1074
+ }
1075
+ // Codex #100 P1:提现真人 Passkey 是【铁律】,绝不可被 protocol param 关闭。
1076
+ // 旧默认 min=0/max=1 让 protocol admin PATCH 设 0 即可绕过(wallet-write 旧代码 if(param===1))。
1077
+ // 双重防线:wallet-write 已改无条件执行 Passkey gate(不再读此 param);此处把 param 锁死 value/min/max=1
1078
+ // (PATCH 校验 min/max → 再不能设 0),并把历史 DB 的 value=0 / 放开的 min/max clamp 回 1,记 protocol_params_log。
1079
+ try {
1080
+ const wkey = 'require_human_presence_for_withdraw';
1081
+ const lockBounds = db.prepare(`UPDATE protocol_params SET min_value = 1, max_value = 1, updated_at = datetime('now')
1082
+ WHERE key = ? AND (min_value != 1 OR max_value != 1)`).run(wkey);
1083
+ if (lockBounds.changes > 0)
1084
+ console.log(`[migration Codex#100] ${wkey} min/max → 1(铁律,锁死不可关闭)`);
1085
+ const hp = db.prepare(`SELECT value FROM protocol_params WHERE key = ? AND CAST(value AS REAL) != 1`).get(wkey);
1086
+ if (hp) {
1087
+ db.prepare(`UPDATE protocol_params SET value = '1', default_value = '1', updated_at = datetime('now') WHERE key = ?`).run(wkey);
1088
+ console.log(`[migration Codex#100] ${wkey} value ${hp.value} → 1(提现真人铁律,历史绕过值 clamp 回)`);
1089
+ try {
1090
+ db.prepare(`INSERT INTO protocol_params_log (id, key, old_value, new_value, changed_by, action)
1091
+ VALUES (?,?,?,?,?,'migrate')`).run(generateId('ppl'), wkey, hp.value, '1', 'migration_Codex-100');
1092
+ }
1093
+ catch { }
1094
+ }
1095
+ }
1096
+ catch (e) {
1097
+ console.error('[migration require_human_presence_for_withdraw lock]', e);
1098
+ }
958
1099
  // Wave G-2: USDC ↔ WAZ 转换助手
959
1100
  function usdcToWaz(usdc) {
960
1101
  const rate = getProtocolParam('waz_usdc_rate', 1.0);
@@ -982,6 +1123,10 @@ function getProtocolParam(key, fallback) {
982
1123
  return fallback;
983
1124
  }
984
1125
  }
1126
+ // PR-F0: 人工铁律 gate(consumeGateToken / requireHumanPresence)抽到 ./human-presence.ts 以便单测
1127
+ // (behavior-zero)。必须在【首个使用点】(下方 arbitrate/vote/claim-verify/webauthn 等路由注册)之前
1128
+ // 实例化 —— 故置于 db + getProtocolParam 定义之后。原为 hoisted 函数声明,现为工厂返回的 const。
1129
+ const { consumeGateToken, requireHumanPresence } = createHumanPresence(db, getProtocolParam);
985
1130
  // Wave E-4 audit P1-3: 平台奖励发放审计 — 记录所有 platform → user 的免费 WAZ 拨付
986
1131
  db.exec(`
987
1132
  CREATE TABLE IF NOT EXISTS platform_reward_log (
@@ -3338,6 +3483,23 @@ function resolveUserRef(raw) {
3338
3483
  }
3339
3484
  return null;
3340
3485
  }
3486
+ // Invite-code-ONLY resolver — for registration sponsor + /i short links. Accepts a 6-7 char permanent_code
3487
+ // with an optional -L/-R side suffix; rejects usr_xxx / @handle / bare handle (anti-ambiguity, narrows the
3488
+ // public invite surface). Excludes sys_protocol + the internal auditor. Distinct from resolveUserRef, which
3489
+ // stays for personal-page / non-invite lookups.
3490
+ function resolveInviteCodeRef(raw) {
3491
+ if (!raw || typeof raw !== 'string')
3492
+ return null;
3493
+ const m = raw.trim().match(/^([A-Za-z0-9]{6,7})(?:-([LRlr]))?$/);
3494
+ if (!m)
3495
+ return null;
3496
+ const code = m[1].toUpperCase();
3497
+ const side = m[2] ? (m[2].toLowerCase() === 'l' ? 'left' : 'right') : null;
3498
+ const r = db.prepare("SELECT id FROM users WHERE permanent_code = ? AND id NOT IN ('sys_protocol', ?) LIMIT 1").get(code, INTERNAL_AUDITOR_ID);
3499
+ if (!r)
3500
+ return null;
3501
+ return { userId: r.id, code, side };
3502
+ }
3341
3503
  // 一次性回填:现有用户补齐 permanent_code + handle(启动时跑)
3342
3504
  try {
3343
3505
  const rows = db.prepare("SELECT id, name FROM users WHERE permanent_code IS NULL OR handle IS NULL").all();
@@ -5687,13 +5849,19 @@ function isTrustedRole(user) {
5687
5849
  // #1013 Phase 118: register 已迁出(VALID_REGIONS 在下方 const → 用 getter 避免 TDZ)
5688
5850
  registerAuthRegisterRoutes(app, {
5689
5851
  db, errorRes, INTERNAL_AUDITOR_ID,
5690
- isAllowedSponsor, resolveUserRef,
5852
+ isAllowedSponsor, resolveUserRef, resolveInviteCodeRef,
5691
5853
  generateId, generateSecureKey, generatePermanentCode, deriveHandle,
5692
5854
  clientIpHash, clientUaHash,
5693
5855
  get VALID_REGIONS() { return VALID_REGIONS; },
5694
5856
  pickPreferredSide, joinPowerLeg,
5695
5857
  get INVITE_ROTATION_HANDLES() { return INVITE_ROTATION_HANDLES; },
5696
5858
  inviteRotationLookup,
5859
+ // 邮箱验证优先注册 — issueCode/findActiveCode 是 hoisted 函数声明、isVerificationEmailReady/
5860
+ // emailDeliveryNotConfigured 是 import,均可在此安全引用;CODE_TTL_MIN/MAX_CODE_ATTEMPTS 是后置 const,
5861
+ // 走 getter 延迟读避免 TDZ。
5862
+ issueCode, findActiveCode, canDeliverCodes: isVerificationEmailReady, emailDeliveryNotConfigured,
5863
+ get CODE_TTL_MIN() { return CODE_TTL_MIN; },
5864
+ get MAX_CODE_ATTEMPTS() { return MAX_CODE_ATTEMPTS; },
5697
5865
  recordSession, broadcastSystemEvent,
5698
5866
  });
5699
5867
  // #1013 Phase 116: me + profile 已迁出
@@ -5718,8 +5886,28 @@ registerBuildTasksRoutes(app, {
5718
5886
  db, auth,
5719
5887
  requireSupportAdmin: (req, res) => requireAdminPermission(req, res, 'support'),
5720
5888
  });
5889
+ // PR9C-1 — public Task Board read surface(无需登录;仅 audience=public + status=open;只读,带 value_boundary)
5890
+ registerPublicBuildTasksRoutes(app, { db, errorRes });
5891
+ // Task Proposal Inbox v1 — public submit(匿名,validated,限流+去重)+ admin review;建议入收件箱,绝不自动成正式任务/上公开板
5892
+ const proposalRateLimiter = createSlidingWindowLimiter(20, 3600_000); // 20 submissions / hour / IP
5893
+ registerTaskProposalsRoutes(app, {
5894
+ db, errorRes,
5895
+ requireSupportAdmin: (req, res) => requireAdminPermission(req, res, 'support'),
5896
+ rateLimitOk: (key) => proposalRateLimiter(key),
5897
+ });
5721
5898
  // RFC-006 Gap 2:贡献者自查看板(build_reputation 独立池)
5722
5899
  registerBuildReputationRoutes(app, { db, auth });
5900
+ // PR-F3c — 最小 GitHub 身份认领 API(发起挑战 → Passkey 人门 + WebAZ 自验 gist → F2 原子认领)。
5901
+ // GitHub 读 token 仅来自可信服务端配置;未配置则 completion fail-closed(不做匿名限流读)。
5902
+ registerContributionIdentityRoutes(app, {
5903
+ auth,
5904
+ requireHumanPresence,
5905
+ errorRes,
5906
+ getGithubReadToken: () => process.env.GITHUB_CONTRIB_READ_TOKEN || undefined,
5907
+ });
5908
+ // PR5F — Contribution Score v1 evidence READ surface (logged-in self-view; read-only, no score).
5909
+ // Returns the caller's OWN component evidence wrapped in the PR5A uncommitted-value boundary.
5910
+ registerContributionScoreRoutes(app, { auth, errorRes });
5723
5911
  // #1013 Phase 48: 3 auth/sessions endpoints 已迁出到 routes/auth-sessions.ts
5724
5912
  registerAuthSessionsRoutes(app, { db, auth, verifyPassword, recordSession, generateSecureKey });
5725
5913
  // 个人资料:查看 API Key + 联系方式
@@ -5851,23 +6039,30 @@ registerAuthLoginRoutes(app, {
5851
6039
  const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
5852
6040
  const CODE_TTL_MIN = 10;
5853
6041
  const MAX_CODE_ATTEMPTS = 5;
5854
- const IS_DEV = process.env.NODE_ENV !== 'production';
5855
6042
  function genCode() {
5856
6043
  return String(Math.floor(100000 + Math.random() * 900000));
5857
6044
  }
5858
- function deliverCode(target, code, purpose) {
5859
- // dev: console;prod: SMTP / SMS(P3)
5860
- console.log(`[verify] ${purpose} → ${target} code=${code} (expires ${CODE_TTL_MIN}min)`);
6045
+ async function deliverCode(target, code, purpose) {
6046
+ return deliverVerificationCode({ target, code, purpose, ttlMin: CODE_TTL_MIN });
5861
6047
  }
5862
- function issueCode(userId, channel, target, purpose) {
6048
+ async function issueCode(userId, channel, target, purpose) {
6049
+ if (channel === 'email' && !isVerificationEmailReady())
6050
+ return emailDeliveryNotConfigured();
5863
6051
  const id = generateId('vcode');
5864
6052
  const code = genCode();
5865
6053
  const expiresAt = new Date(Date.now() + CODE_TTL_MIN * 60_000).toISOString();
5866
6054
  db.prepare(`INSERT INTO verification_codes (id, user_id, channel, target, code, purpose, expires_at)
5867
6055
  VALUES (?,?,?,?,?,?,?)`)
5868
6056
  .run(id, userId, channel, target, code, purpose, expiresAt);
5869
- deliverCode(target, code, purpose);
5870
- return { code, expires_at: expiresAt };
6057
+ const delivered = await deliverCode(target, code, purpose);
6058
+ if (!delivered.ok) {
6059
+ try {
6060
+ db.prepare("UPDATE verification_codes SET used_at = datetime('now') WHERE id = ?").run(id);
6061
+ }
6062
+ catch { }
6063
+ return delivered;
6064
+ }
6065
+ return { ok: true, code, expires_at: expiresAt, provider: delivered.provider };
5871
6066
  }
5872
6067
  function findActiveCode(channel, target, purpose) {
5873
6068
  return db.prepare(`
@@ -5880,12 +6075,13 @@ function findActiveCode(channel, target, purpose) {
5880
6075
  // #1013 Phase 49: 3 recover-key endpoints 已迁出到 routes/recover-key.ts
5881
6076
  registerRecoverKeyRoutes(app, {
5882
6077
  db, internalAuditorId: INTERNAL_AUDITOR_ID,
5883
- issueCode, findActiveCode, CODE_TTL_MIN, MAX_CODE_ATTEMPTS,
6078
+ issueCode, findActiveCode, canDeliverCodes: isVerificationEmailReady,
6079
+ emailDeliveryNotConfigured, hashPassword, CODE_TTL_MIN, MAX_CODE_ATTEMPTS,
5884
6080
  });
5885
6081
  // #1013 Phase 55: 5 profile-credentials endpoints 已迁出到 routes/profile-credentials.ts
5886
6082
  registerProfileCredentialsRoutes(app, {
5887
6083
  db, auth, verifyPassword, hashPassword,
5888
- issueCode, findActiveCode, IS_DEV, MAX_CODE_ATTEMPTS,
6084
+ issueCode, findActiveCode, MAX_CODE_ATTEMPTS,
5889
6085
  });
5890
6086
  // 搜索商品(声誉权重排序)
5891
6087
  // 构建 agent_summary:一句话决策摘要
@@ -6116,6 +6312,7 @@ registerFlashSalesRoutes(app, { db, generateId, auth, broadcastSystemEvent });
6116
6312
  registerTrialRoutes(app, {
6117
6313
  db, generateId, auth, clientIpHash, clientUaHash,
6118
6314
  requireProtocolAdmin: (req, res) => requireAdminPermission(req, res, 'protocol'),
6315
+ logAdminAction,
6119
6316
  });
6120
6317
  // ─── Wave B-2: 预售 / waitlist ─────────────────────────────
6121
6318
  // #1013 Phase 24: 5 endpoints 已迁出到 routes/waitlist.ts
@@ -6537,6 +6734,7 @@ registerAdminOpsRoutes(app, {
6537
6734
  hasAdminPermission,
6538
6735
  INTERNAL_AUDITOR_ID, ADMIN_EXPORT_LIMIT, csvEscapeAdmin,
6539
6736
  anthropic, applyDecayIfDue, computeValueBadges,
6737
+ logAdminAction,
6540
6738
  });
6541
6739
  // AI 2 endpoints — Phase 100 已迁出
6542
6740
  registerAiRoutes(app, { db, auth, anthropic });
@@ -6566,6 +6764,7 @@ registerAdminModerationRoutes(app, {
6566
6764
  db, generateId,
6567
6765
  requireUsersAdmin: (req, res) => requireAdminPermission(req, res, 'users'),
6568
6766
  authFailures, INTERNAL_AUDITOR_ID, broadcastSystemEvent,
6767
+ logAdminAction,
6569
6768
  });
6570
6769
  // 邀请码 3 endpoints — Phase 98 已迁出
6571
6770
  registerReferralRoutes(app, {
@@ -7037,9 +7236,9 @@ setInterval(() => {
7037
7236
  }, 24 * 60 * 60_000);
7038
7237
  // 2026-05-24 #980:测评免单 reach 评估 cron — 每 6h 跑一次
7039
7238
  // #1013 Phase 3: evaluateTrialClaims + /api/admin/trial/run-eval 已迁出到 routes/trial.ts;这里只挂 cron
7040
- setInterval(() => {
7239
+ setInterval(async () => {
7041
7240
  try {
7042
- const r = evaluateTrialClaims(db, generateId);
7241
+ const r = await evaluateTrialClaims(db, generateId);
7043
7242
  if (r.evaluated > 0)
7044
7243
  console.log(`[cron trial-eval] evaluated=${r.evaluated} refunded=${r.refunded} expired=${r.expired}`);
7045
7244
  }
@@ -7063,6 +7262,7 @@ setInterval(() => {
7063
7262
  registerAdminAtomicRoutes(app, {
7064
7263
  requireProtocolAdmin: (req, res) => requireAdminPermission(req, res, 'protocol'),
7065
7264
  processPvLedger, runBinarySettlement, executeSafeSettlementCron,
7265
+ logAdminAction,
7066
7266
  });
7067
7267
  // #1013 Phase 110: tokenomics/status + shares/dashboard 已迁出
7068
7268
  registerDashboardsRoutes(app, { db, auth });
@@ -7070,7 +7270,7 @@ registerDashboardsRoutes(app, { db, auth });
7070
7270
  // #1013 Phase 56: 3 placement endpoints 已迁出到 routes/profile-placement.ts
7071
7271
  registerProfilePlacementRoutes(app, {
7072
7272
  db, auth, internalAuditorId: INTERNAL_AUDITOR_ID,
7073
- resolveUserRef, pickPreferredSide, joinPowerLeg,
7273
+ resolveUserRef, resolveInviteCodeRef, pickPreferredSide, joinPowerLeg,
7074
7274
  });
7075
7275
  // shares/dashboard — Phase 110 已迁出
7076
7276
  // ─── 推土机轨道:推广统计端点 ─────────────────────────────────
@@ -7098,6 +7298,7 @@ registerAdminUsersQueryRoutes(app, {
7098
7298
  adminCanOperateOn, isRootAdmin, isAllowedSponsor,
7099
7299
  maskApiKey, computeLightTags, getAdminScope, getSellerDailyLimit, todayStartISO,
7100
7300
  broadcastSystemEvent, INTERNAL_AUDITOR_ID,
7301
+ logAdminAction,
7101
7302
  });
7102
7303
  function getSellerDailyLimit(user) {
7103
7304
  const id = String(user.id ?? '');
@@ -7364,12 +7565,12 @@ registerRewardsApplyRoutes(app, {
7364
7565
  });
7365
7566
  // task #1093 stage 5: admin manual auto-deactivate sweep trigger
7366
7567
  // Useful for ops + testing. The scheduled cron also runs every N hours.
7367
- app.post('/api/admin/governance/run-auto-deactivate', (req, res) => {
7568
+ app.post('/api/admin/governance/run-auto-deactivate', async (req, res) => {
7368
7569
  const admin = requireAdminPermission(req, res, 'arbitration');
7369
7570
  if (!admin)
7370
7571
  return;
7371
7572
  try {
7372
- const result = runAutoDeactivateSweep({ db, generateId, getProtocolParam });
7573
+ const result = await runAutoDeactivateSweep({ db, generateId, getProtocolParam });
7373
7574
  logAdminAction(admin.id, 'governance_auto_deactivate_sweep', null, null, {
7374
7575
  scanned: result.scanned,
7375
7576
  deactivated_count: result.deactivated.length,
@@ -7530,7 +7731,7 @@ registerOrdersCreateRoutes(app, {
7530
7731
  getActiveFlashSale, applyCouponToOrder, getProtocolParam,
7531
7732
  getProductShareChain, isAllowedSponsor, checkStockAndMaybeDelist, auditSponsorChainCross,
7532
7733
  appendOrderEvent, transition, notifyTransition, shouldAutoAccept, ensureCharityRep,
7533
- broadcastSystemEvent,
7734
+ broadcastSystemEvent, resolveInviteCodeRef,
7534
7735
  signPassport: (message) => privateKeyToAccount(derivePrivKey('platform-hot-wallet')).signMessage({ message }),
7535
7736
  issuerAddress: () => privateKeyToAddress(derivePrivKey('platform-hot-wallet')),
7536
7737
  });
@@ -7954,6 +8155,7 @@ registerAuctionRoutes(app, {
7954
8155
  RFQ_MAX_QTY, RFQ_MAX_PRICE,
7955
8156
  LISTING_CATEGORIES, isListingCategoryKey,
7956
8157
  requireProtocolAdmin: (req, res) => requireAdminPermission(req, res, 'protocol'),
8158
+ logAdminAction,
7957
8159
  });
7958
8160
  // AUC 结算 helper:到期 → 最高 active bid → 建单(或流拍)
7959
8161
  // P0 audit fix #2:顶层 try/catch 兜底,意外抛错时 status='error' 防 cron 死循环
@@ -9336,69 +9538,8 @@ registerWebauthnRoutes(app, {
9336
9538
  invalidateAgentRiskCacheForUser,
9337
9539
  requireHumanPresence, // #1044 — DELETE passkey 自身需 token
9338
9540
  });
9339
- // 验证 gate token:被业务端点(如 /api/wallet/withdraw)消费
9340
- // M-1: CAS 先抢占性 UPDATE,只有 changes=1 才认为本次成功消费;
9341
- // 然后再读 row 校验 user/purpose/业务字段。多副本部署也安全。
9342
- function consumeGateToken(userId, token, purpose, validate) {
9343
- if (!token)
9344
- return { ok: false, reason: '缺少 X-WebAuthn-Token' };
9345
- // 先抢占:未消费 + 未过期 才能 mark consumed
9346
- const claim = db.prepare(`UPDATE webauthn_gate_tokens
9347
- SET consumed_at = datetime('now')
9348
- WHERE id = ? AND consumed_at IS NULL AND expires_at > datetime('now')`).run(token);
9349
- if (claim.changes !== 1) {
9350
- // 抢占失败的两种原因区分(仅用于 reason 文案)
9351
- const exist = db.prepare('SELECT consumed_at FROM webauthn_gate_tokens WHERE id = ?').get(token);
9352
- if (!exist)
9353
- return { ok: false, reason: 'token 不存在' };
9354
- if (exist.consumed_at)
9355
- return { ok: false, reason: 'token 已使用' };
9356
- return { ok: false, reason: 'token 已过期' };
9357
- }
9358
- // 已抢占 → 读 row 校验 user/purpose/业务字段;若校验失败 token 仍然作废(防止枚举攻击下的重试)
9359
- const row = db.prepare(`SELECT user_id, purpose, purpose_data FROM webauthn_gate_tokens WHERE id = ?`)
9360
- .get(token);
9361
- if (row.user_id !== userId)
9362
- return { ok: false, reason: 'token 用户不匹配' };
9363
- if (row.purpose !== purpose)
9364
- return { ok: false, reason: 'token 用途不匹配' };
9365
- let data = null;
9366
- try {
9367
- data = row.purpose_data ? JSON.parse(row.purpose_data) : null;
9368
- }
9369
- catch { }
9370
- if (!validate(data))
9371
- return { ok: false, reason: 'token 业务参数不匹配' };
9372
- return { ok: true };
9373
- }
9374
- // ─── 2026-05-23 Agent 治理铁律:人工铁律节点 ───────────────────
9375
- // spec: docs/AGENT-GOVERNANCE.md §4
9376
- // 关键节点(verifier 投票 / arbitrator 仲裁)必须真实人工参与,agent 代操作要被拦截。
9377
- // 实现:要求 webauthn_gate_token(一次性消费 · 60s 内有效)+ 协议参数开关。
9378
- // 2026-05-25 #1006:默认值 0 → 1 启用强制;is_system fixture 旁路只对 vote/arbitrate 生效。
9379
- // agent_revoke 是普通用户操作,不享有 is_system 豁免(无 fixture 概念)。
9380
- function requireHumanPresence(userId, purpose, token, paramKey, validate = () => true) {
9381
- const enabled = Number(getProtocolParam(paramKey, 1)) === 1;
9382
- if (!enabled)
9383
- return { ok: true }; // 协议参数关闭 → 不强制
9384
- // is_system fixture 旁路:用于 E2E 测试 / 自动化 / migration 账号
9385
- // 仅 vote / arbitrate 适用(这两类有白名单表 + is_system 列);真实用户无此豁免
9386
- if (purpose === 'vote') {
9387
- const wl = db.prepare('SELECT is_system FROM verifier_whitelist WHERE user_id = ?').get(userId);
9388
- if (wl?.is_system === 1)
9389
- return { ok: true };
9390
- }
9391
- else if (purpose === 'arbitrate') {
9392
- const wl = db.prepare('SELECT is_system FROM arbitrator_whitelist WHERE user_id = ?').get(userId);
9393
- if (wl?.is_system === 1)
9394
- return { ok: true };
9395
- }
9396
- const result = consumeGateToken(userId, token, purpose, validate);
9397
- if (!result.ok) {
9398
- return { ok: false, error_code: 'HUMAN_PRESENCE_REQUIRED', reason: result.reason || '此操作需真实人工 WebAuthn 验证', required_when_enabled: true };
9399
- }
9400
- return { ok: true };
9401
- }
9541
+ // consumeGateToken / requireHumanPresence 已抽出到 ./human-presence.ts(PR-F0,behavior-zero,
9542
+ // 工厂 createHumanPresence(db, getProtocolParam),在 db+getProtocolParam 定义后即实例化 —— 见上方)。
9402
9543
  // ─── M7.3 claim 验证任务系统 ──────────────────────────────
9403
9544
  // #1013 Phase 9: 8 endpoints + 三路径结算 + outlier strike + 铁律 §4 已迁出到 routes/claim-verify.ts
9404
9545
  // product_claim_tasks (Sprint 1) 跨域用 isEligibleClaimVerifier — 从 import 拿
@@ -9706,7 +9847,8 @@ registerReviewsRoutes(app, {
9706
9847
  // #1013 Phase 76: 3 垂类 × 2 (POST claim + GET claims) = 6 endpoints 已迁出
9707
9848
  registerClaimInitiatorsRoutes(app, { db, auth, isTrustedRole, errorRes, generateId });
9708
9849
  // ─── 分享 / 重定向 / QR (#1013 Phase 54) ────────────────────
9709
- registerShareRedirectsRoutes(app, { db, auth, clientIpHash, clientUaHash });
9850
+ registerShareRedirectsRoutes(app, { db, auth, clientIpHash, clientUaHash, resolveInviteCodeRef });
9851
+ registerShopReferralRoutes(app, { db, auth, errorRes, internalAuditorId: INTERNAL_AUDITOR_ID, resolveUserRef, resolveInviteCodeRef });
9710
9852
  // 慈善许愿池 API
9711
9853
  // ============================================================
9712
9854
  // ─── 慈善许愿池 (charity) ─────────────────────────────────
@@ -9735,6 +9877,23 @@ registerAdminWalletOpsRoutes(app, {
9735
9877
  getIsMainnet: () => IS_MAINNET,
9736
9878
  getNetwork: () => NETWORK,
9737
9879
  executeWithdrawal: (id) => executeWithdrawal(id),
9880
+ logAdminAction,
9881
+ // dual-accept transition for attribution(非最终安全收紧):只读、不响应地解析登录的 protocol-admin。
9882
+ // 用 resolveBearerProtocolAdmin(钱路强校验):仅认 Authorization: Bearer(不认 req.body.api_key)、
9883
+ // 拒暂停用户、拒已吊销会话;角色+protocol 权限用中央 hasAdminPermission(防漂移)。null → 回落共享 ADMIN_KEY。
9884
+ // 最终弃用 x-admin-key 留后续 PR。
9885
+ resolveProtocolAdminSoft: (req) => resolveBearerProtocolAdmin(db, req, (u) => {
9886
+ let rolesList = [];
9887
+ try {
9888
+ rolesList = JSON.parse(u.roles || '[]');
9889
+ }
9890
+ catch {
9891
+ rolesList = [];
9892
+ }
9893
+ if (u.role !== 'admin' && !rolesList.includes('admin'))
9894
+ return false;
9895
+ return hasAdminPermission(u, 'protocol');
9896
+ }),
9738
9897
  });
9739
9898
  // #1013 Phase 80: 10 wallet read endpoints 已迁出
9740
9899
  registerWalletReadRoutes(app, {
@@ -9824,14 +9983,11 @@ function runEnforcement() {
9824
9983
  console.error('证据清理失败:', e.message);
9825
9984
  }
9826
9985
  // Phase C 笔记图片孤儿 cleanup — 没有任何笔记引用且超过 1 小时 grace 期的 blob
9827
- try {
9828
- const npCleaned = cleanupOrphanNotePhotos(db);
9829
- if (npCleaned.swept > 0)
9830
- console.log(`⚡ Note photo cleanup × ${npCleaned.swept} files (${Math.round(npCleaned.bytes / 1024)}KB)`);
9831
- }
9832
- catch (e) {
9833
- console.error('笔记图片清理失败:', e.message);
9834
- }
9986
+ // RFC-016:cleanupOrphanNotePhotos 已异步(纯读 + fs 删);best-effort 孤儿清理,fire-and-forget 不阻塞同步 cron runEnforcement。
9987
+ cleanupOrphanNotePhotos(db)
9988
+ .then(npCleaned => { if (npCleaned.swept > 0)
9989
+ console.log(`⚡ Note photo cleanup × ${npCleaned.swept} files (${Math.round(npCleaned.bytes / 1024)}KB)`); })
9990
+ .catch(e => console.error('笔记图片清理失败:', e.message));
9835
9991
  // E1 流量口令 reclaim — retired 满 365 天 → reclaimable(namespace 释放)
9836
9992
  try {
9837
9993
  const r = reclaimRetiredAnchors(db);
@@ -10223,6 +10379,21 @@ db.exec(`
10223
10379
  VALUES (?, ?, 'major', ?, ?, ?, ?)`)
10224
10380
  .run('1.0', hash, Date.now(), textZh, textEn, 'Initial v1.0 lock — placeholder canonical text pointing to RFC-002');
10225
10381
  })();
10382
+ (function seedConsentV11() {
10383
+ const existing = db.prepare("SELECT version FROM rewards_consent_texts WHERE version = '1.1'").get();
10384
+ if (existing)
10385
+ return;
10386
+ const textZh = 'WebAZ 分享分润开通(rewards opt-in) v1.1 — 由 RFC-002 §3.3 / §3.10 定义。本同意仅用于记录分享分润相关的经济关系:Passkey 真人签名、推荐关系/左右区位置、佣金/PV/escrow 结算规则。本流程不是购物流程,也不是共建贡献资格;不影响贡献任务、GitHub 贡献认领或普通下单。佣金层级按地区合规配置生效;当前预发布期全局上限为 1 级,“三级”仅为协议最大设计。你可以随时退出,退出不影响已下单或未来订单;已发生的订单和结算按当时有效规则处理。';
10387
+ const textEn = 'WebAZ share-commission opt-in (rewards opt-in) v1.1 — defined by RFC-002 §3.3 / §3.10. This consent only records the economic relationship for share commission: Passkey-signed proof of personhood, referral relationship / left-right placement, and commission / PV / escrow settlement rules. This is not a shopping flow and not contribution eligibility; it does not affect contribution tasks, GitHub contribution claims, or normal orders. Commission levels follow per-region compliance configuration; during pre-launch the global cap is 1 level, and “three tiers” is only the protocol maximum design. You may leave at any time without affecting past or future orders; already-created orders and settlements follow the rules effective at that time.';
10388
+ const hash = createHash('sha256').update(textZh + '\n---\n' + textEn).digest('hex');
10389
+ // effective_at must be strictly later than v1.0's so "latest major" deterministically resolves to v1.1
10390
+ // even on a fresh DB that seeds both rows in the same boot (avoids a same-ms ORDER BY tie).
10391
+ const v10 = db.prepare("SELECT effective_at FROM rewards_consent_texts WHERE version = '1.0'").get();
10392
+ const effectiveAt = Math.max(Date.now(), (v10?.effective_at ?? 0) + 1);
10393
+ db.prepare(`INSERT INTO rewards_consent_texts (version, hash, change_class, effective_at, text_zh, text_en, changelog)
10394
+ VALUES (?, ?, 'major', ?, ?, ?, ?)`)
10395
+ .run('1.1', hash, effectiveAt, textZh, textEn, 'v1.1 clarification — share-commission opt-in framing (not 共建身份/Builder Identity, not contribution eligibility) + current commission-level reality boundary (pre-launch cap 1 level); v1.0 left frozen');
10396
+ })();
10226
10397
  // 4. rewards_applications:申请留痕表(append-only audit;action='activate'|'deactivate'|'auto_downgrade'|'reconfirm')
10227
10398
  db.exec(`
10228
10399
  CREATE TABLE IF NOT EXISTS rewards_applications (
@@ -10314,6 +10485,49 @@ try {
10314
10485
  db.exec('CREATE UNIQUE INDEX IF NOT EXISTS uniq_escrow_recipient_order_path ON pending_commission_escrow(recipient_user_id, order_id, attribution_path)');
10315
10486
  }
10316
10487
  catch { }
10488
+ // Codex #69 P1:pv_escrow_reserve(#1106 隔离负债账)回填历史 pending pv_pair escrow —— 按 delta 对账,不全量转。
10489
+ // 该列在 global_fund 上新增(line ~2319);#1106 之后新建的 pv_pair escrow 结算时【已】pv_escrow_reserve += wazAmount,
10490
+ // 但加列【之前】产生的 pending pv_pair 从没进过 reserve。升级窗口里两者混存。
10491
+ // ⚠️ 不能"全量 SUM(pending pv_pair) 再转":会把已隔离的新 escrow 二次扣 pool。
10492
+ // 正确做法:reserve 的目标值 = 当前所有 pending pv_pair 负债;只补差额 delta = liability - currentReserve 的正数部分。
10493
+ // · delta > 0:pool -= delta, reserve += delta(纯转账,total 不变);pool 不足则记 shortfall 风险项(基于 delta)。
10494
+ // · delta <= 0:已对齐/超额,不反向移动(避免误伤业务流);currentReserve > liability 记 anomaly 供核账。
10495
+ // 幂等(system_state 标志)。放在 pending_commission_escrow 建表/迁移之后(ALTER-after-CREATE 铁律)。
10496
+ try {
10497
+ db.exec("CREATE TABLE IF NOT EXISTS system_state (key TEXT PRIMARY KEY, value TEXT)");
10498
+ const done = db.prepare("SELECT value FROM system_state WHERE key = 'pv_escrow_reserve_backfilled'").get();
10499
+ if (!done) {
10500
+ db.transaction(() => {
10501
+ const liability = db.prepare(`SELECT COALESCE(SUM(amount),0) AS s FROM pending_commission_escrow WHERE status='pending' AND attribution_path='pv_pair'`).get().s;
10502
+ const gf = db.prepare("SELECT pool_balance, pv_escrow_reserve FROM global_fund WHERE id=1").get();
10503
+ const pool = gf?.pool_balance ?? 0;
10504
+ const currentReserve = gf?.pv_escrow_reserve ?? 0;
10505
+ const delta = Math.round((liability - currentReserve) * 100) / 100; // 只补"尚未隔离"的部分
10506
+ if (delta > 0) {
10507
+ db.prepare("UPDATE global_fund SET pv_escrow_reserve = pv_escrow_reserve + ?, pool_balance = pool_balance - ? WHERE id=1").run(delta, delta);
10508
+ console.log(`[migration pv_escrow_reserve backfill] 历史未隔离 pv_pair 负债 delta=${delta}(liability ${liability} - reserve ${currentReserve})从 pool 移入 reserve(pool ${pool}→${pool - delta})`);
10509
+ if (pool < delta) {
10510
+ const shortfall = Math.round((delta - pool) * 100) / 100;
10511
+ console.error(`[migration pv_escrow_reserve backfill] ⚠️ pool_balance(${pool}) < 待回填 delta(${delta});pool 已为负,缺口 ${shortfall} 需人工核账`);
10512
+ db.prepare("INSERT OR REPLACE INTO system_state (key, value) VALUES ('pv_escrow_reserve_backfill_shortfall', ?)").run(String(shortfall));
10513
+ }
10514
+ }
10515
+ else if (delta < 0) {
10516
+ // reserve 比 pending 负债还多 —— 不反向移动(避免误伤),只记异常供 admin 核账
10517
+ const anomaly = Math.round((currentReserve - liability) * 100) / 100;
10518
+ console.error(`[migration pv_escrow_reserve backfill] ⚠️ pv_escrow_reserve(${currentReserve}) > pending pv_pair 负债(${liability}),超额 ${anomaly};不反向移动,记 anomaly 供核账`);
10519
+ db.prepare("INSERT OR REPLACE INTO system_state (key, value) VALUES ('pv_escrow_reserve_backfill_anomaly', ?)").run(String(anomaly));
10520
+ }
10521
+ else {
10522
+ console.log(`[migration pv_escrow_reserve backfill] reserve(${currentReserve})已等于 pending pv_pair 负债(${liability}),无需回填`);
10523
+ }
10524
+ db.prepare("INSERT OR REPLACE INTO system_state (key, value) VALUES ('pv_escrow_reserve_backfilled', '1')").run();
10525
+ })();
10526
+ }
10527
+ }
10528
+ catch (e) {
10529
+ console.error('[migration pv_escrow_reserve backfill]', e);
10530
+ }
10317
10531
  // 6. INSERT 5 个 RFC-002 protocol_params(独立 INSERT,不动 DEFAULT_PARAMS array)
10318
10532
  // 两个标 requires_meta_rule_change=1:require_passkey + consent_delay_seconds(P0-4 闭环)
10319
10533
  const RFC002_PARAMS = [