@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.
- package/README.md +5 -1
- package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
- package/dist/layer0-foundation/L0-1-database/db.js +65 -0
- package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
- package/dist/layer1-agent/L1-1-mcp-server/server.js +288 -208
- package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
- package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
- package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
- package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
- package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +182 -0
- package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
- package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
- package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +11 -3
- package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
- package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
- package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
- package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
- package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
- package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
- package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
- package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +191 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
- package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
- package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
- package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
- package/dist/pwa/acp-feed.js +13 -1
- package/dist/pwa/admin-bearer-auth.js +21 -0
- package/dist/pwa/contract-fingerprint.js +2 -0
- package/dist/pwa/email-delivery.js +127 -0
- package/dist/pwa/endpoint-actions.js +5 -1
- package/dist/pwa/goal-index.js +8 -8
- package/dist/pwa/human-presence.js +62 -0
- package/dist/pwa/public/app.js +1485 -283
- package/dist/pwa/public/i18n.js +297 -59
- package/dist/pwa/public/index.html +1 -0
- package/dist/pwa/public/openapi.json +5 -5
- package/dist/pwa/public/whitepaper/en/index.html +153 -0
- package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
- package/dist/pwa/rate-limit.js +22 -0
- package/dist/pwa/routes/account-deletion.js +15 -13
- package/dist/pwa/routes/addresses.js +10 -9
- package/dist/pwa/routes/admin-admins.js +13 -14
- package/dist/pwa/routes/admin-analytics.js +109 -69
- package/dist/pwa/routes/admin-atomic.js +10 -4
- package/dist/pwa/routes/admin-catalog.js +13 -11
- package/dist/pwa/routes/admin-editor-picks.js +15 -10
- package/dist/pwa/routes/admin-events.js +5 -3
- package/dist/pwa/routes/admin-health.js +2 -1
- package/dist/pwa/routes/admin-moderation.js +50 -29
- package/dist/pwa/routes/admin-ops.js +35 -23
- package/dist/pwa/routes/admin-protocol-params.js +16 -19
- package/dist/pwa/routes/admin-reports.js +23 -21
- package/dist/pwa/routes/admin-tokenomics.js +26 -25
- package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
- package/dist/pwa/routes/admin-users-query.js +65 -53
- package/dist/pwa/routes/admin-verifier-flow.js +82 -41
- package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
- package/dist/pwa/routes/admin-wallet-ops.js +32 -7
- package/dist/pwa/routes/agent-buy.js +46 -22
- package/dist/pwa/routes/agent-governance.js +52 -56
- package/dist/pwa/routes/ai.js +7 -5
- package/dist/pwa/routes/analytics.js +43 -41
- package/dist/pwa/routes/anchors.js +19 -20
- package/dist/pwa/routes/announcements.js +13 -13
- package/dist/pwa/routes/arbitrator.js +97 -31
- package/dist/pwa/routes/auction.js +157 -116
- package/dist/pwa/routes/auth-login.js +6 -4
- package/dist/pwa/routes/auth-read.js +21 -10
- package/dist/pwa/routes/auth-register.js +111 -26
- package/dist/pwa/routes/auth-sessions.js +12 -11
- package/dist/pwa/routes/blocklist.js +16 -15
- package/dist/pwa/routes/build-feedback.js +10 -9
- package/dist/pwa/routes/build-reputation.js +6 -2
- package/dist/pwa/routes/build-tasks.js +45 -13
- package/dist/pwa/routes/buyer-feeds.js +27 -25
- package/dist/pwa/routes/cart.js +16 -15
- package/dist/pwa/routes/charity.js +212 -150
- package/dist/pwa/routes/chat.js +42 -43
- package/dist/pwa/routes/checkin-tasks.js +10 -9
- package/dist/pwa/routes/checkout-helpers.js +12 -10
- package/dist/pwa/routes/claim-initiators.js +34 -14
- package/dist/pwa/routes/claim-verify.js +86 -53
- package/dist/pwa/routes/claim-voting.js +43 -18
- package/dist/pwa/routes/contribution-identity.js +164 -0
- package/dist/pwa/routes/contribution-score.js +19 -0
- package/dist/pwa/routes/coupons.js +19 -16
- package/dist/pwa/routes/dashboards.js +18 -16
- package/dist/pwa/routes/dispute-cases.js +25 -24
- package/dist/pwa/routes/disputes-read.js +45 -51
- package/dist/pwa/routes/disputes-write.js +124 -61
- package/dist/pwa/routes/evidence.js +9 -9
- package/dist/pwa/routes/external-anchors.js +13 -12
- package/dist/pwa/routes/feedback.js +29 -33
- package/dist/pwa/routes/flash-sales.js +18 -16
- package/dist/pwa/routes/follows.js +25 -24
- package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
- package/dist/pwa/routes/governance-onboarding.js +70 -59
- package/dist/pwa/routes/group-buys.js +22 -22
- package/dist/pwa/routes/growth.js +34 -31
- package/dist/pwa/routes/import-product.js +12 -10
- package/dist/pwa/routes/kyc.js +9 -8
- package/dist/pwa/routes/leaderboard.js +20 -18
- package/dist/pwa/routes/listings.js +23 -22
- package/dist/pwa/routes/logistics.js +10 -8
- package/dist/pwa/routes/manifests.js +27 -27
- package/dist/pwa/routes/me-data.js +23 -21
- package/dist/pwa/routes/notifications.js +7 -6
- package/dist/pwa/routes/offers.js +30 -12
- package/dist/pwa/routes/orders-action.js +51 -29
- package/dist/pwa/routes/orders-create.js +75 -20
- package/dist/pwa/routes/orders-read.js +21 -20
- package/dist/pwa/routes/p2p-products.js +30 -18
- package/dist/pwa/routes/payments-governance.js +61 -56
- package/dist/pwa/routes/peers.js +9 -8
- package/dist/pwa/routes/pin-receipts.js +13 -13
- package/dist/pwa/routes/products-aliases.js +12 -10
- package/dist/pwa/routes/products-claims.js +36 -17
- package/dist/pwa/routes/products-create.js +53 -38
- package/dist/pwa/routes/products-crud.js +17 -16
- package/dist/pwa/routes/products-links.js +49 -26
- package/dist/pwa/routes/products-list.js +6 -4
- package/dist/pwa/routes/products-meta.js +40 -39
- package/dist/pwa/routes/products-update.js +19 -5
- package/dist/pwa/routes/profile-credentials.js +20 -19
- package/dist/pwa/routes/profile-identity.js +14 -13
- package/dist/pwa/routes/profile-location.js +7 -6
- package/dist/pwa/routes/profile-placement.js +20 -19
- package/dist/pwa/routes/profile-prefs.js +11 -11
- package/dist/pwa/routes/promoter.js +58 -66
- package/dist/pwa/routes/public-build-tasks.js +19 -0
- package/dist/pwa/routes/public-utils.js +108 -46
- package/dist/pwa/routes/push.js +16 -15
- package/dist/pwa/routes/ratings.js +92 -32
- package/dist/pwa/routes/recover-key.js +66 -26
- package/dist/pwa/routes/referral.js +37 -52
- package/dist/pwa/routes/reputation.js +3 -2
- package/dist/pwa/routes/returns.js +76 -73
- package/dist/pwa/routes/reviews.js +41 -18
- package/dist/pwa/routes/rewards-apply.js +16 -15
- package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
- package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
- package/dist/pwa/routes/rfqs.js +163 -85
- package/dist/pwa/routes/search.js +16 -14
- package/dist/pwa/routes/secondhand.js +25 -22
- package/dist/pwa/routes/seller-quota.js +24 -26
- package/dist/pwa/routes/share-redirects.js +60 -55
- package/dist/pwa/routes/shareables-interactions.js +34 -35
- package/dist/pwa/routes/shareables.js +55 -51
- package/dist/pwa/routes/shop-referral.js +58 -0
- package/dist/pwa/routes/shops.js +25 -20
- package/dist/pwa/routes/signaling.js +10 -9
- package/dist/pwa/routes/skill-market.js +16 -16
- package/dist/pwa/routes/skills.js +15 -14
- package/dist/pwa/routes/snf.js +14 -13
- package/dist/pwa/routes/tags.js +10 -9
- package/dist/pwa/routes/task-proposals.js +121 -0
- package/dist/pwa/routes/trial.js +72 -52
- package/dist/pwa/routes/trusted-kpi.js +20 -18
- package/dist/pwa/routes/url-claim.js +67 -28
- package/dist/pwa/routes/users-public.js +62 -70
- package/dist/pwa/routes/variants.js +12 -13
- package/dist/pwa/routes/verifier-user.js +61 -21
- package/dist/pwa/routes/verify-tasks.js +49 -25
- package/dist/pwa/routes/waitlist.js +16 -15
- package/dist/pwa/routes/wallet-read.js +75 -37
- package/dist/pwa/routes/wallet-write.js +12 -9
- package/dist/pwa/routes/webauthn.js +25 -26
- package/dist/pwa/routes/webhooks.js +26 -26
- package/dist/pwa/routes/welcome.js +45 -50
- package/dist/pwa/routes/wishlist-qa.js +29 -32
- package/dist/pwa/server.js +304 -90
- package/dist/version.js +1 -1
- package/package.json +76 -3
package/dist/pwa/server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
//
|
|
9340
|
-
//
|
|
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
|
-
|
|
9828
|
-
|
|
9829
|
-
if (npCleaned.swept > 0)
|
|
9830
|
-
|
|
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 = [
|