@seasonkoh/webaz 0.1.26 → 0.1.28
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/LICENSE +2 -2
- package/NOTICE +24 -3
- package/README.md +74 -330
- package/README.zh-CN.md +419 -0
- package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +21 -0
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +8 -3
- package/dist/layer1-agent/L1-1-mcp-server/auth.js +13 -1
- package/dist/layer1-agent/L1-1-mcp-server/network-mode.js +69 -0
- package/dist/layer1-agent/L1-1-mcp-server/server.js +270 -82
- package/dist/layer2-business/L2-9-contribution/admin-coordination-ingestion-engine.js +181 -0
- package/dist/layer2-business/L2-9-contribution/admin-coordination-resolver.js +114 -0
- package/dist/layer2-business/L2-9-contribution/admin-coordination-store.js +251 -0
- package/dist/layer2-business/L2-9-contribution/admin-operator-claim-workflow.js +390 -0
- package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +24 -0
- package/dist/layer2-business/L2-9-contribution/build-task-participation.js +6 -2
- package/dist/layer2-business/L2-9-contribution/build-task-quota.js +337 -0
- package/dist/layer2-business/L2-9-contribution/build-task-read.js +25 -2
- package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +57 -7
- package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +1 -1
- package/dist/layer2-business/L2-9-contribution/contribution-facts-read.js +66 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +187 -18
- package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +29 -4
- package/dist/ledger.js +1 -1
- package/dist/pwa/admin-audit.js +38 -0
- package/dist/pwa/anti-abuse-thresholds.js +135 -0
- package/dist/pwa/cf-origin-guard.js +33 -0
- package/dist/pwa/contract-fingerprint.js +1 -0
- package/dist/pwa/data/onboarding-cases.js +2 -2
- package/dist/pwa/data/onboarding-quiz.js +1 -1
- package/dist/pwa/economic-participation.js +2 -2
- package/dist/pwa/integration-contract.js +46 -4
- package/dist/pwa/internal/pv-settlement.js +12 -0
- package/dist/pwa/internal/wallet-signer.js +26 -0
- package/dist/pwa/public/app-account.js +977 -0
- package/dist/pwa/public/app-admin.js +608 -0
- package/dist/pwa/public/app-agents.js +63 -0
- package/dist/pwa/public/app-ai.js +2162 -0
- package/dist/pwa/public/app-contribution.js +836 -0
- package/dist/pwa/public/app-discover.js +1296 -0
- package/dist/pwa/public/app-listings.js +226 -0
- package/dist/pwa/public/app-profile.js +1692 -0
- package/dist/pwa/public/app-seller.js +199 -0
- package/dist/pwa/public/app-shop.js +1145 -0
- package/dist/pwa/public/app.js +15075 -23960
- package/dist/pwa/public/i18n.js +31 -28
- package/dist/pwa/public/index.html +11 -1
- package/dist/pwa/public/openapi.json +4851 -2776
- package/dist/pwa/pv-kill-switch.js +31 -0
- package/dist/pwa/routes/admin-admins.js +48 -1
- package/dist/pwa/routes/admin-analytics.js +1 -10
- package/dist/pwa/routes/admin-atomic.js +4 -17
- package/dist/pwa/routes/admin-operator-claims.js +280 -0
- package/dist/pwa/routes/admin-reports.js +4 -26
- package/dist/pwa/routes/admin-tokenomics.js +2 -76
- package/dist/pwa/routes/admin-users-lifecycle.js +1 -14
- package/dist/pwa/routes/admin-users-query.js +23 -1
- package/dist/pwa/routes/admin-wallet-ops.js +1 -1
- package/dist/pwa/routes/agent-grants.js +255 -0
- package/dist/pwa/routes/auth-read.js +1 -5
- package/dist/pwa/routes/auth-register.js +3 -13
- package/dist/pwa/routes/build-task-quota.js +113 -0
- package/dist/pwa/routes/claim-verify.js +15 -11
- package/dist/pwa/routes/contribution-facts.js +18 -0
- package/dist/pwa/routes/dispute-cases.js +5 -4
- package/dist/pwa/routes/growth.js +3 -3
- package/dist/pwa/routes/orders-action.js +27 -10
- package/dist/pwa/routes/orders-create.js +1 -1
- package/dist/pwa/routes/products-meta.js +19 -6
- package/dist/pwa/routes/profile-placement.js +1 -1
- package/dist/pwa/routes/promoter.js +10 -29
- package/dist/pwa/routes/public-build-tasks.js +5 -1
- package/dist/pwa/routes/public-utils.js +9 -12
- package/dist/pwa/routes/referral.js +5 -26
- package/dist/pwa/routes/rewards-apply.js +3 -2
- package/dist/pwa/routes/share-redirects.js +1 -1
- package/dist/pwa/routes/shareables-interactions.js +2 -1
- package/dist/pwa/routes/task-proposals.js +85 -9
- package/dist/pwa/routes/users-public.js +1 -4
- package/dist/pwa/routes/wallet-read.js +2 -14
- package/dist/pwa/routes/webauthn.js +7 -2
- package/dist/pwa/server-schema.js +9 -0
- package/dist/pwa/server.js +319 -2034
- package/dist/runtime/agent-grant-scopes.js +128 -0
- package/dist/runtime/agent-grant-verifier.js +67 -0
- package/dist/runtime/agent-pairing.js +60 -0
- package/dist/runtime/apply-webaz-runtime-schema.js +15 -0
- package/dist/runtime/webaz-schema-helpers.js +1848 -0
- package/dist/settlement-math.js +3 -3
- package/dist/version.js +6 -4
- package/package.json +43 -8
- package/dist/index.js +0 -182
- package/dist/pwa/public/docs/ECONOMIC-MODEL.md +0 -287
- package/dist/pwa/public/docs/INTEGRATOR.md +0 -67
- package/dist/pwa/public/docs/META-RULES-FULL.md +0 -543
- package/dist/test-dispute.js +0 -153
- package/dist/test-manifest.js +0 -61
- package/dist/test-mcp-tools.js +0 -135
- package/dist/test-reputation.js +0 -116
- package/dist/test-skill-market.js +0 -101
package/dist/pwa/server.js
CHANGED
|
@@ -24,7 +24,10 @@ import { setSeamDb } from '../layer0-foundation/L0-1-database/db.js'; // RFC-016
|
|
|
24
24
|
import { initSystemUser, transition, getOrderStatus, checkTimeouts, settleFault } from '../layer0-foundation/L0-2-state-machine/engine.js';
|
|
25
25
|
import { endpointToAction, endpointToReadAction } from './endpoint-actions.js';
|
|
26
26
|
import { AGENT_RATE_PER_MIN_DEFAULTS, CROSS_USER_READ_DAILY_CAP, MASS_ACTION_TYPES, MASS_ACTION_DAILY_CAPS } from './limits.js';
|
|
27
|
+
// #420 P1-2/P1-3/P1-4 — 反滥用阈值单一真相源(governance-adjustable protocol_params)+ 纯决策函数
|
|
28
|
+
import { ANTI_ABUSE_PARAMS, readAntiAbuseThresholds, agentTrustLevel, agentSybilPenalty, agentStrikeSeverity, verifierOutlierBand } from './anti-abuse-thresholds.js';
|
|
27
29
|
import { initOrderChainSchema, appendOrderEvent, getOrderChain, verifyOrderChain } from '../layer0-foundation/L0-2-state-machine/order-chain.js';
|
|
30
|
+
import { initVerifierWhitelistSchema, initMcpToolCallsSchema, initNotePhotoIndexSchema, initUserWishlistSchema, initProductQaSchema, initCouponsSchema, initAnnouncementsSchema, initProductWaitlistSchema, initFlashSalesSchema, initPublicIdeasSchema, initAuctionRemindersSchema, initEmailSubscriptionsSchema, initFeedbackTicketsSchema, initFeedbackMessagesSchema, initDisputeCasesSchema, initDisputeCommentsSchema, initDisputeCommentRepliesSchema, initShareableCommentsSchema, initDisputeFairnessVotesSchema, initOrderRatingsSchema, initBuyerRatingsSchema, initUserAddressesSchema, initP2pShopsSchema, initShareableLikesSchema, initShareableBookmarksSchema, initShareableTagsSchema, initManifestRegistrySchema, initPeerDirectorySchema, initSignalingQueueSchema, initConversationsSchema, initMessagesSchema, initChatReportsSchema, initQuotaIncreaseApplicationsSchema, initVerifierApplicationsSchema, initArbitratorReviewSchema, initVerifierAppealsSchema, initUserModerationSchema, initAdminAuditLogSchema, initVerificationCodesSchema, initAgentCallLogSchema, initAgentReputationSchema, initAgentDeclarationsSchema, initAgentAttestationsSchema, initAgentStrikesSchema, initAgentRevocationsSchema, initProductAliasesSchema, initRegionChangeLogSchema, initCartItemsSchema, initFollowsSchema, initPushSubscriptionsSchema, initUserSessionsSchema, initUserBlocklistSchema, initImportLogsSchema, initErrorLogSchema, initSecondhandItemsSchema, initProductTrialCampaignsSchema, initProductTrialClaimsSchema, initReturnRequestsSchema, initReturnMessagesSchema, initProductVariantsSchema, initEditorPicksSchema, initKycRecordsSchema, initWebauthnSchema, initClaimVerificationBaseSchema, initClaimVerifierSuspensionsSchema, initProductClaimSchema, initReviewClaimSchema, initSecondhandClaimSchema, initAuctionClaimSchema, initWishClaimSchema, initShareableClickLogSchema, initCommissionAuditLogSchema, initRegistrationAuditLogSchema, initProductExternalLinksBaseSchema, initLinkChallengesSchema, initVerifyTasksSchema, initVerifySubmissionsSchema, initVerifierStatsSchema, initRegisterListSearchColumns } from './server-schema.js';
|
|
28
31
|
// RFC-014 PR4 — 正常成交结算走整数 base-units + allocate + 绝对值落库。
|
|
29
32
|
import { toUnits, toDecimal, mulRate, allocate } from '../money.js';
|
|
30
33
|
import { applyWalletDelta, creditColumns } from '../ledger.js';
|
|
@@ -42,7 +45,6 @@ import { initSkillSchema, shouldAutoAccept, } from '../layer4-economics/L4-4-ski
|
|
|
42
45
|
import { initReputationSchema, recordOrderReputation, recordViolationReputation, recordDisputeReputation, recordRepEvent, getReputation, getStakeDiscount, applyDecayIfDue, } from '../layer4-economics/L4-3-reputation/reputation-engine.js';
|
|
43
46
|
import { generateManifest } from '../layer0-foundation/L0-5-manifest/manifest.js';
|
|
44
47
|
import Anthropic from '@anthropic-ai/sdk';
|
|
45
|
-
import { privateKeyToAddress, privateKeyToAccount } from 'viem/accounts';
|
|
46
48
|
import { createPublicClient, createWalletClient, http, parseAbiItem, parseAbi, parseEther } from 'viem';
|
|
47
49
|
import { baseSepolia, base } from 'viem/chains';
|
|
48
50
|
import { createHmac, createHash, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
|
|
@@ -73,9 +75,10 @@ import { registerDisputeCasesRoutes } from './routes/dispute-cases.js';
|
|
|
73
75
|
// Claim verify (#1013 Phase 9) — 8 endpoints + 三路径结算 cron + 铁律 §4
|
|
74
76
|
// requireHumanPresence 仍在 server.ts(arbitrate / agent_revoke / vote 3 处用),通过 deps 注入
|
|
75
77
|
// settleClaimTask + 多个内部 helper 已 export 供 server.ts 其它路径调用(如 product-claims)
|
|
76
|
-
import { registerClaimVerifyRoutes, processClaimTaskQueue, isEligibleClaimVerifier as isEligibleClaimVerifierRaw, activeClaimTaskCountForVerifier as activeClaimTaskCountForVerifierRaw, settleClaimTask as settleClaimTaskRaw, notifyEligibleVerifiers as notifyEligibleVerifiersRaw,
|
|
77
|
-
//
|
|
78
|
-
|
|
78
|
+
import { registerClaimVerifyRoutes, processClaimTaskQueue, isEligibleClaimVerifier as isEligibleClaimVerifierRaw, activeClaimTaskCountForVerifier as activeClaimTaskCountForVerifierRaw, settleClaimTask as settleClaimTaskRaw, notifyEligibleVerifiers as notifyEligibleVerifiersRaw,
|
|
79
|
+
// #420 P1-3:verifier outlier 阈值改由 protocol_params 驱动(见 anti-abuse-thresholds.ts),
|
|
80
|
+
// checkVerifierOutlier 不再 import claim-verify 的 CLAIM_*_THRESHOLD 常量。
|
|
81
|
+
} from './routes/claim-verify.js';
|
|
79
82
|
// Follows (#1013 Phase 10) — 4 endpoints (status/post/delete/me)
|
|
80
83
|
// /api/follows/feed 留 server.ts(依赖 products 跨域,待商品域拆分时一并处理)
|
|
81
84
|
import { registerFollowsRoutes } from './routes/follows.js';
|
|
@@ -174,7 +177,7 @@ import { registerShareRedirectsRoutes } from './routes/share-redirects.js';
|
|
|
174
177
|
import { registerShopReferralRoutes } from './routes/shop-referral.js';
|
|
175
178
|
// Profile 凭据 (#1013 Phase 55) — 5 endpoints (密码 + 邮箱绑定)
|
|
176
179
|
import { registerProfileCredentialsRoutes } from './routes/profile-credentials.js';
|
|
177
|
-
// Profile
|
|
180
|
+
// Profile 放置挂靠 (#1013 Phase 56) — 3 endpoints
|
|
178
181
|
import { registerProfilePlacementRoutes } from './routes/profile-placement.js';
|
|
179
182
|
// Profile 位置 (#1013 Phase 57) — 2 endpoints
|
|
180
183
|
import { registerProfileLocationRoutes } from './routes/profile-location.js';
|
|
@@ -314,15 +317,24 @@ import { initBuildTaskAgentMetadataSchema } from '../layer2-business/L2-9-contri
|
|
|
314
317
|
import { initTaskProposalSchema } from '../layer2-business/L2-9-contribution/task-proposal-store.js';
|
|
315
318
|
import { initTaskProposalAiSchema } from '../layer2-business/L2-9-contribution/task-proposal-ai-store.js';
|
|
316
319
|
import { initTaskProposalDraftLinkSchema } from '../layer2-business/L2-9-contribution/task-proposal-draft.js';
|
|
320
|
+
import { initBuildTaskQuotaSchema } from '../layer2-business/L2-9-contribution/build-task-quota.js';
|
|
321
|
+
import { registerBuildTaskQuotaRoutes } from './routes/build-task-quota.js';
|
|
322
|
+
import { registerAdminOperatorClaimRoutes } from './routes/admin-operator-claims.js';
|
|
317
323
|
import { registerTaskProposalsRoutes } from './routes/task-proposals.js';
|
|
324
|
+
import { participationRecordingActive, matchingRewardsActive } from './pv-kill-switch.js'; // Category C: participation recording (default ON) vs matching-rewards payout (default OFF)
|
|
325
|
+
import { createPvSettlementEngine } from './internal/pv-settlement.js'; // matching-rewards engine EXCISED — no-op stub (see internal/pv-settlement.ts)
|
|
326
|
+
import { createLocalSeedSigner } from './internal/wallet-signer.js'; // Phase 0: hot-wallet custody signer seam (docs/HOT-WALLET-CUSTODY-MIGRATION.md)
|
|
327
|
+
import { createCfOriginGuard } from './cf-origin-guard.js'; // Cloudflare-only origin guard (off by default)
|
|
318
328
|
import { createSlidingWindowLimiter } from './rate-limit.js';
|
|
319
329
|
import { registerBuildReputationRoutes } from './routes/build-reputation.js';
|
|
320
330
|
import { initBuildReputationSchema } from '../layer2-business/L2-9-contribution/build-reputation-engine.js';
|
|
321
331
|
import { initGithubCredentialStoreSchema } from '../layer2-business/L2-9-contribution/github-credential-store.js';
|
|
322
332
|
import { initIdentityBindingSchema } from '../layer2-business/L2-9-contribution/identity-binding-store.js';
|
|
323
333
|
import { initIdentityClaimChallengeSchema } from '../layer2-business/L2-9-contribution/identity-claim-challenge-store.js';
|
|
334
|
+
import { initAdminCoordinationSchema } from '../layer2-business/L2-9-contribution/admin-coordination-store.js';
|
|
324
335
|
import { registerContributionIdentityRoutes } from './routes/contribution-identity.js';
|
|
325
336
|
import { registerContributionScoreRoutes } from './routes/contribution-score.js';
|
|
337
|
+
import { registerContributionFactsRoutes } from './routes/contribution-facts.js';
|
|
326
338
|
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
327
339
|
// ─── 链上地址派生 ──────────────────────────────────────────────
|
|
328
340
|
const MASTER_SEED = process.env.WALLET_MASTER_SEED ?? 'webaz-dev-seed-changeme';
|
|
@@ -353,11 +365,13 @@ else if (MASTER_SEED.length < 32) {
|
|
|
353
365
|
function generateSecureKey(prefix) {
|
|
354
366
|
return `${prefix}_${randomBytes(32).toString('hex')}`;
|
|
355
367
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
368
|
+
// Phase 0 (docs/HOT-WALLET-CUSTODY-MIGRATION.md): all USDC-custody key derivation / signing goes
|
|
369
|
+
// through the WalletSigner seam. LocalSeedSigner reproduces the historical HMAC-SHA256(MASTER_SEED, role)
|
|
370
|
+
// derivation EXACTLY — addresses + signatures unchanged. Phase 1+ swaps in KMS / multisig signers
|
|
371
|
+
// (HOT_WALLET_SIGNER env) behind the same interface, no call-site changes.
|
|
372
|
+
const walletSigner = createLocalSeedSigner(MASTER_SEED);
|
|
359
373
|
function deriveDepositAddress(userId) {
|
|
360
|
-
return
|
|
374
|
+
return walletSigner.depositAddress(userId);
|
|
361
375
|
}
|
|
362
376
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
363
377
|
const db = initDatabase();
|
|
@@ -381,10 +395,13 @@ initBuildTaskAgentMetadataSchema(db); // PR9B — agent-ready task metadata sate
|
|
|
381
395
|
initTaskProposalSchema(db); // Task Proposal Inbox v1 — suggestion inbox(maintainer review;never auto build_task)
|
|
382
396
|
initTaskProposalAiSchema(db); // Task Proposal AI-assist — assistant-only recommendation/evidence(human decides)
|
|
383
397
|
initTaskProposalDraftLinkSchema(db); // Task Proposal draft links — source proposal ↔ draft task(converted at publish)
|
|
398
|
+
initBuildTaskQuotaSchema(db); // PR #18 — build_task create quota-increase requests(non-root request → root grant)
|
|
384
399
|
initBuildReputationSchema(db); // RFC-006 build_reputation(独立池 + 贡献者看板)
|
|
385
400
|
initGithubCredentialStoreSchema(db); // PR 3B-3a — GitHub credential store + RFC-017 fact layer (schema only)
|
|
386
401
|
initIdentityBindingSchema(db); // PR 4a — GitHub identity → WebAZ account binding (append-only events + active projection)
|
|
387
402
|
initIdentityClaimChallengeSchema(db); // PR-F1 — identity-claim publication-challenge state (server-side nonce hash; schema only)
|
|
403
|
+
// NB: initAdminCoordinationSchema is intentionally NOT called here — it FKs admin_audit_log, which is
|
|
404
|
+
// created later; it runs right after the admin_audit_log block below (search initAdminCoordinationSchema).
|
|
388
405
|
initSnfSchema(db);
|
|
389
406
|
initExternalAnchorSchema(db);
|
|
390
407
|
// 启动时检查月衰减(last_decay_at ≥25 天才触发,重启幂等)
|
|
@@ -401,11 +418,11 @@ ensureEvidenceColumns(db);
|
|
|
401
418
|
initAnchorRegistrySchema(db);
|
|
402
419
|
// boot-order fix(2026-05-26):anchor migration 引用 users.handle / search_anchor,
|
|
403
420
|
// 但对应 ALTER TABLE 在 735+/958+ 行才跑。旧 DB(v3 era)触发 prepare 失败 → 此处 catch
|
|
404
|
-
// 后 warn 不阻塞 server,但日志噪音 → 预热那两列让 migration
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
421
|
+
// 后 warn 不阻塞 server,但日志噪音 → 预热那两列让 migration 真正能跑。
|
|
422
|
+
// handle 现由 initRegisterListSearchColumns 在此预热(与 MCP runtime schema 同源,见
|
|
423
|
+
// src/runtime/webaz-schema-helpers.ts);该 helper 同时建 permanent_code/region + 11 个
|
|
424
|
+
// products 结构化字段(纯非钱列,从下方各 inline 块单点收口到此处,CREATE-before-ALTER 不变)。
|
|
425
|
+
initRegisterListSearchColumns(db);
|
|
409
426
|
try {
|
|
410
427
|
db.exec("ALTER TABLE users ADD COLUMN search_anchor TEXT");
|
|
411
428
|
}
|
|
@@ -443,27 +460,9 @@ catch (e) {
|
|
|
443
460
|
console.warn('[anchor-registry] migration:', e.message);
|
|
444
461
|
}
|
|
445
462
|
// ─── 验证员白名单表 ───────────────────────────────────────────────
|
|
446
|
-
db
|
|
447
|
-
CREATE TABLE IF NOT EXISTS verifier_whitelist (
|
|
448
|
-
user_id TEXT PRIMARY KEY,
|
|
449
|
-
added_at TEXT DEFAULT (datetime('now')),
|
|
450
|
-
note TEXT
|
|
451
|
-
)
|
|
452
|
-
`);
|
|
463
|
+
initVerifierWhitelistSchema(db);
|
|
453
464
|
// ─── MCP 工具调用埋点表(远程上报)─────────────────────────────────
|
|
454
|
-
db
|
|
455
|
-
CREATE TABLE IF NOT EXISTS mcp_tool_calls (
|
|
456
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
457
|
-
tool_name TEXT NOT NULL,
|
|
458
|
-
user_id_hash TEXT,
|
|
459
|
-
server_version TEXT,
|
|
460
|
-
outcome TEXT NOT NULL,
|
|
461
|
-
latency_ms INTEGER NOT NULL,
|
|
462
|
-
ts TEXT NOT NULL DEFAULT (datetime('now'))
|
|
463
|
-
)
|
|
464
|
-
`);
|
|
465
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_mcp_tc_ts ON mcp_tool_calls(ts)`);
|
|
466
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_mcp_tc_tool ON mcp_tool_calls(tool_name, ts)`);
|
|
465
|
+
initMcpToolCallsSchema(db);
|
|
467
466
|
// ─── 内部审核账号(固定 ID,密钥由 MASTER_SEED 派生,幂等)────────
|
|
468
467
|
const INTERNAL_AUDITOR_ID = 'usr_iaudit_001';
|
|
469
468
|
const INTERNAL_AUDITOR_KEY = 'key_iaudit_' + createHmac('sha256', MASTER_SEED).update('internal_auditor_v1').digest('hex').slice(0, 32);
|
|
@@ -521,11 +520,10 @@ try {
|
|
|
521
520
|
}
|
|
522
521
|
catch { }
|
|
523
522
|
// Tokenomics 推土机轨道 — Phase 1(分享现金分润)
|
|
524
|
-
// 详见 docs/modules/tokenomics-pv-commission.md
|
|
525
523
|
for (const stmt of [
|
|
526
524
|
'ALTER TABLE users ADD COLUMN sponsor_id TEXT',
|
|
527
525
|
'ALTER TABLE users ADD COLUMN sponsor_path TEXT',
|
|
528
|
-
|
|
526
|
+
// users.region moved to initRegisterListSearchColumns (single source, shared w/ MCP) — see ~line 494.
|
|
529
527
|
// Admin 分级:root 全权 / regional 按 admin_scope 区域受限
|
|
530
528
|
"ALTER TABLE users ADD COLUMN admin_type TEXT", // root | regional
|
|
531
529
|
"ALTER TABLE users ADD COLUMN admin_scope TEXT", // global | china | us | eu | india | singapore
|
|
@@ -583,28 +581,7 @@ for (const stmt of [
|
|
|
583
581
|
// M8 二手板块:独立表,避免污染 products 商家货架
|
|
584
582
|
// 关键差异:1 件即 1 件(无库存)、个人卖家无需 seller 角色、无质保、协议费 1%(vs 商家 2%)
|
|
585
583
|
try {
|
|
586
|
-
db
|
|
587
|
-
id TEXT PRIMARY KEY,
|
|
588
|
-
seller_id TEXT NOT NULL,
|
|
589
|
-
title TEXT NOT NULL,
|
|
590
|
-
description TEXT,
|
|
591
|
-
category TEXT NOT NULL, -- phone/computer/appliance/furniture/clothing/book/toy/sports/other
|
|
592
|
-
condition_grade TEXT NOT NULL, -- brand_new/like_new/lightly_used/well_used/heavily_used
|
|
593
|
-
price REAL NOT NULL,
|
|
594
|
-
negotiable INTEGER DEFAULT 0,
|
|
595
|
-
images TEXT, -- JSON 数组:dataURL 字符串 (最多 9 张)
|
|
596
|
-
region TEXT,
|
|
597
|
-
fulfillment TEXT DEFAULT 'both', -- shipping / in_person / both
|
|
598
|
-
status TEXT DEFAULT 'available', -- available / reserved / sold / closed
|
|
599
|
-
view_count INTEGER DEFAULT 0,
|
|
600
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
601
|
-
updated_at TEXT DEFAULT (datetime('now')),
|
|
602
|
-
sold_at TEXT,
|
|
603
|
-
sold_order_id TEXT
|
|
604
|
-
)`);
|
|
605
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_si_status_created ON secondhand_items(status, created_at DESC)`);
|
|
606
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_si_seller ON secondhand_items(seller_id, status)`);
|
|
607
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_si_cat ON secondhand_items(category, status)`);
|
|
584
|
+
initSecondhandItemsSchema(db);
|
|
608
585
|
}
|
|
609
586
|
catch (e) {
|
|
610
587
|
console.error('[secondhand schema]', e);
|
|
@@ -729,7 +706,7 @@ db.exec(`
|
|
|
729
706
|
max_levels INTEGER NOT NULL, -- 0=完全禁 MLM / 1=仅 L1 / 2=L1+L2 / 3=全三级(仅控佣金层级)
|
|
730
707
|
active INTEGER DEFAULT 1,
|
|
731
708
|
mlm_ui_visible INTEGER DEFAULT 1, -- 0=UI 全面隐藏推土机/分润链/佣金展示
|
|
732
|
-
pv_enabled INTEGER DEFAULT 0 --
|
|
709
|
+
pv_enabled INTEGER DEFAULT 0 -- 区域级 PV 开关(独立于佣金层级 max_levels)。默认 0=关
|
|
733
710
|
)
|
|
734
711
|
`);
|
|
735
712
|
// Phase B 迁移:加 mlm_ui_visible 列
|
|
@@ -737,7 +714,7 @@ try {
|
|
|
737
714
|
db.exec('ALTER TABLE region_config ADD COLUMN mlm_ui_visible INTEGER DEFAULT 1');
|
|
738
715
|
}
|
|
739
716
|
catch { /* 已存在 */ }
|
|
740
|
-
// 2026-06-04 解耦迁移:加 pv_enabled
|
|
717
|
+
// 2026-06-04 解耦迁移:加 pv_enabled 列(区域级 PV 开关,与佣金层级 max_levels 分离)
|
|
741
718
|
try {
|
|
742
719
|
db.exec('ALTER TABLE region_config ADD COLUMN pv_enabled INTEGER DEFAULT 0');
|
|
743
720
|
}
|
|
@@ -780,29 +757,9 @@ catch { }
|
|
|
780
757
|
}
|
|
781
758
|
catch { }
|
|
782
759
|
});
|
|
783
|
-
// P13: 购物车
|
|
784
|
-
db
|
|
785
|
-
|
|
786
|
-
user_id TEXT NOT NULL,
|
|
787
|
-
product_id TEXT NOT NULL,
|
|
788
|
-
qty INTEGER NOT NULL DEFAULT 1,
|
|
789
|
-
added_at TEXT DEFAULT (datetime('now')),
|
|
790
|
-
PRIMARY KEY (user_id, product_id)
|
|
791
|
-
)
|
|
792
|
-
`);
|
|
793
|
-
// P14: 关注关系(社交电商)
|
|
794
|
-
db.exec(`
|
|
795
|
-
CREATE TABLE IF NOT EXISTS follows (
|
|
796
|
-
follower_id TEXT NOT NULL,
|
|
797
|
-
followee_id TEXT NOT NULL,
|
|
798
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
799
|
-
PRIMARY KEY (follower_id, followee_id)
|
|
800
|
-
)
|
|
801
|
-
`);
|
|
802
|
-
try {
|
|
803
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_follows_followee ON follows(followee_id)');
|
|
804
|
-
}
|
|
805
|
-
catch { }
|
|
760
|
+
// P13: 购物车 / P14: 关注关系(社交电商)→ server-schema.ts
|
|
761
|
+
initCartItemsSchema(db);
|
|
762
|
+
initFollowsSchema(db);
|
|
806
763
|
// P14: 用户 feed 可见性开关(默认公开)
|
|
807
764
|
try {
|
|
808
765
|
db.exec("ALTER TABLE users ADD COLUMN feed_visible INTEGER DEFAULT 1");
|
|
@@ -857,6 +814,13 @@ try {
|
|
|
857
814
|
catch { }
|
|
858
815
|
// 已注册默认参数(首次启动 seed) — P0-2 加 min/max 边界
|
|
859
816
|
const DEFAULT_PARAMS = [
|
|
817
|
+
// Category C:参与记录 vs 奖励兑付,分开两套开关。
|
|
818
|
+
// · 参与记录默认 ON:PV 是参与/贡献记录(非收益/非兑付/非权益),默认允许记录(只在显式 =0 时关)。
|
|
819
|
+
// · 匹配奖励引擎已切除(#401):该标志保留但只门控一个 no-op stub,无兑付路径;
|
|
820
|
+
// matching_rewards_activation_cleared = 法律/治理放行、matching_rewards_active = 运营开关。pre-launch 均 0。
|
|
821
|
+
{ key: 'participation_recording_active', value: '1', type: 'number', description: '参与记录开关:PV 生成+聚合(参与记录,非收益/非兑付);默认 1=开。置 0 才停止记录。', category: 'system', min: 0, max: 1 },
|
|
822
|
+
{ key: 'matching_rewards_active', value: '0', type: 'number', description: '匹配奖励运营开关(引擎已切除 #401,现仅门控 no-op stub;无兑付);默认 0=关。', category: 'system', min: 0, max: 1 },
|
|
823
|
+
{ key: 'matching_rewards_activation_cleared', value: '0', type: 'number', description: '奖励兑付法律/治理放行标志(开启奖励前必须经合规+治理审批置 1);默认 0。', category: 'system', min: 0, max: 1 },
|
|
860
824
|
// RFC-008:平台费硬帽 2%(=当前稳态 → 治理只能在 0–2% 减免、永不涨)。合计封顶 = 平台费2% + fund_base1% = 3%。宪法级合法性见 CHARTER 修订(单独治理步)。
|
|
861
825
|
{ key: 'protocol_fee_rate_shop', value: '0.02', type: 'number', description: '商家订单平台费率(RFC-008 硬帽 2%,只减不涨;前期可减免)', category: 'fee', min: 0, max: 0.02 },
|
|
862
826
|
{ key: 'protocol_fee_rate_secondhand', value: '0.01', type: 'number', description: '二手订单平台费率(RFC-008 硬帽 2%,只减不涨)', category: 'fee', min: 0, max: 0.02 },
|
|
@@ -881,6 +845,8 @@ const DEFAULT_PARAMS = [
|
|
|
881
845
|
{ key: 'max_addresses_per_user', value: '20', type: 'number', description: '单用户最多收货地址数', category: 'limit', min: 1, max: 100 },
|
|
882
846
|
{ key: 'max_compare_items', value: '4', type: 'number', description: '商品对比最多件数', category: 'limit', min: 2, max: 10 },
|
|
883
847
|
{ key: 'feedback_rate_per_hour', value: '5', type: 'number', description: '反馈工单每小时上限', category: 'limit', min: 1, max: 100 },
|
|
848
|
+
{ key: 'max_quota_extra_count', value: '50', type: 'number', description: 'PR#18 build_task 扩容申请:单次最多额外任务数', category: 'limit', min: 1, max: 500 },
|
|
849
|
+
{ key: 'max_quota_duration_hours', value: '72', type: 'number', description: 'PR#18 build_task 扩容授权:最长有效期(小时)', category: 'limit', min: 1, max: 2160 },
|
|
884
850
|
{ key: 'export_csv_limit', value: '5000', type: 'number', description: '订单导出 CSV 行数上限', category: 'limit', min: 100, max: 50000 },
|
|
885
851
|
{ key: 'return_window_extension_days', value: '0', type: 'number', description: '退货窗口全局延长天数', category: 'general', min: 0, max: 90 },
|
|
886
852
|
// Wave G-2: USDC / 链上配置
|
|
@@ -948,6 +914,9 @@ const DEFAULT_PARAMS = [
|
|
|
948
914
|
// 假设:这两个 param 都满足"increase = more protection"语义(见 admin-protocol-params.ts 头部注释)
|
|
949
915
|
{ key: 'constitutional_supermajority_ratio', value: '0.667', type: 'number', description: 'CHARTER §4 I-4:宪法级修改超级多数比例(phase A: user solo 1-of-1;phase B+: maintainer 多签 ratio)— only-increase 防绕过', category: 'constitutional', min: 0.5, max: 1.0 },
|
|
950
916
|
{ key: 'constitutional_notice_days', value: '60', type: 'number', description: 'CHARTER §4 I-4:宪法级修改 RFC 公示期(天)— only-increase 防绕过', category: 'constitutional', min: 30, max: 365 },
|
|
917
|
+
// #420 P1-2/P1-3/P1-4:反滥用阈值(agent 信任公式 / strike 阶梯 / verifier outlier)→ 治理可调。
|
|
918
|
+
// 默认值 === 抽取前硬编码字面量(单一真相源在 anti-abuse-thresholds.ts;测试强制校验一致)。
|
|
919
|
+
...ANTI_ABUSE_PARAMS,
|
|
951
920
|
];
|
|
952
921
|
for (const p of DEFAULT_PARAMS) {
|
|
953
922
|
try {
|
|
@@ -1161,23 +1130,7 @@ function disbursePlatformReward(userId, amount, source, ref) {
|
|
|
1161
1130
|
// Wave E-5: PWA Push 订阅
|
|
1162
1131
|
// 注:实际 push 投递需要 web-push 库(npm i web-push)+ VAPID 私钥签名;
|
|
1163
1132
|
// 当前实现只做订阅层 + SW push 事件处理,留待 web-push 接入后即可发送
|
|
1164
|
-
db
|
|
1165
|
-
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
|
1166
|
-
id TEXT PRIMARY KEY,
|
|
1167
|
-
user_id TEXT NOT NULL,
|
|
1168
|
-
endpoint TEXT NOT NULL,
|
|
1169
|
-
p256dh TEXT NOT NULL,
|
|
1170
|
-
auth TEXT NOT NULL,
|
|
1171
|
-
user_agent TEXT,
|
|
1172
|
-
enabled INTEGER DEFAULT 1,
|
|
1173
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1174
|
-
UNIQUE(user_id, endpoint)
|
|
1175
|
-
)
|
|
1176
|
-
`);
|
|
1177
|
-
try {
|
|
1178
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_push_user ON push_subscriptions(user_id, enabled)');
|
|
1179
|
-
}
|
|
1180
|
-
catch { }
|
|
1133
|
+
initPushSubscriptionsSchema(db);
|
|
1181
1134
|
// 2026-05-22 V2:verifier 新任务通知偏好(默认开,可关)
|
|
1182
1135
|
try {
|
|
1183
1136
|
db.exec('ALTER TABLE users ADD COLUMN notify_claim_tasks INTEGER DEFAULT 1');
|
|
@@ -1250,14 +1203,8 @@ catch { } // 完整店铺介绍(多段)
|
|
|
1250
1203
|
// ─── 4 层身份模型 ─────────────────────────────────────────
|
|
1251
1204
|
// id (内部 usr_xxx, 永不可改) + permanent_code (6 位 Crockford base32, 永不可改, 对外短码)
|
|
1252
1205
|
// + handle (@username, 可改 7天1次/年3次) + name (昵称, 可重复可改)
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
}
|
|
1256
|
-
catch { }
|
|
1257
|
-
try {
|
|
1258
|
-
db.exec("ALTER TABLE users ADD COLUMN handle TEXT");
|
|
1259
|
-
}
|
|
1260
|
-
catch { }
|
|
1206
|
+
// permanent_code / handle + 其唯一索引已上移到 initRegisterListSearchColumns(~line 494,
|
|
1207
|
+
// 与 MCP runtime schema 同源);此处仅保留 handle 的附属列(不在 register/list/search 路径上)。
|
|
1261
1208
|
try {
|
|
1262
1209
|
db.exec("ALTER TABLE users ADD COLUMN handle_last_created_at TEXT");
|
|
1263
1210
|
}
|
|
@@ -1266,14 +1213,6 @@ try {
|
|
|
1266
1213
|
db.exec("ALTER TABLE users ADD COLUMN handle_change_log TEXT");
|
|
1267
1214
|
}
|
|
1268
1215
|
catch { } // JSON: [{at, from}], 保留近 365 天
|
|
1269
|
-
try {
|
|
1270
|
-
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_permanent_code ON users(permanent_code) WHERE permanent_code IS NOT NULL");
|
|
1271
|
-
}
|
|
1272
|
-
catch { }
|
|
1273
|
-
try {
|
|
1274
|
-
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_handle ON users(handle) WHERE handle IS NOT NULL");
|
|
1275
|
-
}
|
|
1276
|
-
catch { }
|
|
1277
1216
|
// P15 雷达扫描:粗粒度地理位置(0.1° ≈ 11km × 11km,QVOD 风格匿名聚合)
|
|
1278
1217
|
try {
|
|
1279
1218
|
db.exec("ALTER TABLE users ADD COLUMN geo_lat REAL");
|
|
@@ -1369,27 +1308,7 @@ const LARGE_WITHDRAW_THRESHOLD = 100;
|
|
|
1369
1308
|
// 用途:防 api_key 泄露后无法吊销的根本问题。每个 api_key 关联一个 session 行;
|
|
1370
1309
|
// 用户可在 "活跃会话" 页查看 IP/UA/最后活跃,单点吊销或一键全登出。
|
|
1371
1310
|
// "一键全登出" = rotate users.api_key(所有旧 key 即刻 401,新 key 在 session 表里)。
|
|
1372
|
-
db
|
|
1373
|
-
CREATE TABLE IF NOT EXISTS user_sessions (
|
|
1374
|
-
id TEXT PRIMARY KEY,
|
|
1375
|
-
user_id TEXT NOT NULL,
|
|
1376
|
-
api_key TEXT NOT NULL,
|
|
1377
|
-
ip TEXT,
|
|
1378
|
-
user_agent TEXT,
|
|
1379
|
-
fingerprint_hash TEXT,
|
|
1380
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1381
|
-
last_seen_at TEXT DEFAULT (datetime('now')),
|
|
1382
|
-
revoked_at TEXT
|
|
1383
|
-
)
|
|
1384
|
-
`);
|
|
1385
|
-
try {
|
|
1386
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_user ON user_sessions(user_id, revoked_at)');
|
|
1387
|
-
}
|
|
1388
|
-
catch { }
|
|
1389
|
-
try {
|
|
1390
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_key ON user_sessions(api_key)');
|
|
1391
|
-
}
|
|
1392
|
-
catch { }
|
|
1311
|
+
initUserSessionsSchema(db);
|
|
1393
1312
|
// A4 智能下单:用户默认地址(搜索时自动过滤不可达商品 + 下单时预填)
|
|
1394
1313
|
try {
|
|
1395
1314
|
db.exec("ALTER TABLE users ADD COLUMN default_address_text TEXT");
|
|
@@ -1405,19 +1324,7 @@ try {
|
|
|
1405
1324
|
}
|
|
1406
1325
|
catch { }
|
|
1407
1326
|
// A2 黑名单(精准匹配护栏):买家可拉黑卖家,搜索时自动过滤
|
|
1408
|
-
db
|
|
1409
|
-
CREATE TABLE IF NOT EXISTS user_blocklist (
|
|
1410
|
-
blocker_id TEXT NOT NULL,
|
|
1411
|
-
blocked_id TEXT NOT NULL,
|
|
1412
|
-
reason TEXT,
|
|
1413
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1414
|
-
PRIMARY KEY (blocker_id, blocked_id)
|
|
1415
|
-
)
|
|
1416
|
-
`);
|
|
1417
|
-
try {
|
|
1418
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_blocklist_blocker ON user_blocklist(blocker_id)");
|
|
1419
|
-
}
|
|
1420
|
-
catch { }
|
|
1327
|
+
initUserBlocklistSchema(db);
|
|
1421
1328
|
// P-Distrib β:分布式内容层(外链 shareables + P2P 原生 manifests + pin 经济)
|
|
1422
1329
|
// shareables = 外链分享(YouTube/TikTok/小红书 等外部内容)— 仅索引 URL,零内容存储
|
|
1423
1330
|
db.exec(`
|
|
@@ -1503,99 +1410,12 @@ try {
|
|
|
1503
1410
|
db.exec("CREATE INDEX IF NOT EXISTS idx_share_order ON shareables(related_order_id) WHERE related_order_id IS NOT NULL");
|
|
1504
1411
|
}
|
|
1505
1412
|
catch { }
|
|
1506
|
-
//
|
|
1507
|
-
//
|
|
1508
|
-
db
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
)
|
|
1513
|
-
`);
|
|
1514
|
-
try {
|
|
1515
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_npi_shareable ON note_photo_index(shareable_id)");
|
|
1516
|
-
}
|
|
1517
|
-
catch { }
|
|
1518
|
-
// Wave A-1: 个人心愿单(独立于慈善 wishes)
|
|
1519
|
-
db.exec(`
|
|
1520
|
-
CREATE TABLE IF NOT EXISTS user_wishlist (
|
|
1521
|
-
user_id TEXT NOT NULL,
|
|
1522
|
-
product_id TEXT NOT NULL,
|
|
1523
|
-
note TEXT,
|
|
1524
|
-
notify_price_drop INTEGER DEFAULT 1,
|
|
1525
|
-
notify_back_in_stock INTEGER DEFAULT 1,
|
|
1526
|
-
price_at_add REAL,
|
|
1527
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1528
|
-
PRIMARY KEY(user_id, product_id)
|
|
1529
|
-
)
|
|
1530
|
-
`);
|
|
1531
|
-
try {
|
|
1532
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_wl_product ON user_wishlist(product_id)');
|
|
1533
|
-
}
|
|
1534
|
-
catch { }
|
|
1535
|
-
// Wave A-2: 商品 Q&A(公开问答 — 自动 FAQ + 防虚假承诺)
|
|
1536
|
-
db.exec(`
|
|
1537
|
-
CREATE TABLE IF NOT EXISTS product_qa (
|
|
1538
|
-
id TEXT PRIMARY KEY,
|
|
1539
|
-
product_id TEXT NOT NULL,
|
|
1540
|
-
asker_id TEXT NOT NULL,
|
|
1541
|
-
seller_id TEXT NOT NULL,
|
|
1542
|
-
question TEXT NOT NULL,
|
|
1543
|
-
answer TEXT,
|
|
1544
|
-
answered_at TEXT,
|
|
1545
|
-
is_public INTEGER DEFAULT 1,
|
|
1546
|
-
helpful_count INTEGER DEFAULT 0,
|
|
1547
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1548
|
-
)
|
|
1549
|
-
`);
|
|
1550
|
-
try {
|
|
1551
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_qa_product ON product_qa(product_id, created_at DESC)');
|
|
1552
|
-
}
|
|
1553
|
-
catch { }
|
|
1554
|
-
try {
|
|
1555
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_qa_seller ON product_qa(seller_id, answered_at)');
|
|
1556
|
-
}
|
|
1557
|
-
catch { }
|
|
1558
|
-
try {
|
|
1559
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_qa_asker ON product_qa(asker_id)');
|
|
1560
|
-
}
|
|
1561
|
-
catch { }
|
|
1562
|
-
// 防重复 +1 的 votes 表
|
|
1563
|
-
db.exec(`
|
|
1564
|
-
CREATE TABLE IF NOT EXISTS product_qa_helpful_voters (
|
|
1565
|
-
qa_id TEXT NOT NULL,
|
|
1566
|
-
user_id TEXT NOT NULL,
|
|
1567
|
-
voted_at TEXT DEFAULT (datetime('now')),
|
|
1568
|
-
PRIMARY KEY (qa_id, user_id)
|
|
1569
|
-
)
|
|
1570
|
-
`);
|
|
1571
|
-
// Wave A-3: 优惠券 / 限时折扣(卖家发券 · 全店满减 · 单品限时)
|
|
1572
|
-
db.exec(`
|
|
1573
|
-
CREATE TABLE IF NOT EXISTS coupons (
|
|
1574
|
-
id TEXT PRIMARY KEY,
|
|
1575
|
-
seller_id TEXT NOT NULL,
|
|
1576
|
-
code TEXT NOT NULL,
|
|
1577
|
-
scope TEXT NOT NULL, -- 'product' | 'shop' | 'all'
|
|
1578
|
-
scope_id TEXT, -- product_id when scope='product'
|
|
1579
|
-
discount_type TEXT NOT NULL, -- 'percentage' | 'fixed'
|
|
1580
|
-
discount_value REAL NOT NULL,
|
|
1581
|
-
min_order_amount REAL DEFAULT 0,
|
|
1582
|
-
max_uses INTEGER DEFAULT 0, -- 0 = unlimited
|
|
1583
|
-
uses_count INTEGER DEFAULT 0,
|
|
1584
|
-
starts_at TEXT,
|
|
1585
|
-
expires_at TEXT,
|
|
1586
|
-
is_active INTEGER DEFAULT 1,
|
|
1587
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1588
|
-
UNIQUE(seller_id, code)
|
|
1589
|
-
)
|
|
1590
|
-
`);
|
|
1591
|
-
try {
|
|
1592
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_coupons_seller ON coupons(seller_id, is_active)');
|
|
1593
|
-
}
|
|
1594
|
-
catch { }
|
|
1595
|
-
try {
|
|
1596
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_coupons_scope ON coupons(scope, scope_id) WHERE is_active = 1');
|
|
1597
|
-
}
|
|
1598
|
-
catch { }
|
|
1413
|
+
// 笔记图片 hash 索引 / Wave A 购物表(心愿单 · 商品Q&A · 优惠券)→ server-schema.ts
|
|
1414
|
+
// 纯幂等建表/建索引 DDL,原位调用保持 boot 顺序不变
|
|
1415
|
+
initNotePhotoIndexSchema(db);
|
|
1416
|
+
initUserWishlistSchema(db);
|
|
1417
|
+
initProductQaSchema(db);
|
|
1418
|
+
initCouponsSchema(db);
|
|
1599
1419
|
// Orders 表加 coupon 字段(记录使用了哪张券、折扣多少)
|
|
1600
1420
|
for (const stmt of [
|
|
1601
1421
|
'ALTER TABLE orders ADD COLUMN coupon_id TEXT',
|
|
@@ -1606,147 +1426,20 @@ for (const stmt of [
|
|
|
1606
1426
|
}
|
|
1607
1427
|
catch { }
|
|
1608
1428
|
}
|
|
1609
|
-
// Wave A-4
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
title TEXT NOT NULL,
|
|
1615
|
-
body TEXT NOT NULL,
|
|
1616
|
-
target_roles TEXT, -- JSON array: ['buyer','seller'] or null=all
|
|
1617
|
-
target_regions TEXT, -- JSON array: ['china','us'] or null=all
|
|
1618
|
-
severity TEXT DEFAULT 'info', -- 'info' | 'warning' | 'critical'
|
|
1619
|
-
is_active INTEGER DEFAULT 1,
|
|
1620
|
-
starts_at TEXT,
|
|
1621
|
-
expires_at TEXT,
|
|
1622
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1623
|
-
)
|
|
1624
|
-
`);
|
|
1625
|
-
try {
|
|
1626
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_ann_active ON announcements(is_active, created_at DESC)');
|
|
1627
|
-
}
|
|
1628
|
-
catch { }
|
|
1629
|
-
// 用户阅读记录(PK 防重复 dismiss)
|
|
1630
|
-
db.exec(`
|
|
1631
|
-
CREATE TABLE IF NOT EXISTS announcement_reads (
|
|
1632
|
-
user_id TEXT NOT NULL,
|
|
1633
|
-
announcement_id TEXT NOT NULL,
|
|
1634
|
-
read_at TEXT DEFAULT (datetime('now')),
|
|
1635
|
-
PRIMARY KEY (user_id, announcement_id)
|
|
1636
|
-
)
|
|
1637
|
-
`);
|
|
1638
|
-
// Wave B-2: 预售 / waitlist(缺货商品允许买家排队 → 回货时通知)
|
|
1639
|
-
db.exec(`
|
|
1640
|
-
CREATE TABLE IF NOT EXISTS product_waitlist (
|
|
1641
|
-
user_id TEXT NOT NULL,
|
|
1642
|
-
product_id TEXT NOT NULL,
|
|
1643
|
-
desired_qty INTEGER DEFAULT 1,
|
|
1644
|
-
note TEXT,
|
|
1645
|
-
notified_at TEXT, -- 回货时填,表示已发通知
|
|
1646
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1647
|
-
PRIMARY KEY (user_id, product_id)
|
|
1648
|
-
)
|
|
1649
|
-
`);
|
|
1650
|
-
try {
|
|
1651
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_waitlist_product ON product_waitlist(product_id) WHERE notified_at IS NULL');
|
|
1652
|
-
}
|
|
1653
|
-
catch { }
|
|
1654
|
-
// Wave D-4: 限时促销 / Flash Sale
|
|
1655
|
-
db.exec(`
|
|
1656
|
-
CREATE TABLE IF NOT EXISTS flash_sales (
|
|
1657
|
-
id TEXT PRIMARY KEY,
|
|
1658
|
-
seller_id TEXT NOT NULL,
|
|
1659
|
-
product_id TEXT NOT NULL,
|
|
1660
|
-
variant_id TEXT, -- 可选,绑定具体规格
|
|
1661
|
-
sale_price REAL NOT NULL,
|
|
1662
|
-
original_price REAL NOT NULL, -- 创建时快照,用于显示「省 X」
|
|
1663
|
-
max_qty INTEGER DEFAULT 0, -- 0 = 不限
|
|
1664
|
-
sold_count INTEGER DEFAULT 0,
|
|
1665
|
-
starts_at TEXT NOT NULL,
|
|
1666
|
-
ends_at TEXT NOT NULL,
|
|
1667
|
-
is_active INTEGER DEFAULT 1,
|
|
1668
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1669
|
-
)
|
|
1670
|
-
`);
|
|
1671
|
-
try {
|
|
1672
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_flash_product ON flash_sales(product_id, is_active)');
|
|
1673
|
-
}
|
|
1674
|
-
catch { }
|
|
1675
|
-
try {
|
|
1676
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_flash_seller ON flash_sales(seller_id, ends_at DESC)');
|
|
1677
|
-
}
|
|
1678
|
-
catch { }
|
|
1429
|
+
// Wave A-4 公告+阅读 / Wave B-2 预售waitlist / Wave D-4 限时促销 → server-schema.ts
|
|
1430
|
+
// 纯幂等建表/建索引 DDL,原位调用保持 boot 顺序不变
|
|
1431
|
+
initAnnouncementsSchema(db);
|
|
1432
|
+
initProductWaitlistSchema(db);
|
|
1433
|
+
initFlashSalesSchema(db);
|
|
1679
1434
|
// 公共助手:拿商品(含 variant 选项)当前生效的 flash sale
|
|
1680
1435
|
// #1013 Phase 23: 已迁出到 routes/flash-sales.ts,本地 wrapper 让 orders 流程签名不变
|
|
1681
1436
|
const getActiveFlashSale = (productId, variantId) => getActiveFlashSaleRaw(db, productId, variantId);
|
|
1682
1437
|
// 2026-05-24 #978: 测评免单 (Trial Review Refund)
|
|
1683
1438
|
// 卖家发新品时可开启「测评免单」计划:买家以原价正常下单,发笔记达 reach 阈值后系统自动退款
|
|
1684
1439
|
// reach_score = views * 0.1 + shares * 1 + conversions * 10
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
-- 1 product 1 row (复用同一行:关闭后再开 = UPDATE status='active',避免 UNIQUE 阻断 reopen)
|
|
1689
|
-
product_id TEXT NOT NULL UNIQUE REFERENCES products(id),
|
|
1690
|
-
seller_id TEXT NOT NULL REFERENCES users(id),
|
|
1691
|
-
quota_total INTEGER NOT NULL, -- 总名额 1-200
|
|
1692
|
-
quota_claimed INTEGER NOT NULL DEFAULT 0,
|
|
1693
|
-
reach_threshold INTEGER NOT NULL, -- 综合 reach 阈值 (默认 50)
|
|
1694
|
-
min_chars INTEGER NOT NULL DEFAULT 50, -- 笔记最少字数
|
|
1695
|
-
min_days_live INTEGER NOT NULL DEFAULT 7, -- 笔记需 live 至少 N 天才评估
|
|
1696
|
-
status TEXT NOT NULL DEFAULT 'active', -- active / paused / closed
|
|
1697
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1698
|
-
closed_at TEXT
|
|
1699
|
-
)
|
|
1700
|
-
`);
|
|
1701
|
-
try {
|
|
1702
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_ptc_seller ON product_trial_campaigns(seller_id, status)");
|
|
1703
|
-
}
|
|
1704
|
-
catch { }
|
|
1705
|
-
try {
|
|
1706
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_ptc_product ON product_trial_campaigns(product_id, status)");
|
|
1707
|
-
}
|
|
1708
|
-
catch { }
|
|
1709
|
-
db.exec(`
|
|
1710
|
-
CREATE TABLE IF NOT EXISTS product_trial_claims (
|
|
1711
|
-
id TEXT PRIMARY KEY, -- pcl_xxxx
|
|
1712
|
-
campaign_id TEXT NOT NULL REFERENCES product_trial_campaigns(id),
|
|
1713
|
-
product_id TEXT NOT NULL REFERENCES products(id),
|
|
1714
|
-
seller_id TEXT NOT NULL REFERENCES users(id),
|
|
1715
|
-
buyer_id TEXT NOT NULL REFERENCES users(id),
|
|
1716
|
-
order_id TEXT NOT NULL REFERENCES orders(id),
|
|
1717
|
-
note_id TEXT, -- shareables.id with type='note'
|
|
1718
|
-
status TEXT NOT NULL DEFAULT 'pending_note', -- pending_note / pending_threshold / refunded / expired / cancelled
|
|
1719
|
-
reach_score REAL DEFAULT 0,
|
|
1720
|
-
metrics_json TEXT, -- 最新评估的 {views, shares, conversions} 快照
|
|
1721
|
-
refund_amount REAL,
|
|
1722
|
-
refunded_at TEXT,
|
|
1723
|
-
expired_at TEXT,
|
|
1724
|
-
last_eval_at TEXT,
|
|
1725
|
-
claimed_at TEXT DEFAULT (datetime('now')),
|
|
1726
|
-
note_linked_at TEXT,
|
|
1727
|
-
UNIQUE(buyer_id, product_id) -- 一买家一商品仅 1 个名额
|
|
1728
|
-
)
|
|
1729
|
-
`);
|
|
1730
|
-
try {
|
|
1731
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_pcl_campaign ON product_trial_claims(campaign_id, status)");
|
|
1732
|
-
}
|
|
1733
|
-
catch { }
|
|
1734
|
-
try {
|
|
1735
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_pcl_buyer ON product_trial_claims(buyer_id, status)");
|
|
1736
|
-
}
|
|
1737
|
-
catch { }
|
|
1738
|
-
try {
|
|
1739
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_pcl_seller ON product_trial_claims(seller_id, status)");
|
|
1740
|
-
}
|
|
1741
|
-
catch { }
|
|
1742
|
-
try {
|
|
1743
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_pcl_eval ON product_trial_claims(status, last_eval_at)");
|
|
1744
|
-
}
|
|
1745
|
-
catch { }
|
|
1746
|
-
try {
|
|
1747
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_pcl_note ON product_trial_claims(note_id) WHERE note_id IS NOT NULL");
|
|
1748
|
-
}
|
|
1749
|
-
catch { }
|
|
1440
|
+
// 测评免单计划 + 认领 → server-schema.ts;claims 的 snap/audit ALTER 刻意留原位(紧跟下方)
|
|
1441
|
+
initProductTrialCampaignsSchema(db);
|
|
1442
|
+
initProductTrialClaimsSchema(db);
|
|
1750
1443
|
// 审计 P0-1:claim 时快照 campaign 配置,cron 评估按快照而非当前活动(防卖家中途上调阈值白嫖)
|
|
1751
1444
|
for (const col of [
|
|
1752
1445
|
'ALTER TABLE product_trial_claims ADD COLUMN snap_reach_threshold INTEGER',
|
|
@@ -1763,29 +1456,8 @@ for (const col of [
|
|
|
1763
1456
|
}
|
|
1764
1457
|
catch { /* 已存在 */ }
|
|
1765
1458
|
}
|
|
1766
|
-
//
|
|
1767
|
-
|
|
1768
|
-
db.exec(`
|
|
1769
|
-
CREATE TABLE IF NOT EXISTS email_subscriptions (
|
|
1770
|
-
id TEXT PRIMARY KEY,
|
|
1771
|
-
email TEXT NOT NULL UNIQUE,
|
|
1772
|
-
source TEXT NOT NULL DEFAULT 'welcome',
|
|
1773
|
-
consent_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1774
|
-
unsubscribe_token TEXT NOT NULL UNIQUE,
|
|
1775
|
-
unsubscribed_at TEXT,
|
|
1776
|
-
ip_hash TEXT,
|
|
1777
|
-
user_id TEXT,
|
|
1778
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1779
|
-
)
|
|
1780
|
-
`);
|
|
1781
|
-
try {
|
|
1782
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_es_status ON email_subscriptions(unsubscribed_at, created_at DESC)");
|
|
1783
|
-
}
|
|
1784
|
-
catch { }
|
|
1785
|
-
try {
|
|
1786
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_es_source ON email_subscriptions(source, created_at DESC)");
|
|
1787
|
-
}
|
|
1788
|
-
catch { }
|
|
1459
|
+
// 邮箱订阅独立表(GDPR-ready)→ server-schema.ts;后续 ALTER 列扩展刻意留原位(紧跟下方)
|
|
1460
|
+
initEmailSubscriptionsSchema(db);
|
|
1789
1461
|
// 2026-05-26: 用户期望身份 + 备注(welcome 表单丰富化)
|
|
1790
1462
|
try {
|
|
1791
1463
|
db.exec("ALTER TABLE email_subscriptions ADD COLUMN role_preference TEXT");
|
|
@@ -1808,71 +1480,13 @@ try {
|
|
|
1808
1480
|
db.exec("ALTER TABLE email_subscriptions ADD COLUMN handled_by TEXT");
|
|
1809
1481
|
}
|
|
1810
1482
|
catch { }
|
|
1811
|
-
//
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
ip_hash TEXT,
|
|
1819
|
-
ua_hash TEXT,
|
|
1820
|
-
status TEXT NOT NULL DEFAULT 'new', -- new / triaged / resolved / spam
|
|
1821
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1822
|
-
)
|
|
1823
|
-
`);
|
|
1824
|
-
try {
|
|
1825
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_pi_status ON public_ideas(status, created_at DESC)");
|
|
1826
|
-
}
|
|
1827
|
-
catch { }
|
|
1828
|
-
try {
|
|
1829
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_pi_rate ON public_ideas(ip_hash, created_at)");
|
|
1830
|
-
}
|
|
1831
|
-
catch { }
|
|
1832
|
-
// 2026-05-24 #959: 拍卖「⏰ 提醒我」
|
|
1833
|
-
// 买家订阅拍卖,cron 在 deadline - lead_minutes 时发通知;1 个订阅 = 多行(每个 lead 时间一行)
|
|
1834
|
-
// 默认订阅 = [60, 10](结束前 1h + 10min 各提醒一次)
|
|
1835
|
-
db.exec(`
|
|
1836
|
-
CREATE TABLE IF NOT EXISTS auction_reminders (
|
|
1837
|
-
id TEXT PRIMARY KEY, -- arm_xxxx
|
|
1838
|
-
auction_id TEXT NOT NULL REFERENCES auctions(id),
|
|
1839
|
-
user_id TEXT NOT NULL REFERENCES users(id),
|
|
1840
|
-
lead_minutes INTEGER NOT NULL, -- 提前多少分钟提醒
|
|
1841
|
-
fire_at TEXT NOT NULL, -- deadline - lead_minutes(创建时算好)
|
|
1842
|
-
sent_at TEXT,
|
|
1843
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1844
|
-
UNIQUE(auction_id, user_id, lead_minutes)
|
|
1845
|
-
)
|
|
1846
|
-
`);
|
|
1847
|
-
try {
|
|
1848
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_arm_due ON auction_reminders(sent_at, fire_at) WHERE sent_at IS NULL");
|
|
1849
|
-
}
|
|
1850
|
-
catch { }
|
|
1851
|
-
try {
|
|
1852
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_arm_user ON auction_reminders(user_id, auction_id)");
|
|
1853
|
-
}
|
|
1854
|
-
catch { }
|
|
1855
|
-
// Wave D-3: 用户反馈 / 客服工单(buyer-to-platform,独立于 disputes)
|
|
1856
|
-
db.exec(`
|
|
1857
|
-
CREATE TABLE IF NOT EXISTS feedback_tickets (
|
|
1858
|
-
id TEXT PRIMARY KEY,
|
|
1859
|
-
user_id TEXT NOT NULL,
|
|
1860
|
-
category TEXT NOT NULL, -- 'bug' | 'abuse' | 'feature' | 'account' | 'other'
|
|
1861
|
-
severity TEXT DEFAULT 'medium', -- 'low' | 'medium' | 'high'
|
|
1862
|
-
subject TEXT NOT NULL,
|
|
1863
|
-
body TEXT NOT NULL,
|
|
1864
|
-
status TEXT NOT NULL DEFAULT 'open', -- open | in_progress | resolved | closed
|
|
1865
|
-
admin_reply TEXT,
|
|
1866
|
-
replied_by TEXT,
|
|
1867
|
-
replied_at TEXT,
|
|
1868
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1869
|
-
updated_at TEXT DEFAULT (datetime('now'))
|
|
1870
|
-
)
|
|
1871
|
-
`);
|
|
1872
|
-
try {
|
|
1873
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_feedback_user ON feedback_tickets(user_id, created_at DESC)');
|
|
1874
|
-
}
|
|
1875
|
-
catch { }
|
|
1483
|
+
// 首屏「我有建议」公开收集 / #959 拍卖「⏰ 提醒我」 → server-schema.ts
|
|
1484
|
+
// 纯幂等建表/建索引 DDL,原位调用保持 boot 顺序不变(email_subscriptions 仍留原位)
|
|
1485
|
+
initPublicIdeasSchema(db);
|
|
1486
|
+
initAuctionRemindersSchema(db);
|
|
1487
|
+
// Wave D-3: 用户反馈 / 客服工单(buyer-to-platform,独立于 disputes)→ server-schema.ts
|
|
1488
|
+
// 后续 ALTER 列扩展刻意留原位(紧跟下方)
|
|
1489
|
+
initFeedbackTicketsSchema(db);
|
|
1876
1490
|
// G-4: AI 建议回复
|
|
1877
1491
|
try {
|
|
1878
1492
|
db.exec('ALTER TABLE feedback_tickets ADD COLUMN ai_suggested_reply TEXT');
|
|
@@ -1891,21 +1505,9 @@ try {
|
|
|
1891
1505
|
db.exec('ALTER TABLE feedback_tickets ADD COLUMN admin_seen_at TEXT');
|
|
1892
1506
|
}
|
|
1893
1507
|
catch { }
|
|
1894
|
-
// W7 客服 ticket-thread — 多轮消息(user ↔ admin
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
id TEXT PRIMARY KEY, -- fmsg_xxx
|
|
1898
|
-
ticket_id TEXT NOT NULL,
|
|
1899
|
-
sender_id TEXT NOT NULL,
|
|
1900
|
-
sender_role TEXT NOT NULL, -- 'user' | 'admin'
|
|
1901
|
-
body TEXT NOT NULL,
|
|
1902
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1903
|
-
)
|
|
1904
|
-
`);
|
|
1905
|
-
try {
|
|
1906
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_fmsg_ticket ON feedback_messages(ticket_id, created_at)');
|
|
1907
|
-
}
|
|
1908
|
-
catch { }
|
|
1508
|
+
// W7 客服 ticket-thread — 多轮消息(user ↔ admin)→ server-schema.ts
|
|
1509
|
+
// 后续 ALTER 列扩展刻意留原位(紧跟下方)
|
|
1510
|
+
initFeedbackMessagesSchema(db);
|
|
1909
1511
|
// 跨窗反诈一致性:所有 thread 消息表加 flagged + flag_reasons
|
|
1910
1512
|
try {
|
|
1911
1513
|
db.exec('ALTER TABLE feedback_messages ADD COLUMN flagged INTEGER DEFAULT 0');
|
|
@@ -1916,50 +1518,10 @@ try {
|
|
|
1916
1518
|
}
|
|
1917
1519
|
catch { }
|
|
1918
1520
|
// ─── 公开仲裁判例 (P1) ─────────────────────────────────────
|
|
1919
|
-
// disputes
|
|
1920
|
-
db
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
dispute_id TEXT, -- 原始 disputes.id (内部追溯)
|
|
1924
|
-
order_id TEXT,
|
|
1925
|
-
product_id TEXT, -- 关键索引:按商品查公开判例
|
|
1926
|
-
seller_id TEXT,
|
|
1927
|
-
buyer_id TEXT, -- 仅内部使用,不外露
|
|
1928
|
-
category_tag TEXT, -- 物流 / 质量 / 描述不符 / 售后 / 拒收 / 其他
|
|
1929
|
-
winner TEXT, -- buyer / seller / split / dismissed
|
|
1930
|
-
resolution TEXT, -- 简短人读判决 (如 '全额退款')
|
|
1931
|
-
amount_bucket TEXT, -- '0-100' / '100-500' / '500-2000' / '2000+' WAZ
|
|
1932
|
-
buyer_argument TEXT, -- 脱敏后买家陈述
|
|
1933
|
-
seller_argument TEXT, -- 脱敏后卖家陈述
|
|
1934
|
-
ruling_text TEXT, -- 仲裁员判决书
|
|
1935
|
-
arbitrator_id TEXT,
|
|
1936
|
-
fairness_yes INTEGER DEFAULT 0,
|
|
1937
|
-
fairness_no INTEGER DEFAULT 0,
|
|
1938
|
-
comment_count INTEGER DEFAULT 0,
|
|
1939
|
-
published_at TEXT DEFAULT (datetime('now')),
|
|
1940
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1941
|
-
)
|
|
1942
|
-
`);
|
|
1943
|
-
try {
|
|
1944
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_dcase_product ON dispute_cases(product_id, published_at DESC)');
|
|
1945
|
-
}
|
|
1946
|
-
catch { }
|
|
1947
|
-
try {
|
|
1948
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_dcase_seller ON dispute_cases(seller_id, published_at DESC)');
|
|
1949
|
-
}
|
|
1950
|
-
catch { }
|
|
1951
|
-
db.exec(`
|
|
1952
|
-
CREATE TABLE IF NOT EXISTS dispute_comments (
|
|
1953
|
-
id TEXT PRIMARY KEY, -- dcom_xxx
|
|
1954
|
-
case_id TEXT NOT NULL,
|
|
1955
|
-
commenter_id TEXT NOT NULL,
|
|
1956
|
-
body TEXT NOT NULL,
|
|
1957
|
-
flagged INTEGER DEFAULT 0,
|
|
1958
|
-
likes INTEGER DEFAULT 0,
|
|
1959
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1960
|
-
UNIQUE(case_id, commenter_id) -- 一案一人一次(防刷)
|
|
1961
|
-
)
|
|
1962
|
-
`);
|
|
1521
|
+
// 公开判例(裁决后脱敏版本,disputes 是当事人/仲裁员私域)→ server-schema.ts
|
|
1522
|
+
initDisputeCasesSchema(db);
|
|
1523
|
+
// 公开判例评论 → server-schema.ts;anonymous ALTER + idx_dcom_case 刻意留原位
|
|
1524
|
+
initDisputeCommentsSchema(db);
|
|
1963
1525
|
try {
|
|
1964
1526
|
db.exec('ALTER TABLE dispute_comments ADD COLUMN anonymous INTEGER DEFAULT 0');
|
|
1965
1527
|
}
|
|
@@ -1968,27 +1530,8 @@ try {
|
|
|
1968
1530
|
db.exec('CREATE INDEX IF NOT EXISTS idx_dcom_case ON dispute_comments(case_id, created_at DESC)');
|
|
1969
1531
|
}
|
|
1970
1532
|
catch { }
|
|
1971
|
-
// W5 仲裁公开评论楼中楼 —
|
|
1972
|
-
db
|
|
1973
|
-
CREATE TABLE IF NOT EXISTS dispute_comment_replies (
|
|
1974
|
-
id TEXT PRIMARY KEY, -- drep_xxx
|
|
1975
|
-
parent_comment_id TEXT NOT NULL, -- 指向 dispute_comments.id
|
|
1976
|
-
case_id TEXT NOT NULL,
|
|
1977
|
-
replier_id TEXT NOT NULL,
|
|
1978
|
-
body TEXT NOT NULL,
|
|
1979
|
-
anonymous INTEGER DEFAULT 0,
|
|
1980
|
-
likes INTEGER DEFAULT 0,
|
|
1981
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1982
|
-
)
|
|
1983
|
-
`);
|
|
1984
|
-
try {
|
|
1985
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_drep_parent ON dispute_comment_replies(parent_comment_id, created_at)');
|
|
1986
|
-
}
|
|
1987
|
-
catch { }
|
|
1988
|
-
try {
|
|
1989
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_drep_case ON dispute_comment_replies(case_id, created_at DESC)');
|
|
1990
|
-
}
|
|
1991
|
-
catch { }
|
|
1533
|
+
// W5 仲裁公开评论楼中楼 — 单层子回复 → server-schema.ts;后续 ALTER 刻意留原位
|
|
1534
|
+
initDisputeCommentRepliesSchema(db);
|
|
1992
1535
|
// 跨窗反诈一致性
|
|
1993
1536
|
try {
|
|
1994
1537
|
db.exec('ALTER TABLE dispute_comment_replies ADD COLUMN flagged INTEGER DEFAULT 0');
|
|
@@ -2003,68 +1546,21 @@ try {
|
|
|
2003
1546
|
db.exec('ALTER TABLE dispute_comments ADD COLUMN flag_reasons TEXT');
|
|
2004
1547
|
}
|
|
2005
1548
|
catch { }
|
|
2006
|
-
// W6 笔记评论 — 原生 parent_id 楼中楼(仅 1
|
|
2007
|
-
db
|
|
2008
|
-
CREATE TABLE IF NOT EXISTS shareable_comments (
|
|
2009
|
-
id TEXT PRIMARY KEY, -- scom_xxx
|
|
2010
|
-
shareable_id TEXT NOT NULL, -- shareables.id
|
|
2011
|
-
commenter_id TEXT NOT NULL,
|
|
2012
|
-
parent_id TEXT, -- 子评论指向父评论;root = NULL
|
|
2013
|
-
body TEXT NOT NULL,
|
|
2014
|
-
flagged INTEGER DEFAULT 0,
|
|
2015
|
-
likes INTEGER DEFAULT 0,
|
|
2016
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
2017
|
-
)
|
|
2018
|
-
`);
|
|
2019
|
-
try {
|
|
2020
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_scom_shareable ON shareable_comments(shareable_id, parent_id, created_at DESC)');
|
|
2021
|
-
}
|
|
2022
|
-
catch { }
|
|
1549
|
+
// W6 笔记评论 — 原生 parent_id 楼中楼(仅 1 层)→ server-schema.ts;flag_reasons ALTER 刻意留原位
|
|
1550
|
+
initShareableCommentsSchema(db);
|
|
2023
1551
|
try {
|
|
2024
1552
|
db.exec('ALTER TABLE shareable_comments ADD COLUMN flag_reasons TEXT');
|
|
2025
1553
|
}
|
|
2026
1554
|
catch { }
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
case_id TEXT NOT NULL,
|
|
2030
|
-
voter_id TEXT NOT NULL,
|
|
2031
|
-
vote TEXT NOT NULL, -- 'yes' / 'no'
|
|
2032
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
2033
|
-
PRIMARY KEY (case_id, voter_id)
|
|
2034
|
-
)
|
|
2035
|
-
`);
|
|
1555
|
+
// 公开判例公平性投票 → server-schema.ts;idx_feedback_open 刻意留原位(非本表索引,不相邻)
|
|
1556
|
+
initDisputeFairnessVotesSchema(db);
|
|
2036
1557
|
try {
|
|
2037
1558
|
db.exec('CREATE INDEX IF NOT EXISTS idx_feedback_open ON feedback_tickets(status, created_at DESC) WHERE status IN (\'open\', \'in_progress\')');
|
|
2038
1559
|
}
|
|
2039
1560
|
catch { }
|
|
2040
|
-
// Wave C-3: 买家评价 / 评分
|
|
2041
|
-
db
|
|
2042
|
-
|
|
2043
|
-
order_id TEXT PRIMARY KEY,
|
|
2044
|
-
buyer_id TEXT NOT NULL,
|
|
2045
|
-
seller_id TEXT NOT NULL,
|
|
2046
|
-
product_id TEXT NOT NULL,
|
|
2047
|
-
stars INTEGER NOT NULL, -- 1-5
|
|
2048
|
-
comment TEXT,
|
|
2049
|
-
reply TEXT, -- seller 可回复
|
|
2050
|
-
replied_at TEXT,
|
|
2051
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
2052
|
-
)
|
|
2053
|
-
`);
|
|
2054
|
-
try {
|
|
2055
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_rating_seller ON order_ratings(seller_id, created_at DESC)');
|
|
2056
|
-
}
|
|
2057
|
-
catch { }
|
|
2058
|
-
try {
|
|
2059
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_rating_product ON order_ratings(product_id, created_at DESC)');
|
|
2060
|
-
}
|
|
2061
|
-
catch { }
|
|
2062
|
-
// P2 hot-path:覆盖 recommend_count 子查询(COUNT DISTINCT buyer_id WHERE product_id=? AND stars>=4)
|
|
2063
|
-
try {
|
|
2064
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_rating_recommend ON order_ratings(product_id, stars, buyer_id)');
|
|
2065
|
-
}
|
|
2066
|
-
catch { }
|
|
2067
|
-
// P2 hot-path:覆盖 sales_count 子查询(COUNT WHERE product_id=? AND status=completed)
|
|
1561
|
+
// Wave C-3: 买家评价 / 评分 → server-schema.ts;后续结构化维度 ALTER + 跨表 orders 索引刻意留原位
|
|
1562
|
+
initOrderRatingsSchema(db);
|
|
1563
|
+
// P2 hot-path:覆盖 sales_count 子查询(COUNT WHERE product_id=? AND status=completed)—— orders 表索引,留原位
|
|
2068
1564
|
try {
|
|
2069
1565
|
db.exec('CREATE INDEX IF NOT EXISTS idx_orders_product_status ON orders(product_id, status)');
|
|
2070
1566
|
}
|
|
@@ -2095,73 +1591,12 @@ try {
|
|
|
2095
1591
|
db.exec('ALTER TABLE order_ratings ADD COLUMN buyer_followup_at TEXT');
|
|
2096
1592
|
}
|
|
2097
1593
|
catch { }
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
comment TEXT,
|
|
2105
|
-
dim_payment_speed INTEGER,
|
|
2106
|
-
dim_communication INTEGER,
|
|
2107
|
-
dim_responsiveness INTEGER,
|
|
2108
|
-
hidden_until TEXT,
|
|
2109
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
2110
|
-
)
|
|
2111
|
-
`);
|
|
2112
|
-
try {
|
|
2113
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_buyer_ratings_buyer ON buyer_ratings(buyer_id, created_at DESC)');
|
|
2114
|
-
}
|
|
2115
|
-
catch { }
|
|
2116
|
-
// Wave C-2: 多收货地址簿 — buyer 保存常用地址,下单时选择默认地址
|
|
2117
|
-
db.exec(`
|
|
2118
|
-
CREATE TABLE IF NOT EXISTS user_addresses (
|
|
2119
|
-
id TEXT PRIMARY KEY,
|
|
2120
|
-
user_id TEXT NOT NULL,
|
|
2121
|
-
label TEXT NOT NULL, -- 标签(家 / 公司 / 父母家)
|
|
2122
|
-
recipient TEXT NOT NULL,
|
|
2123
|
-
phone TEXT,
|
|
2124
|
-
region TEXT, -- 省/市/区
|
|
2125
|
-
detail TEXT NOT NULL, -- 详细地址
|
|
2126
|
-
is_default INTEGER DEFAULT 0,
|
|
2127
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
2128
|
-
updated_at TEXT DEFAULT (datetime('now'))
|
|
2129
|
-
)
|
|
2130
|
-
`);
|
|
2131
|
-
try {
|
|
2132
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_addr_user ON user_addresses(user_id, is_default DESC)');
|
|
2133
|
-
}
|
|
2134
|
-
catch { }
|
|
2135
|
-
// Wave B-3: 退货请求 — 买家在 return_days 窗口内可发起,卖家可接受/拒绝;拒绝后可走 dispute
|
|
2136
|
-
db.exec(`
|
|
2137
|
-
CREATE TABLE IF NOT EXISTS return_requests (
|
|
2138
|
-
id TEXT PRIMARY KEY,
|
|
2139
|
-
order_id TEXT NOT NULL,
|
|
2140
|
-
buyer_id TEXT NOT NULL,
|
|
2141
|
-
seller_id TEXT NOT NULL,
|
|
2142
|
-
product_id TEXT NOT NULL,
|
|
2143
|
-
reason TEXT NOT NULL, -- 'quality' | 'wrong_item' | 'damaged' | 'no_longer_needed' | 'other'
|
|
2144
|
-
reason_text TEXT,
|
|
2145
|
-
refund_amount DECIMAL(18,2), -- 默认 = order.total_amount
|
|
2146
|
-
status TEXT NOT NULL DEFAULT 'pending', -- pending | accepted | rejected | refunded | escalated | cancelled
|
|
2147
|
-
seller_response TEXT,
|
|
2148
|
-
escalated_dispute_id TEXT,
|
|
2149
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
2150
|
-
resolved_at TEXT
|
|
2151
|
-
)
|
|
2152
|
-
`);
|
|
2153
|
-
try {
|
|
2154
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_returns_seller_pending ON return_requests(seller_id, status) WHERE status = \'pending\'');
|
|
2155
|
-
}
|
|
2156
|
-
catch { }
|
|
2157
|
-
try {
|
|
2158
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_returns_buyer ON return_requests(buyer_id, created_at)');
|
|
2159
|
-
}
|
|
2160
|
-
catch { }
|
|
2161
|
-
try {
|
|
2162
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_returns_order ON return_requests(order_id)');
|
|
2163
|
-
}
|
|
2164
|
-
catch { }
|
|
1594
|
+
// 反向评价:卖家给买家评分(双盲)→ server-schema.ts
|
|
1595
|
+
initBuyerRatingsSchema(db);
|
|
1596
|
+
// Wave C-2: 多收货地址簿 → server-schema.ts
|
|
1597
|
+
initUserAddressesSchema(db);
|
|
1598
|
+
// Wave B-3: 退货请求 → server-schema.ts;pickup ALTER 刻意留原位(紧跟下方)
|
|
1599
|
+
initReturnRequestsSchema(db);
|
|
2165
1600
|
// 2026-05-22 L3+B3:退货上门取件(MVP — 仅声明阶段)
|
|
2166
1601
|
// 完整状态机(accepted_pickup_pending → picked_up → refunded)留 Phase 2
|
|
2167
1602
|
try {
|
|
@@ -2172,21 +1607,8 @@ try {
|
|
|
2172
1607
|
db.exec('ALTER TABLE return_requests ADD COLUMN pickup_address TEXT');
|
|
2173
1608
|
}
|
|
2174
1609
|
catch { }
|
|
2175
|
-
// W2 售后协商时间线 — 多轮消息(buyer ↔ seller
|
|
2176
|
-
db
|
|
2177
|
-
CREATE TABLE IF NOT EXISTS return_messages (
|
|
2178
|
-
id TEXT PRIMARY KEY, -- rmsg_xxx
|
|
2179
|
-
return_id TEXT NOT NULL,
|
|
2180
|
-
sender_id TEXT NOT NULL,
|
|
2181
|
-
sender_role TEXT NOT NULL, -- 'buyer' | 'seller' | 'system'
|
|
2182
|
-
body TEXT NOT NULL,
|
|
2183
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
2184
|
-
)
|
|
2185
|
-
`);
|
|
2186
|
-
try {
|
|
2187
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_rmsg_return ON return_messages(return_id, created_at)');
|
|
2188
|
-
}
|
|
2189
|
-
catch { }
|
|
1610
|
+
// W2 售后协商时间线 — 多轮消息(buyer ↔ seller)→ server-schema.ts;flagged/flag_reasons ALTER 刻意留原位(紧跟下方)
|
|
1611
|
+
initReturnMessagesSchema(db);
|
|
2190
1612
|
// 跨窗反诈一致性
|
|
2191
1613
|
try {
|
|
2192
1614
|
db.exec('ALTER TABLE return_messages ADD COLUMN flagged INTEGER DEFAULT 0');
|
|
@@ -2198,24 +1620,8 @@ try {
|
|
|
2198
1620
|
catch { }
|
|
2199
1621
|
// Wave B-1 Phase 1: 商品 variants(同款多 SKU — 颜色/尺寸/规格组合)
|
|
2200
1622
|
// schema + CRUD 端点;Phase 2 再集成订单/购物车
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
id TEXT PRIMARY KEY,
|
|
2204
|
-
product_id TEXT NOT NULL,
|
|
2205
|
-
sku TEXT, -- 卖家内部 SKU 编号(可选)
|
|
2206
|
-
options_json TEXT NOT NULL, -- {"颜色":"红","尺寸":"L"} 必填
|
|
2207
|
-
price_override REAL, -- null = 用 product.price
|
|
2208
|
-
stock INTEGER DEFAULT 0,
|
|
2209
|
-
images_json TEXT, -- variant 专属图(可选,null = 用 product.images)
|
|
2210
|
-
is_active INTEGER DEFAULT 1,
|
|
2211
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
2212
|
-
updated_at TEXT DEFAULT (datetime('now'))
|
|
2213
|
-
)
|
|
2214
|
-
`);
|
|
2215
|
-
try {
|
|
2216
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_pv_product ON product_variants(product_id, is_active)');
|
|
2217
|
-
}
|
|
2218
|
-
catch { }
|
|
1623
|
+
// Wave B-1: 商品 variants → server-schema.ts;has_variants/options_key ALTER + 回填 + uniq 索引刻意留原位(紧跟下方)
|
|
1624
|
+
initProductVariantsSchema(db);
|
|
2219
1625
|
// 给 products 加 has_variants 标记(避免每次查 join 检查)
|
|
2220
1626
|
try {
|
|
2221
1627
|
db.exec('ALTER TABLE products ADD COLUMN has_variants INTEGER DEFAULT 0');
|
|
@@ -2274,146 +1680,15 @@ try {
|
|
|
2274
1680
|
db.exec('CREATE INDEX IF NOT EXISTS idx_products_p2p ON products(p2p_mode, status)');
|
|
2275
1681
|
}
|
|
2276
1682
|
catch { }
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
2287
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
2288
|
-
updated_at TEXT DEFAULT (datetime('now'))
|
|
2289
|
-
)
|
|
2290
|
-
`);
|
|
2291
|
-
try {
|
|
2292
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_p2p_shops_owner ON p2p_shops(owner_id, status)');
|
|
2293
|
-
}
|
|
2294
|
-
catch { }
|
|
2295
|
-
db.exec(`
|
|
2296
|
-
CREATE TABLE IF NOT EXISTS shareable_likes (
|
|
2297
|
-
id TEXT PRIMARY KEY,
|
|
2298
|
-
shareable_id TEXT NOT NULL,
|
|
2299
|
-
user_id TEXT NOT NULL,
|
|
2300
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
2301
|
-
UNIQUE(shareable_id, user_id)
|
|
2302
|
-
)
|
|
2303
|
-
`);
|
|
2304
|
-
try {
|
|
2305
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_shr_likes_shr ON shareable_likes(shareable_id)');
|
|
2306
|
-
}
|
|
2307
|
-
catch { }
|
|
2308
|
-
try {
|
|
2309
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_shr_likes_user ON shareable_likes(user_id, created_at DESC)');
|
|
2310
|
-
}
|
|
2311
|
-
catch { }
|
|
2312
|
-
// 2026-05-22 audit P2:收藏功能(小红书风格"收藏" tab)
|
|
2313
|
-
db.exec(`
|
|
2314
|
-
CREATE TABLE IF NOT EXISTS shareable_bookmarks (
|
|
2315
|
-
id TEXT PRIMARY KEY,
|
|
2316
|
-
shareable_id TEXT NOT NULL,
|
|
2317
|
-
user_id TEXT NOT NULL,
|
|
2318
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
2319
|
-
UNIQUE(shareable_id, user_id)
|
|
2320
|
-
)
|
|
2321
|
-
`);
|
|
2322
|
-
try {
|
|
2323
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_shr_bm_user ON shareable_bookmarks(user_id, created_at DESC)');
|
|
2324
|
-
}
|
|
2325
|
-
catch { }
|
|
2326
|
-
try {
|
|
2327
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_shr_bm_shr ON shareable_bookmarks(shareable_id)');
|
|
2328
|
-
}
|
|
2329
|
-
catch { }
|
|
2330
|
-
// 2026-05-22 audit P1 backlog:# 话题/标签系统(小红书风格内容分发)
|
|
2331
|
-
db.exec(`
|
|
2332
|
-
CREATE TABLE IF NOT EXISTS shareable_tags (
|
|
2333
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2334
|
-
shareable_id TEXT NOT NULL,
|
|
2335
|
-
tag TEXT NOT NULL, -- 已 lowercase + trim,最长 30 字符
|
|
2336
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
2337
|
-
UNIQUE(shareable_id, tag)
|
|
2338
|
-
)
|
|
2339
|
-
`);
|
|
2340
|
-
try {
|
|
2341
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_shr_tags_tag ON shareable_tags(tag, created_at DESC)');
|
|
2342
|
-
}
|
|
2343
|
-
catch { }
|
|
2344
|
-
try {
|
|
2345
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_shr_tags_shr ON shareable_tags(shareable_id)');
|
|
2346
|
-
}
|
|
2347
|
-
catch { }
|
|
2348
|
-
// manifest_registry = 原生 P2P 内容索引(仅 hash + 签名 + 元数据;字节在用户设备)
|
|
2349
|
-
db.exec(`
|
|
2350
|
-
CREATE TABLE IF NOT EXISTS manifest_registry (
|
|
2351
|
-
hash TEXT PRIMARY KEY,
|
|
2352
|
-
owner_id TEXT NOT NULL,
|
|
2353
|
-
content_type TEXT NOT NULL,
|
|
2354
|
-
byte_size INTEGER NOT NULL,
|
|
2355
|
-
title TEXT,
|
|
2356
|
-
description TEXT,
|
|
2357
|
-
thumbnail_data_uri TEXT,
|
|
2358
|
-
signature TEXT NOT NULL,
|
|
2359
|
-
signed_at TEXT NOT NULL,
|
|
2360
|
-
related_product_id TEXT,
|
|
2361
|
-
related_anchor TEXT,
|
|
2362
|
-
status TEXT DEFAULT 'active',
|
|
2363
|
-
takedown_reason TEXT,
|
|
2364
|
-
takedown_at TEXT,
|
|
2365
|
-
takedown_by TEXT,
|
|
2366
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
2367
|
-
)
|
|
2368
|
-
`);
|
|
2369
|
-
try {
|
|
2370
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_mfst_owner ON manifest_registry(owner_id, status)");
|
|
2371
|
-
}
|
|
2372
|
-
catch { }
|
|
2373
|
-
try {
|
|
2374
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_mfst_product ON manifest_registry(related_product_id, status)");
|
|
2375
|
-
}
|
|
2376
|
-
catch { }
|
|
2377
|
-
try {
|
|
2378
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_mfst_anchor ON manifest_registry(related_anchor, status)");
|
|
2379
|
-
}
|
|
2380
|
-
catch { }
|
|
2381
|
-
// peer_directory = 在线 peer 注册(哪些 user 持有哪些 hash 的 cache,heartbeat 5min 失效)
|
|
2382
|
-
db.exec(`
|
|
2383
|
-
CREATE TABLE IF NOT EXISTS peer_directory (
|
|
2384
|
-
peer_id TEXT NOT NULL,
|
|
2385
|
-
manifest_hash TEXT NOT NULL,
|
|
2386
|
-
is_owner INTEGER DEFAULT 0,
|
|
2387
|
-
pin_intent INTEGER DEFAULT 0,
|
|
2388
|
-
last_heartbeat TEXT NOT NULL,
|
|
2389
|
-
bytes_served_total INTEGER DEFAULT 0,
|
|
2390
|
-
PRIMARY KEY (peer_id, manifest_hash)
|
|
2391
|
-
)
|
|
2392
|
-
`);
|
|
2393
|
-
try {
|
|
2394
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_peer_hash ON peer_directory(manifest_hash, last_heartbeat DESC)");
|
|
2395
|
-
}
|
|
2396
|
-
catch { }
|
|
2397
|
-
try {
|
|
2398
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_peer_heartbeat ON peer_directory(last_heartbeat)");
|
|
2399
|
-
}
|
|
2400
|
-
catch { }
|
|
2401
|
-
// signaling_queue = WebRTC SDP/ICE 中继(TTL 2min,cron 清理)
|
|
2402
|
-
db.exec(`
|
|
2403
|
-
CREATE TABLE IF NOT EXISTS signaling_queue (
|
|
2404
|
-
id TEXT PRIMARY KEY,
|
|
2405
|
-
to_peer_id TEXT NOT NULL,
|
|
2406
|
-
from_peer_id TEXT NOT NULL,
|
|
2407
|
-
signal_type TEXT NOT NULL,
|
|
2408
|
-
signal_data TEXT NOT NULL,
|
|
2409
|
-
created_at TEXT NOT NULL,
|
|
2410
|
-
delivered_at TEXT
|
|
2411
|
-
)
|
|
2412
|
-
`);
|
|
2413
|
-
try {
|
|
2414
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_sig_to ON signaling_queue(to_peer_id, delivered_at)");
|
|
2415
|
-
}
|
|
2416
|
-
catch { }
|
|
1683
|
+
// P2P 店铺 / 笔记点赞·收藏·标签 / manifest·peer·signaling(原生 P2P 内容层)→ server-schema.ts
|
|
1684
|
+
// 纯幂等建表/建索引 DDL,原位调用保持 boot 顺序不变
|
|
1685
|
+
initP2pShopsSchema(db);
|
|
1686
|
+
initShareableLikesSchema(db);
|
|
1687
|
+
initShareableBookmarksSchema(db);
|
|
1688
|
+
initShareableTagsSchema(db);
|
|
1689
|
+
initManifestRegistrySchema(db);
|
|
1690
|
+
initPeerDirectorySchema(db);
|
|
1691
|
+
initSignalingQueueSchema(db);
|
|
2417
1692
|
// pin_receipts = 服务证明(pinner 给 recipient 传输 N bytes 时双签的回执)
|
|
2418
1693
|
// recipient 之后下单同商品 → settlePinRewards 从 basin 拨 0.5% 订单额分给最近 5 个 pinners
|
|
2419
1694
|
db.exec(`
|
|
@@ -2617,7 +1892,7 @@ catch { }
|
|
|
2617
1892
|
// 2026-06-04:佣金兜底科目从 charity_fund 拆出,慈善科目自此【纯净】只服务慈善许愿板块。
|
|
2618
1893
|
// 三级佣金中【无资格人 / 无资格 / 区域档位截断 / max=0 整池 / opt-out 放弃 / escrow 到期】
|
|
2619
1894
|
// 的部分,统一入此【独立科目】。定位 = 协议储备,**只进不出**,用途由治理(DAO/创始人)决定。
|
|
2620
|
-
// 与 global_fund(
|
|
1895
|
+
// 与 global_fund(由 1% base 注资的预留池;匹配奖励引擎已切除,当前无消费方) 互不流通 —— 三套科目彻底独立。
|
|
2621
1896
|
// 命名注意:本表是【持久储备科目】,区别于每单的 commission_pool/commissionPool(= total×rate 预算变量)。
|
|
2622
1897
|
// total_chain_gap — L2/L3 空缺(自发现/上家断链)
|
|
2623
1898
|
// total_orphan_sponsor — sponsor 被封/无资格 + opt-out 主动放弃 + escrow 到期(无合格受益人桶)
|
|
@@ -2796,7 +2071,7 @@ try {
|
|
|
2796
2071
|
db.exec("CREATE INDEX IF NOT EXISTS idx_cft_from_kind_time ON charity_fund_txns(from_user_id, kind, created_at DESC)");
|
|
2797
2072
|
}
|
|
2798
2073
|
catch { }
|
|
2799
|
-
//
|
|
2074
|
+
// 放置树 / PV 参与记录数据层(中性参与记录;匹配奖励引擎已切除)
|
|
2800
2075
|
for (const stmt of [
|
|
2801
2076
|
'ALTER TABLE users ADD COLUMN placement_id TEXT',
|
|
2802
2077
|
"ALTER TABLE users ADD COLUMN placement_side TEXT",
|
|
@@ -2895,14 +2170,20 @@ try {
|
|
|
2895
2170
|
db.exec('ALTER TABLE global_fund ADD COLUMN pv_escrow_reserve REAL DEFAULT 0');
|
|
2896
2171
|
}
|
|
2897
2172
|
catch { /* 已存在 */ }
|
|
2173
|
+
// 迁移:management_bonus_pool → protocol_reserve_pool(中性标识,去 comp-plan;保留既有余额)。
|
|
2174
|
+
// RENAME 必须在 CREATE IF NOT EXISTS 之前:旧库重命名保余额;新库无旧表→ALTER 抛错被吞,由下方 CREATE 建新表。
|
|
2175
|
+
try {
|
|
2176
|
+
db.exec('ALTER TABLE management_bonus_pool RENAME TO protocol_reserve_pool');
|
|
2177
|
+
}
|
|
2178
|
+
catch { /* 已迁移 / 全新库 */ }
|
|
2898
2179
|
db.exec(`
|
|
2899
|
-
CREATE TABLE IF NOT EXISTS
|
|
2180
|
+
CREATE TABLE IF NOT EXISTS protocol_reserve_pool (
|
|
2900
2181
|
id INTEGER PRIMARY KEY CHECK(id=1),
|
|
2901
2182
|
balance REAL DEFAULT 0
|
|
2902
2183
|
)
|
|
2903
2184
|
`);
|
|
2904
2185
|
try {
|
|
2905
|
-
db.prepare('INSERT OR IGNORE INTO
|
|
2186
|
+
db.prepare('INSERT OR IGNORE INTO protocol_reserve_pool (id) VALUES (1)').run();
|
|
2906
2187
|
}
|
|
2907
2188
|
catch { }
|
|
2908
2189
|
db.exec(`
|
|
@@ -2911,95 +2192,24 @@ db.exec(`
|
|
|
2911
2192
|
pv_threshold REAL NOT NULL,
|
|
2912
2193
|
score_per_hit REAL NOT NULL,
|
|
2913
2194
|
active INTEGER DEFAULT 1
|
|
2914
|
-
)
|
|
2915
|
-
`);
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
]
|
|
2923
|
-
try {
|
|
2924
|
-
db.prepare("INSERT OR IGNORE INTO binary_tier_config (tier, pv_threshold, score_per_hit) VALUES (?,?,?)").run(tier, threshold, score);
|
|
2925
|
-
}
|
|
2926
|
-
catch { }
|
|
2927
|
-
});
|
|
2928
|
-
// ─── 7档级差对碰升级(资金/PV 解耦控盘版)─────────────────────
|
|
2929
|
-
// 资金流:1% 永远入基金池;区域 max_levels<3 时 L3 分润那份也回流入池
|
|
2930
|
-
// PV 流:order.total × product.category.pv_multiplier(防交叉补贴)
|
|
2931
|
-
// 对碰:7 档阶梯门槛 300→33000,折扣系数 1.00→0.70;成功匹配单次双侧全清
|
|
2932
|
-
// 安全阀:30%/50%/70% 拨出率 × pool;100 元/点 cap;剩余沉淀
|
|
2933
|
-
for (const stmt of [
|
|
2934
|
-
'ALTER TABLE binary_tier_config ADD COLUMN base_score REAL',
|
|
2935
|
-
'ALTER TABLE binary_tier_config ADD COLUMN discount_coef REAL DEFAULT 1.0',
|
|
2936
|
-
]) {
|
|
2937
|
-
try {
|
|
2938
|
-
db.exec(stmt);
|
|
2939
|
-
}
|
|
2940
|
-
catch { }
|
|
2941
|
-
}
|
|
2942
|
-
// V3 升级(方案 D · 强激励):score_per_hit 直接 = 元(不再 base × coef 中间步骤)
|
|
2943
|
-
// PV 阈值跳跃 2.3–2.5x 平均 → 完美对碰拨比 1.20,实际拨比 ~1.5(考虑 min/max 不平衡)
|
|
2944
|
-
// 实际沉淀率 ~26%(vs 旧方案 28%),但 T7 门槛大幅降低 → 推广友好度 5×
|
|
2945
|
-
//
|
|
2946
|
-
// 升级触发:用独立 system_state.tokenomics_version 标志,幂等 + 防 admin 改 tier 后误触发
|
|
2947
|
-
// (之前用 T7 score_per_hit 检测,admin 改值会导致重启时重复 ×100 score —— 严重 bug)
|
|
2948
|
-
// 提前 CREATE 一次 system_state(line 1723 之前),不影响后续幂等创建
|
|
2949
|
-
try {
|
|
2950
|
-
db.exec("CREATE TABLE IF NOT EXISTS system_state (key TEXT PRIMARY KEY, value TEXT)");
|
|
2951
|
-
}
|
|
2952
|
-
catch { }
|
|
2953
|
-
const _currentTokenomicsVersion = (() => {
|
|
2954
|
-
try {
|
|
2955
|
-
const row = db.prepare("SELECT value FROM system_state WHERE key = 'tokenomics_version'").get();
|
|
2956
|
-
return row?.value ?? null;
|
|
2957
|
-
}
|
|
2958
|
-
catch {
|
|
2959
|
-
return null;
|
|
2960
|
-
}
|
|
2961
|
-
})();
|
|
2962
|
-
const _needsTierV3Seed = _currentTokenomicsVersion !== 'v3';
|
|
2963
|
-
if (_needsTierV3Seed) {
|
|
2964
|
-
try {
|
|
2965
|
-
db.prepare("DELETE FROM binary_tier_config").run();
|
|
2966
|
-
}
|
|
2967
|
-
catch { }
|
|
2968
|
-
;
|
|
2969
|
-
[
|
|
2970
|
-
// [tier, pv_threshold, score (= 元)]
|
|
2971
|
-
[1, 300, 100],
|
|
2972
|
-
[2, 700, 200],
|
|
2973
|
-
[3, 1600, 400],
|
|
2974
|
-
[4, 3800, 800],
|
|
2975
|
-
[5, 9000, 1600],
|
|
2976
|
-
[6, 22000, 3200],
|
|
2977
|
-
[7, 55000, 6400],
|
|
2978
|
-
].forEach(([tier, threshold, score]) => {
|
|
2979
|
-
try {
|
|
2980
|
-
db.prepare(`INSERT INTO binary_tier_config (tier, pv_threshold, base_score, discount_coef, score_per_hit, active)
|
|
2981
|
-
VALUES (?,?,?,?,?,1)`).run(tier, threshold, score, 1.0, score);
|
|
2982
|
-
}
|
|
2983
|
-
catch { }
|
|
2984
|
-
});
|
|
2985
|
-
// V3 迁移:旧 score (1, 1.9, 3.6, ...) 是基于 cap=100 设计的;新规则 cap=1.0
|
|
2986
|
-
// 所有未结算 score × 100,保证用户应得金额不变(旧 score×100 + 新 cap1.0 = 旧 score×100)
|
|
2987
|
-
// 仅在 version 从 null/旧值 → 'v3' 时执行一次,admin 后续改 tier 不重触发
|
|
2988
|
-
if (_currentTokenomicsVersion !== 'v3') {
|
|
2989
|
-
try {
|
|
2990
|
-
db.prepare("UPDATE binary_score_records SET score = score * 100 WHERE settled_at IS NULL").run();
|
|
2991
|
-
}
|
|
2992
|
-
catch (e) {
|
|
2993
|
-
console.error('[V3 score migration]', e);
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2195
|
+
)
|
|
2196
|
+
`);
|
|
2197
|
+
// binary_tier_config 保留为【预留空表 / dormant structure】—— 不 seed 任何档位 / 阈值 / 分数参数。
|
|
2198
|
+
// 匹配奖励引擎已切除(#401);若未来经法律 / 治理放行重启奖励功能,再按届时合规设计填充。
|
|
2199
|
+
// (base_score / discount_coef 列仅为历史 schema 兼容保留,不写入)
|
|
2200
|
+
for (const stmt of [
|
|
2201
|
+
'ALTER TABLE binary_tier_config ADD COLUMN base_score REAL',
|
|
2202
|
+
'ALTER TABLE binary_tier_config ADD COLUMN discount_coef REAL DEFAULT 1.0',
|
|
2203
|
+
]) {
|
|
2996
2204
|
try {
|
|
2997
|
-
db.
|
|
2998
|
-
}
|
|
2999
|
-
catch (e) {
|
|
3000
|
-
console.error('[V3 version flag]', e);
|
|
2205
|
+
db.exec(stmt);
|
|
3001
2206
|
}
|
|
2207
|
+
catch { }
|
|
2208
|
+
}
|
|
2209
|
+
try {
|
|
2210
|
+
db.exec("CREATE TABLE IF NOT EXISTS system_state (key TEXT PRIMARY KEY, value TEXT)");
|
|
3002
2211
|
}
|
|
2212
|
+
catch { }
|
|
3003
2213
|
// 商品类目(PV 乘数 = 资金/PV 解耦核心)
|
|
3004
2214
|
db.exec(`
|
|
3005
2215
|
CREATE TABLE IF NOT EXISTS product_categories (
|
|
@@ -3226,56 +2436,10 @@ try {
|
|
|
3226
2436
|
db.exec('CREATE INDEX IF NOT EXISTS idx_aucbids_buyer ON auction_bids(buyer_id, status, submitted_at DESC)');
|
|
3227
2437
|
}
|
|
3228
2438
|
catch { }
|
|
3229
|
-
// CHAT — 上下文绑定聊天(order / rfq / listing_qa
|
|
3230
|
-
db
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
kind TEXT NOT NULL,
|
|
3234
|
-
context_id TEXT NOT NULL,
|
|
3235
|
-
user_a TEXT NOT NULL,
|
|
3236
|
-
user_b TEXT NOT NULL,
|
|
3237
|
-
last_message_at TEXT,
|
|
3238
|
-
last_preview TEXT,
|
|
3239
|
-
unread_a INTEGER NOT NULL DEFAULT 0,
|
|
3240
|
-
unread_b INTEGER NOT NULL DEFAULT 0,
|
|
3241
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
3242
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
3243
|
-
UNIQUE(kind, context_id, user_a, user_b)
|
|
3244
|
-
)
|
|
3245
|
-
`);
|
|
3246
|
-
try {
|
|
3247
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_conv_a ON conversations(user_a, last_message_at DESC)');
|
|
3248
|
-
}
|
|
3249
|
-
catch { }
|
|
3250
|
-
try {
|
|
3251
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_conv_b ON conversations(user_b, last_message_at DESC)');
|
|
3252
|
-
}
|
|
3253
|
-
catch { }
|
|
3254
|
-
try {
|
|
3255
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_conv_ctx ON conversations(kind, context_id)');
|
|
3256
|
-
}
|
|
3257
|
-
catch { }
|
|
3258
|
-
db.exec(`
|
|
3259
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
3260
|
-
id TEXT PRIMARY KEY,
|
|
3261
|
-
conversation_id TEXT NOT NULL,
|
|
3262
|
-
sender_id TEXT NOT NULL,
|
|
3263
|
-
body TEXT NOT NULL DEFAULT '',
|
|
3264
|
-
attachments TEXT,
|
|
3265
|
-
flagged INTEGER NOT NULL DEFAULT 0,
|
|
3266
|
-
flag_reasons TEXT,
|
|
3267
|
-
read_at TEXT,
|
|
3268
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
3269
|
-
)
|
|
3270
|
-
`);
|
|
3271
|
-
try {
|
|
3272
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_msg_conv ON messages(conversation_id, created_at)');
|
|
3273
|
-
}
|
|
3274
|
-
catch { }
|
|
3275
|
-
try {
|
|
3276
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_msg_sender ON messages(sender_id, created_at DESC)');
|
|
3277
|
-
}
|
|
3278
|
-
catch { }
|
|
2439
|
+
// CHAT — 上下文绑定聊天(order / rfq / listing_qa)→ server-schema.ts
|
|
2440
|
+
initConversationsSchema(db);
|
|
2441
|
+
// 聊天消息 → server-schema.ts;kind/meta ALTER 刻意留原位(紧跟下方)
|
|
2442
|
+
initMessagesSchema(db);
|
|
3279
2443
|
// W1 私信结构化消息:kind = 'text' | 'offer' | 'tracking';meta = JSON payload
|
|
3280
2444
|
try {
|
|
3281
2445
|
db.exec("ALTER TABLE messages ADD COLUMN kind TEXT DEFAULT 'text'");
|
|
@@ -3285,24 +2449,8 @@ try {
|
|
|
3285
2449
|
db.exec('ALTER TABLE messages ADD COLUMN meta TEXT');
|
|
3286
2450
|
}
|
|
3287
2451
|
catch { }
|
|
3288
|
-
// 反诈举报表(chat report →
|
|
3289
|
-
db
|
|
3290
|
-
CREATE TABLE IF NOT EXISTS chat_reports (
|
|
3291
|
-
id TEXT PRIMARY KEY,
|
|
3292
|
-
conversation_id TEXT NOT NULL,
|
|
3293
|
-
message_id TEXT,
|
|
3294
|
-
reporter_id TEXT NOT NULL,
|
|
3295
|
-
reported_id TEXT NOT NULL,
|
|
3296
|
-
reason TEXT NOT NULL,
|
|
3297
|
-
note TEXT,
|
|
3298
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
3299
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
3300
|
-
)
|
|
3301
|
-
`);
|
|
3302
|
-
try {
|
|
3303
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_chatrpt_status ON chat_reports(status, created_at)');
|
|
3304
|
-
}
|
|
3305
|
-
catch { }
|
|
2452
|
+
// 反诈举报表(chat report → 人工审核)→ server-schema.ts
|
|
2453
|
+
initChatReportsSchema(db);
|
|
3306
2454
|
// 基金池入池流水(depositToFund 审计 + 4 周历史均值数据源)
|
|
3307
2455
|
db.exec(`
|
|
3308
2456
|
CREATE TABLE IF NOT EXISTS fund_deposits (
|
|
@@ -3347,7 +2495,7 @@ try {
|
|
|
3347
2495
|
db.exec('CREATE INDEX IF NOT EXISTS idx_settle_periods_status ON settlement_periods(status, started_at)');
|
|
3348
2496
|
}
|
|
3349
2497
|
catch { }
|
|
3350
|
-
//
|
|
2498
|
+
// 休眠:管理津贴 payout 已随匹配引擎切除(#401);列 + state 作为休眠结构保留(可逆,默认关闭、无消费方)。
|
|
3351
2499
|
try {
|
|
3352
2500
|
db.exec("ALTER TABLE users ADD COLUMN mgmt_bonus_eligible INTEGER DEFAULT 0");
|
|
3353
2501
|
}
|
|
@@ -3562,39 +2710,15 @@ try {
|
|
|
3562
2710
|
catch (e) {
|
|
3563
2711
|
console.warn('[D1b] migration', e);
|
|
3564
2712
|
}
|
|
3565
|
-
// 邀请码轮询:5 用户固定列表(按 handle),admin 可切开关;
|
|
3566
|
-
// 默认关闭,前端按钮置灰。开启后访客点 "获取邀请码" → 按"自纠偏轮询"派号。
|
|
3567
|
-
// 算法:
|
|
3568
|
-
// · 每槽维护 issued_count / registered_count
|
|
3569
|
-
// · max_reg - min_reg ≥ 3(不均衡)→ 发 registered 最少的(补齐)
|
|
3570
|
-
// · 否则 → 发 issued 最少的(顺序均分)
|
|
3571
|
-
// · 点击即 issued++;注册成功 → registered++
|
|
3572
|
-
try {
|
|
3573
|
-
db.prepare("INSERT OR IGNORE INTO system_state (key, value) VALUES ('invite_rotation_enabled', '0')").run();
|
|
3574
|
-
}
|
|
3575
|
-
catch { }
|
|
3576
|
-
try {
|
|
3577
|
-
db.exec(`CREATE TABLE IF NOT EXISTS invite_rotation_stats (
|
|
3578
|
-
slot INTEGER PRIMARY KEY,
|
|
3579
|
-
issued_count INTEGER NOT NULL DEFAULT 0,
|
|
3580
|
-
registered_count INTEGER NOT NULL DEFAULT 0
|
|
3581
|
-
)`);
|
|
3582
|
-
for (let i = 0; i < 5; i++) {
|
|
3583
|
-
db.prepare("INSERT OR IGNORE INTO invite_rotation_stats (slot) VALUES (?)").run(i);
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3586
|
-
catch (e) {
|
|
3587
|
-
console.error('[invite-rotation schema]', e);
|
|
3588
|
-
}
|
|
3589
2713
|
// 让 sys_protocol 可作为公库 sponsor(孤儿注册时分润自动归公库)
|
|
3590
2714
|
try {
|
|
3591
2715
|
db.prepare("UPDATE users SET l1_share_override = 1 WHERE id = 'sys_protocol'").run();
|
|
3592
2716
|
}
|
|
3593
2717
|
catch { }
|
|
3594
2718
|
// M7.2.6 + 2026-05-21 PV 合规扩展:按全球各国监管态度分档
|
|
3595
|
-
// 详细法律依据 + 风险评估见 docs/
|
|
2719
|
+
// 详细法律依据 + 风险评估见 docs/PARTICIPATION-ATTRIBUTION-COMPLIANCE.md
|
|
3596
2720
|
//
|
|
3597
|
-
// max_levels=0(完全禁 MLM,整池入
|
|
2721
|
+
// max_levels=0(完全禁 MLM,整池入 commission_reserve):
|
|
3598
2722
|
// GCC 国家 / 伊朗 / 朝鲜 / 缅甸 — 法律完全禁止任何下线计酬
|
|
3599
2723
|
// max_levels=1(仅 L1,类联盟营销):
|
|
3600
2724
|
// 越南 / 印尼 / 菲律宾 — 严格 license 制度,多级风险高
|
|
@@ -3691,68 +2815,13 @@ for (const stmt of [
|
|
|
3691
2815
|
}
|
|
3692
2816
|
catch { }
|
|
3693
2817
|
}
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
status TEXT DEFAULT 'pending',
|
|
3702
|
-
applied_at TEXT DEFAULT (datetime('now')),
|
|
3703
|
-
reviewed_at TEXT,
|
|
3704
|
-
reviewed_by TEXT,
|
|
3705
|
-
decision_note TEXT
|
|
3706
|
-
)
|
|
3707
|
-
`);
|
|
3708
|
-
try {
|
|
3709
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_quota_apps_status ON quota_increase_applications(status)');
|
|
3710
|
-
}
|
|
3711
|
-
catch { }
|
|
3712
|
-
// Verifier 申请记录
|
|
3713
|
-
db.exec(`
|
|
3714
|
-
CREATE TABLE IF NOT EXISTS verifier_applications (
|
|
3715
|
-
id TEXT PRIMARY KEY,
|
|
3716
|
-
user_id TEXT NOT NULL,
|
|
3717
|
-
status TEXT DEFAULT 'pending',
|
|
3718
|
-
applied_at TEXT DEFAULT (datetime('now')),
|
|
3719
|
-
reviewed_at TEXT,
|
|
3720
|
-
reviewed_by TEXT,
|
|
3721
|
-
decision_note TEXT,
|
|
3722
|
-
snapshot TEXT
|
|
3723
|
-
)
|
|
3724
|
-
`);
|
|
3725
|
-
try {
|
|
3726
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_verifier_apps_status ON verifier_applications(status)');
|
|
3727
|
-
}
|
|
3728
|
-
catch { }
|
|
3729
|
-
// Arbitrator 申请 + 白名单(外部仲裁员路径 — 与 verifier 平行)
|
|
3730
|
-
// 内部仲裁员:role='arbitrator'(admin 通过 /admin/admins 创建,自动 is_system=1)
|
|
3731
|
-
// 外部仲裁员:role='buyer' + arbitrator_whitelist 行(buyer 申请→admin 批准)
|
|
3732
|
-
db.exec(`
|
|
3733
|
-
CREATE TABLE IF NOT EXISTS arbitrator_applications (
|
|
3734
|
-
id TEXT PRIMARY KEY,
|
|
3735
|
-
user_id TEXT NOT NULL,
|
|
3736
|
-
status TEXT DEFAULT 'pending',
|
|
3737
|
-
applied_at TEXT DEFAULT (datetime('now')),
|
|
3738
|
-
reviewed_at TEXT,
|
|
3739
|
-
reviewed_by TEXT,
|
|
3740
|
-
decision_note TEXT,
|
|
3741
|
-
snapshot TEXT
|
|
3742
|
-
);
|
|
3743
|
-
CREATE TABLE IF NOT EXISTS arbitrator_whitelist (
|
|
3744
|
-
user_id TEXT PRIMARY KEY,
|
|
3745
|
-
added_at TEXT DEFAULT (datetime('now')),
|
|
3746
|
-
note TEXT,
|
|
3747
|
-
is_system INTEGER DEFAULT 0,
|
|
3748
|
-
granted_by TEXT,
|
|
3749
|
-
stake_amount INTEGER DEFAULT 0
|
|
3750
|
-
)
|
|
3751
|
-
`);
|
|
3752
|
-
try {
|
|
3753
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_arb_apps_status ON arbitrator_applications(status)');
|
|
3754
|
-
}
|
|
3755
|
-
catch { }
|
|
2818
|
+
// 配额提升申请 → server-schema.ts
|
|
2819
|
+
initQuotaIncreaseApplicationsSchema(db);
|
|
2820
|
+
// Verifier 申请记录 → server-schema.ts
|
|
2821
|
+
initVerifierApplicationsSchema(db);
|
|
2822
|
+
// Arbitrator 申请 + 白名单(外部仲裁员路径)→ server-schema.ts
|
|
2823
|
+
// legacy 内部仲裁员 → 白名单的 migration INSERT 刻意留原位(紧跟下方)
|
|
2824
|
+
initArbitratorReviewSchema(db);
|
|
3756
2825
|
// Migration:legacy 内部仲裁员 (role='arbitrator') → 自动加入白名单(is_system=1)
|
|
3757
2826
|
try {
|
|
3758
2827
|
db.prepare(`
|
|
@@ -3763,26 +2832,8 @@ try {
|
|
|
3763
2832
|
catch (e) {
|
|
3764
2833
|
console.warn('[arb migration]', e.message);
|
|
3765
2834
|
}
|
|
3766
|
-
// Verifier 申诉记录
|
|
3767
|
-
db
|
|
3768
|
-
CREATE TABLE IF NOT EXISTS verifier_appeals (
|
|
3769
|
-
id TEXT PRIMARY KEY,
|
|
3770
|
-
user_id TEXT NOT NULL,
|
|
3771
|
-
task_id TEXT,
|
|
3772
|
-
submission_id TEXT,
|
|
3773
|
-
reason TEXT NOT NULL,
|
|
3774
|
-
evidence_urls TEXT DEFAULT '[]',
|
|
3775
|
-
status TEXT DEFAULT 'pending',
|
|
3776
|
-
admin_note TEXT,
|
|
3777
|
-
reviewed_by TEXT,
|
|
3778
|
-
reviewed_at TEXT,
|
|
3779
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
3780
|
-
)
|
|
3781
|
-
`);
|
|
3782
|
-
try {
|
|
3783
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_verifier_appeals_status ON verifier_appeals(status)');
|
|
3784
|
-
}
|
|
3785
|
-
catch { }
|
|
2835
|
+
// Verifier 申诉记录 → server-schema.ts
|
|
2836
|
+
initVerifierAppealsSchema(db);
|
|
3786
2837
|
// 扩展 verifier_whitelist
|
|
3787
2838
|
for (const stmt of [
|
|
3788
2839
|
"ALTER TABLE verifier_whitelist ADD COLUMN tier TEXT DEFAULT 'active-2'", // 旧数据兼容:当满级
|
|
@@ -3805,34 +2856,14 @@ try {
|
|
|
3805
2856
|
db.prepare("UPDATE verifier_whitelist SET is_system = 1, tier = 'active-2', daily_quota = 9999 WHERE user_id = ?").run(INTERNAL_AUDITOR_ID);
|
|
3806
2857
|
}
|
|
3807
2858
|
catch { }
|
|
3808
|
-
// 用户暂停状态(admin
|
|
3809
|
-
db
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
)
|
|
3817
|
-
`);
|
|
3818
|
-
// admin 操作审计日志
|
|
3819
|
-
db.exec(`
|
|
3820
|
-
CREATE TABLE IF NOT EXISTS admin_audit_log (
|
|
3821
|
-
id TEXT PRIMARY KEY,
|
|
3822
|
-
admin_id TEXT NOT NULL,
|
|
3823
|
-
action TEXT NOT NULL,
|
|
3824
|
-
target_type TEXT,
|
|
3825
|
-
target_id TEXT,
|
|
3826
|
-
detail TEXT,
|
|
3827
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
3828
|
-
)
|
|
3829
|
-
`);
|
|
3830
|
-
try {
|
|
3831
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_admin_audit_log_created ON admin_audit_log(created_at)');
|
|
3832
|
-
}
|
|
3833
|
-
catch { }
|
|
3834
|
-
// Bootstrap admin(env BOOTSTRAP_ADMIN_NAME → 该用户升为 admin,幂等)
|
|
3835
|
-
;
|
|
2859
|
+
// 用户暂停状态(admin 管理)→ server-schema.ts
|
|
2860
|
+
initUserModerationSchema(db);
|
|
2861
|
+
// admin 操作审计日志 → server-schema.ts(initAdminCoordinationSchema FK 依赖本表,须先建)
|
|
2862
|
+
initAdminAuditLogSchema(db);
|
|
2863
|
+
// admin/agent coordination contribution — operator-claim + agent-mandate event logs + fact-source link
|
|
2864
|
+
// (schema only). Placed HERE because it FKs users + contribution_facts (both created above) AND
|
|
2865
|
+
// admin_audit_log (created just above). No ingestion runs at boot.
|
|
2866
|
+
initAdminCoordinationSchema(db);
|
|
3836
2867
|
(() => {
|
|
3837
2868
|
const bootName = process.env.BOOTSTRAP_ADMIN_NAME;
|
|
3838
2869
|
if (!bootName?.trim())
|
|
@@ -3855,41 +2886,16 @@ catch { }
|
|
|
3855
2886
|
.run(JSON.stringify(roles), u.id);
|
|
3856
2887
|
console.log(`[WebAZ] ✓ ${u.name} 已升级为 admin (bootstrap)`);
|
|
3857
2888
|
})();
|
|
3858
|
-
// 验证码表(邮箱绑定 / 找回密钥 / 改密码
|
|
3859
|
-
db
|
|
3860
|
-
CREATE TABLE IF NOT EXISTS verification_codes (
|
|
3861
|
-
id TEXT PRIMARY KEY,
|
|
3862
|
-
user_id TEXT NOT NULL,
|
|
3863
|
-
channel TEXT NOT NULL, -- 'email' / 'phone'
|
|
3864
|
-
target TEXT NOT NULL, -- 邮箱地址 / 手机号
|
|
3865
|
-
code TEXT NOT NULL, -- 6 位数字
|
|
3866
|
-
purpose TEXT NOT NULL, -- 'bind_email' / 'recover_key' / ...
|
|
3867
|
-
attempts INTEGER DEFAULT 0,
|
|
3868
|
-
used_at TEXT,
|
|
3869
|
-
expires_at TEXT NOT NULL,
|
|
3870
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
3871
|
-
)
|
|
3872
|
-
`);
|
|
3873
|
-
try {
|
|
3874
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_verification_codes_lookup ON verification_codes(channel, target, purpose)');
|
|
3875
|
-
}
|
|
3876
|
-
catch { }
|
|
2889
|
+
// 验证码表(邮箱绑定 / 找回密钥 / 改密码 等共用)→ server-schema.ts
|
|
2890
|
+
initVerificationCodesSchema(db);
|
|
3877
2891
|
const NEW_PRODUCT_COLS = [
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
2892
|
+
// specs/brand/model/source_price/ship_regions/handling_hours/estimated_days/
|
|
2893
|
+
// fragile/return_days/return_condition/warranty_days moved to
|
|
2894
|
+
// initRegisterListSearchColumns (single source, shared w/ MCP) — see ~line 494.
|
|
3881
2895
|
'ALTER TABLE products ADD COLUMN source_url TEXT',
|
|
3882
|
-
'ALTER TABLE products ADD COLUMN source_price REAL',
|
|
3883
2896
|
'ALTER TABLE products ADD COLUMN source_price_at TEXT',
|
|
3884
2897
|
'ALTER TABLE products ADD COLUMN weight_kg REAL',
|
|
3885
|
-
'ALTER TABLE products ADD COLUMN ship_regions TEXT DEFAULT "全国"',
|
|
3886
2898
|
'ALTER TABLE products ADD COLUMN excluded_regions TEXT',
|
|
3887
|
-
'ALTER TABLE products ADD COLUMN handling_hours INTEGER DEFAULT 24',
|
|
3888
|
-
'ALTER TABLE products ADD COLUMN estimated_days TEXT',
|
|
3889
|
-
'ALTER TABLE products ADD COLUMN fragile INTEGER DEFAULT 0',
|
|
3890
|
-
'ALTER TABLE products ADD COLUMN return_days INTEGER DEFAULT 7',
|
|
3891
|
-
'ALTER TABLE products ADD COLUMN return_condition TEXT',
|
|
3892
|
-
'ALTER TABLE products ADD COLUMN warranty_days INTEGER DEFAULT 0',
|
|
3893
2899
|
'ALTER TABLE products ADD COLUMN commitment_hash TEXT',
|
|
3894
2900
|
'ALTER TABLE products ADD COLUMN description_hash TEXT',
|
|
3895
2901
|
'ALTER TABLE products ADD COLUMN price_hash TEXT',
|
|
@@ -3993,20 +2999,12 @@ catch { }
|
|
|
3993
2999
|
})();
|
|
3994
3000
|
// ─── 里程碑 3:反操纵层 schema ─────────────────────────────────
|
|
3995
3001
|
try {
|
|
3996
|
-
db
|
|
3997
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3998
|
-
shareable_id TEXT NOT NULL,
|
|
3999
|
-
ip_hash TEXT NOT NULL,
|
|
4000
|
-
ua_hash TEXT NOT NULL,
|
|
4001
|
-
ref_path TEXT,
|
|
4002
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4003
|
-
)`);
|
|
4004
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_scl_share_ts ON shareable_click_log(shareable_id, created_at)`);
|
|
4005
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_scl_share_ipua ON shareable_click_log(shareable_id, ip_hash, ua_hash, created_at)`);
|
|
3002
|
+
initShareableClickLogSchema(db);
|
|
4006
3003
|
}
|
|
4007
3004
|
catch (e) {
|
|
4008
3005
|
console.error('[M3 schema scl]', e);
|
|
4009
3006
|
}
|
|
3007
|
+
// shareables ALTER 刻意留原位(scl init 之后、cal init 之前)
|
|
4010
3008
|
try {
|
|
4011
3009
|
db.exec('ALTER TABLE shareables ADD COLUMN unique_click_count INTEGER DEFAULT 0');
|
|
4012
3010
|
}
|
|
@@ -4016,137 +3014,42 @@ try {
|
|
|
4016
3014
|
}
|
|
4017
3015
|
catch { }
|
|
4018
3016
|
try {
|
|
4019
|
-
db
|
|
4020
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4021
|
-
order_id TEXT,
|
|
4022
|
-
buyer_id TEXT NOT NULL,
|
|
4023
|
-
seller_id TEXT NOT NULL,
|
|
4024
|
-
flag TEXT NOT NULL, -- 'sponsor_chain_cross' / 'self_in_chain'
|
|
4025
|
-
detail TEXT, -- JSON: { relation: 'buyer_ancestor_of_seller' | ..., path: '...' }
|
|
4026
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4027
|
-
)`);
|
|
4028
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_cal_buyer ON commission_audit_log(buyer_id, created_at)`);
|
|
3017
|
+
initCommissionAuditLogSchema(db);
|
|
4029
3018
|
}
|
|
4030
3019
|
catch (e) {
|
|
4031
3020
|
console.error('[M3 schema cal]', e);
|
|
4032
3021
|
}
|
|
4033
3022
|
try {
|
|
4034
|
-
db
|
|
4035
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4036
|
-
user_id TEXT NOT NULL,
|
|
4037
|
-
ip_hash TEXT NOT NULL,
|
|
4038
|
-
ua_hash TEXT NOT NULL,
|
|
4039
|
-
sponsor_id TEXT,
|
|
4040
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4041
|
-
)`);
|
|
4042
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_ral_ip_ts ON registration_audit_log(ip_hash, created_at)`);
|
|
3023
|
+
initRegistrationAuditLogSchema(db);
|
|
4043
3024
|
}
|
|
4044
3025
|
catch (e) {
|
|
4045
3026
|
console.error('[M3 schema ral]', e);
|
|
4046
3027
|
}
|
|
4047
|
-
// ─── 里程碑 4:Agent
|
|
3028
|
+
// ─── 里程碑 4:Agent observability/reputation schema → server-schema.ts ─
|
|
4048
3029
|
try {
|
|
4049
|
-
db
|
|
4050
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4051
|
-
api_key TEXT,
|
|
4052
|
-
user_id TEXT,
|
|
4053
|
-
endpoint TEXT NOT NULL,
|
|
4054
|
-
method TEXT,
|
|
4055
|
-
status_code INTEGER,
|
|
4056
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4057
|
-
)`);
|
|
4058
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_acl_apikey_ts ON agent_call_log(api_key, created_at)`);
|
|
4059
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_acl_user_ts ON agent_call_log(user_id, created_at)`);
|
|
3030
|
+
initAgentCallLogSchema(db);
|
|
4060
3031
|
}
|
|
4061
3032
|
catch (e) {
|
|
4062
3033
|
console.error('[M4 schema acl]', e);
|
|
4063
3034
|
}
|
|
4064
3035
|
try {
|
|
4065
|
-
db
|
|
4066
|
-
api_key TEXT PRIMARY KEY,
|
|
4067
|
-
user_id TEXT NOT NULL,
|
|
4068
|
-
trust_score REAL DEFAULT 0,
|
|
4069
|
-
level TEXT DEFAULT 'new',
|
|
4070
|
-
signals TEXT, -- JSON
|
|
4071
|
-
last_calculated_at TEXT,
|
|
4072
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4073
|
-
)`);
|
|
4074
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_ar_user ON agent_reputation(user_id)`);
|
|
3036
|
+
initAgentReputationSchema(db);
|
|
4075
3037
|
}
|
|
4076
3038
|
catch (e) {
|
|
4077
3039
|
console.error('[M4 schema ar]', e);
|
|
4078
3040
|
}
|
|
4079
|
-
// ─── 2026-05-23 Agent 治理(spec: docs/AGENT-GOVERNANCE.md
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
db
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
operator_contact TEXT NOT NULL, -- email/handle/DID
|
|
4087
|
-
purpose TEXT NOT NULL, -- ≤200 字
|
|
4088
|
-
declared_scope TEXT NOT NULL, -- JSON: {roles, actions, regions}
|
|
4089
|
-
attestations TEXT, -- JSON: {gdpr, kids_safe, no_pii_export, ...}
|
|
4090
|
-
repo_url TEXT,
|
|
4091
|
-
homepage TEXT,
|
|
4092
|
-
revoked_at TEXT, -- 撤销时间(用户主动 revoke)
|
|
4093
|
-
revoked_reason TEXT,
|
|
4094
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
4095
|
-
updated_at TEXT DEFAULT (datetime('now'))
|
|
4096
|
-
)`);
|
|
4097
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_agd_operator ON agent_declarations(operator_name)`);
|
|
4098
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_agd_revoked ON agent_declarations(revoked_at) WHERE revoked_at IS NOT NULL`);
|
|
4099
|
-
// agent_attestations:bilateral consent(用户主动同意某 agent 的 scope)
|
|
4100
|
-
db.exec(`CREATE TABLE IF NOT EXISTS agent_attestations (
|
|
4101
|
-
id TEXT PRIMARY KEY,
|
|
4102
|
-
api_key TEXT NOT NULL, -- agent 的 api_key
|
|
4103
|
-
user_id TEXT NOT NULL, -- 同意此 agent 行动的用户
|
|
4104
|
-
approved_scope TEXT NOT NULL, -- JSON:用户实际批准的子集
|
|
4105
|
-
spend_cap_per_order REAL, -- 该用户给此 agent 的单笔下单上限(可空 = 沿用 declared_scope)
|
|
4106
|
-
spend_cap_daily REAL, -- 24h 累计上限
|
|
4107
|
-
granted_at TEXT DEFAULT (datetime('now')),
|
|
4108
|
-
revoked_at TEXT,
|
|
4109
|
-
UNIQUE(api_key, user_id)
|
|
4110
|
-
)`);
|
|
4111
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_aat_user ON agent_attestations(user_id, revoked_at)`);
|
|
4112
|
-
// agent_strikes:违规累积(3-strike state machine)
|
|
4113
|
-
db.exec(`CREATE TABLE IF NOT EXISTS agent_strikes (
|
|
4114
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4115
|
-
api_key TEXT NOT NULL,
|
|
4116
|
-
user_id TEXT NOT NULL,
|
|
4117
|
-
severity TEXT NOT NULL, -- 'warning' | 'suspend_7d' | 'permanent'
|
|
4118
|
-
reason_code TEXT NOT NULL, -- 'fake_shipment' | 'mass_spam' | 'overlimit_order' | 'fraud_claim' | ...
|
|
4119
|
-
reason_detail TEXT,
|
|
4120
|
-
reported_by TEXT, -- user_id(举报人 / system / admin)
|
|
4121
|
-
related_ref TEXT, -- 关联 order/dispute/claim_task id
|
|
4122
|
-
issued_at TEXT DEFAULT (datetime('now')),
|
|
4123
|
-
expires_at TEXT, -- warning=24h; suspend_7d=7d; permanent=null
|
|
4124
|
-
appeal_status TEXT DEFAULT 'none', -- 'none' | 'pending' | 'approved' | 'denied'
|
|
4125
|
-
appeal_reason TEXT,
|
|
4126
|
-
appeal_decided_by TEXT,
|
|
4127
|
-
appeal_decided_at TEXT
|
|
4128
|
-
)`);
|
|
4129
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_ast_apikey ON agent_strikes(api_key, issued_at DESC)`);
|
|
4130
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_ast_user ON agent_strikes(user_id, issued_at DESC)`);
|
|
4131
|
-
// 注:SQLite 不允许 partial index 用非确定性函数 (datetime('now'));用 expires_at 普通索引代替
|
|
4132
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_ast_active ON agent_strikes(api_key, expires_at)`);
|
|
4133
|
-
// 2026-05-23 P1 fix 5.3:skills 加 disabled_by_strike_at(被 strike 自动停用后可恢复)
|
|
3041
|
+
// ─── 2026-05-23 Agent 治理(spec: docs/AGENT-GOVERNANCE.md)→ server-schema.ts ─
|
|
3042
|
+
// 顺序须保持:declarations → attestations → strikes →(ALTER skills 留原位)→ revocations
|
|
3043
|
+
try {
|
|
3044
|
+
initAgentDeclarationsSchema(db);
|
|
3045
|
+
initAgentAttestationsSchema(db);
|
|
3046
|
+
initAgentStrikesSchema(db);
|
|
3047
|
+
// 2026-05-23 P1 fix 5.3:skills 加 disabled_by_strike_at(被 strike 自动停用后可恢复)—— 留 server.ts 原位
|
|
4134
3048
|
try {
|
|
4135
3049
|
db.exec(`ALTER TABLE skills ADD COLUMN disabled_by_strike_at TEXT`);
|
|
4136
3050
|
}
|
|
4137
3051
|
catch { }
|
|
4138
|
-
|
|
4139
|
-
db.exec(`CREATE TABLE IF NOT EXISTS agent_revocations (
|
|
4140
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4141
|
-
target_kind TEXT NOT NULL, -- 'api_key' | 'operator_name'
|
|
4142
|
-
target_value TEXT NOT NULL,
|
|
4143
|
-
revoked_by TEXT NOT NULL, -- user_id(用户自己 OR root admin)
|
|
4144
|
-
revoked_by_role TEXT, -- 'self' | 'admin'
|
|
4145
|
-
reason TEXT,
|
|
4146
|
-
revoked_at TEXT DEFAULT (datetime('now')),
|
|
4147
|
-
UNIQUE(target_kind, target_value, revoked_by)
|
|
4148
|
-
)`);
|
|
4149
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_arev_target ON agent_revocations(target_kind, target_value)`);
|
|
3052
|
+
initAgentRevocationsSchema(db);
|
|
4150
3053
|
}
|
|
4151
3054
|
catch (e) {
|
|
4152
3055
|
console.error('[agent_governance schema]', e);
|
|
@@ -4155,73 +3058,22 @@ catch (e) {
|
|
|
4155
3058
|
// 协议级精准匹配:卖家声明该 SKU 的多种 alias(外部 id / 标题 / 短链 / 淘口令 token / 标题片段)
|
|
4156
3059
|
// 服务端用 findProductsByAlias 做"完全相等 + 包含"判定。alias 至少 6 字符,反通用词。
|
|
4157
3060
|
try {
|
|
4158
|
-
db
|
|
4159
|
-
id TEXT PRIMARY KEY,
|
|
4160
|
-
product_id TEXT NOT NULL,
|
|
4161
|
-
alias_type TEXT NOT NULL, -- 'external_id' | 'external_title' | 'short_url' | 'kouling_token' | 'title_substring'
|
|
4162
|
-
alias_value TEXT NOT NULL,
|
|
4163
|
-
min_match_chars INTEGER DEFAULT 6,
|
|
4164
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
4165
|
-
challenged_at TEXT, -- M7.4 verifier 挑战时间
|
|
4166
|
-
status TEXT DEFAULT 'active', -- 'active' | 'revoked' | 'challenged'
|
|
4167
|
-
UNIQUE(alias_type, alias_value, product_id)
|
|
4168
|
-
)`);
|
|
4169
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_alias_value ON product_aliases(alias_value)`);
|
|
4170
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_alias_product ON product_aliases(product_id)`);
|
|
4171
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_alias_type ON product_aliases(alias_type)`);
|
|
3061
|
+
initProductAliasesSchema(db);
|
|
4172
3062
|
}
|
|
4173
3063
|
catch (e) {
|
|
4174
3064
|
console.error('[M7.2 schema product_aliases]', e);
|
|
4175
3065
|
}
|
|
4176
|
-
// M-5:region 切换 audit log + 24h 限流
|
|
3066
|
+
// M-5:region 切换 audit log + 24h 限流 → server-schema.ts
|
|
4177
3067
|
try {
|
|
4178
|
-
db
|
|
4179
|
-
id TEXT PRIMARY KEY,
|
|
4180
|
-
user_id TEXT NOT NULL,
|
|
4181
|
-
from_region TEXT,
|
|
4182
|
-
to_region TEXT NOT NULL,
|
|
4183
|
-
ip TEXT,
|
|
4184
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4185
|
-
)`);
|
|
4186
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_region_change_user_ts ON region_change_log(user_id, created_at DESC)`);
|
|
3068
|
+
initRegionChangeLogSchema(db);
|
|
4187
3069
|
}
|
|
4188
3070
|
catch (e) {
|
|
4189
3071
|
console.error('[M-5 schema region_change_log]', e);
|
|
4190
3072
|
}
|
|
4191
|
-
// WebAuthn / Passkey — 大额提现 等敏感操作的二次确认(commit B
|
|
3073
|
+
// WebAuthn / Passkey — 大额提现 等敏感操作的二次确认(commit B)→ server-schema.ts
|
|
3074
|
+
// users.webauthn_required_for_withdraw ALTER 刻意留原位(init 后、同一 try 内)
|
|
4192
3075
|
try {
|
|
4193
|
-
db
|
|
4194
|
-
id TEXT PRIMARY KEY, -- credential.id (base64url)
|
|
4195
|
-
user_id TEXT NOT NULL,
|
|
4196
|
-
public_key BLOB NOT NULL, -- COSE public key
|
|
4197
|
-
counter INTEGER NOT NULL DEFAULT 0,
|
|
4198
|
-
transports TEXT, -- JSON array
|
|
4199
|
-
device_label TEXT, -- user-friendly label
|
|
4200
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
4201
|
-
last_used_at TEXT
|
|
4202
|
-
)`);
|
|
4203
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_wac_user ON webauthn_credentials(user_id)`);
|
|
4204
|
-
db.exec(`CREATE TABLE IF NOT EXISTS webauthn_challenges (
|
|
4205
|
-
id TEXT PRIMARY KEY,
|
|
4206
|
-
user_id TEXT NOT NULL,
|
|
4207
|
-
challenge TEXT NOT NULL,
|
|
4208
|
-
purpose TEXT NOT NULL, -- 'register' | 'withdraw' | 'change-password' | 'reveal-key' | 'region'
|
|
4209
|
-
purpose_data TEXT, -- JSON:例如 {amount: 1000, to_address: '0x...'}
|
|
4210
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
4211
|
-
expires_at TEXT NOT NULL,
|
|
4212
|
-
consumed_at TEXT
|
|
4213
|
-
)`);
|
|
4214
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_wac_chall_user ON webauthn_challenges(user_id, expires_at)`);
|
|
4215
|
-
// gate token:auth/finish 成功后颁发,绑定 user + purpose + 业务参数(防重放)
|
|
4216
|
-
db.exec(`CREATE TABLE IF NOT EXISTS webauthn_gate_tokens (
|
|
4217
|
-
id TEXT PRIMARY KEY, -- token
|
|
4218
|
-
user_id TEXT NOT NULL,
|
|
4219
|
-
purpose TEXT NOT NULL,
|
|
4220
|
-
purpose_data TEXT, -- JSON
|
|
4221
|
-
expires_at TEXT NOT NULL, -- now + 60s
|
|
4222
|
-
consumed_at TEXT
|
|
4223
|
-
)`);
|
|
4224
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_wac_gate_user ON webauthn_gate_tokens(user_id, expires_at)`);
|
|
3076
|
+
initWebauthnSchema(db);
|
|
4225
3077
|
// 用户 opt-in 设置
|
|
4226
3078
|
try {
|
|
4227
3079
|
db.exec("ALTER TABLE users ADD COLUMN webauthn_required_for_withdraw INTEGER DEFAULT 0");
|
|
@@ -4243,41 +3095,8 @@ const WEBAUTHN_CHALLENGE_TTL_MS = 5 * 60 * 1000; // 5 分钟
|
|
|
4243
3095
|
const WEBAUTHN_GATE_TTL_MS = 90 * 1000; // 90 秒
|
|
4244
3096
|
// M7.3:claim 验证任务系统 — 买家对推荐理由发起验证,3 verifier 共识仲裁
|
|
4245
3097
|
try {
|
|
4246
|
-
db
|
|
4247
|
-
|
|
4248
|
-
order_id TEXT NOT NULL,
|
|
4249
|
-
buyer_id TEXT NOT NULL,
|
|
4250
|
-
seller_id TEXT NOT NULL,
|
|
4251
|
-
product_id TEXT NOT NULL,
|
|
4252
|
-
claim_target TEXT NOT NULL, -- 'price' | 'commission' | 'protection' | 'return' | 'warranty' | 'handling' | 'other'
|
|
4253
|
-
claim_text TEXT NOT NULL, -- 买家陈述(≤ 500 字)
|
|
4254
|
-
evidence_uri TEXT, -- 买家证据(URL / hash)
|
|
4255
|
-
stake_buyer REAL NOT NULL, -- 买家锁定的质押金
|
|
4256
|
-
seller_evidence_uri TEXT, -- 卖家提交的证据
|
|
4257
|
-
seller_evidence_at TEXT,
|
|
4258
|
-
deadline_at TEXT NOT NULL, -- 默认 48h;卖家提交证据后 +24h
|
|
4259
|
-
status TEXT NOT NULL DEFAULT 'open', -- 'open' | 'sealed' | 'resolved_pass' | 'resolved_fail' | 'resolved_no_fault' | 'timeout_pass' | 'timeout_fail'
|
|
4260
|
-
resolved_at TEXT,
|
|
4261
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
4262
|
-
UNIQUE(order_id)
|
|
4263
|
-
)`);
|
|
4264
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_cvt_status ON claim_verification_tasks(status)`);
|
|
4265
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_cvt_buyer ON claim_verification_tasks(buyer_id)`);
|
|
4266
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_cvt_seller ON claim_verification_tasks(seller_id)`);
|
|
4267
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_cvt_deadline ON claim_verification_tasks(deadline_at) WHERE status = 'open'`);
|
|
4268
|
-
db.exec(`CREATE TABLE IF NOT EXISTS claim_verification_votes (
|
|
4269
|
-
id TEXT PRIMARY KEY,
|
|
4270
|
-
task_id TEXT NOT NULL,
|
|
4271
|
-
verifier_id TEXT NOT NULL,
|
|
4272
|
-
vote TEXT NOT NULL, -- 'pass' | 'fail' | 'no_fault'
|
|
4273
|
-
evidence_uri TEXT,
|
|
4274
|
-
note TEXT,
|
|
4275
|
-
voted_at TEXT DEFAULT (datetime('now')),
|
|
4276
|
-
UNIQUE(task_id, verifier_id)
|
|
4277
|
-
)`);
|
|
4278
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_cvv_task ON claim_verification_votes(task_id)`);
|
|
4279
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_cvv_verifier ON claim_verification_votes(verifier_id)`);
|
|
4280
|
-
// M7.3b 结算扩展
|
|
3098
|
+
initClaimVerificationBaseSchema(db);
|
|
3099
|
+
// M7.3b 结算扩展 — 刻意留 server.ts 原位(base init 之后,按原顺序)
|
|
4281
3100
|
try {
|
|
4282
3101
|
db.exec(`ALTER TABLE claim_verification_tasks ADD COLUMN majority_vote TEXT`);
|
|
4283
3102
|
}
|
|
@@ -4286,187 +3105,18 @@ try {
|
|
|
4286
3105
|
db.exec(`ALTER TABLE claim_verification_votes ADD COLUMN was_majority INTEGER`);
|
|
4287
3106
|
}
|
|
4288
3107
|
catch { }
|
|
4289
|
-
// verifier 禁言 /
|
|
4290
|
-
db
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
// 任何登录用户(除 seller 本人)可对商品的某项声明发起验证
|
|
4302
|
-
// 3 verifier 共识投票判定 upheld(声明不实,卖家失分)/ dismissed(声明属实,发起人失质押)
|
|
4303
|
-
db.exec(`CREATE TABLE IF NOT EXISTS product_claim_tasks (
|
|
4304
|
-
id TEXT PRIMARY KEY,
|
|
4305
|
-
product_id TEXT NOT NULL,
|
|
4306
|
-
claimant_id TEXT NOT NULL,
|
|
4307
|
-
seller_id TEXT NOT NULL,
|
|
4308
|
-
claim_target TEXT NOT NULL, -- 'title' | 'description' | 'condition' | 'return_days' | 'handling_hours' | 'warranty_days' | 'shipping_regions' | 'origin' | 'other'
|
|
4309
|
-
claim_text TEXT NOT NULL, -- 发起人陈述 6-500 字
|
|
4310
|
-
evidence_uri TEXT, -- 发起人证据 URL
|
|
4311
|
-
stake_claimant REAL NOT NULL, -- 发起人锁定质押
|
|
4312
|
-
seller_evidence_uri TEXT, -- 卖家反驳证据
|
|
4313
|
-
seller_evidence_at TEXT,
|
|
4314
|
-
deadline_at TEXT NOT NULL, -- 默认 72h;卖家提交证据后 +24h
|
|
4315
|
-
status TEXT NOT NULL DEFAULT 'open', -- 'open' | 'sealed' | 'resolved_upheld' | 'resolved_dismissed' | 'expired'
|
|
4316
|
-
ruling TEXT, -- 'upheld' | 'dismissed' | 'insufficient'
|
|
4317
|
-
majority_vote TEXT,
|
|
4318
|
-
resolved_at TEXT,
|
|
4319
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4320
|
-
)`);
|
|
4321
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_pct_status ON product_claim_tasks(status)`);
|
|
4322
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_pct_product ON product_claim_tasks(product_id)`);
|
|
4323
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_pct_claimant ON product_claim_tasks(claimant_id)`);
|
|
4324
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_pct_seller ON product_claim_tasks(seller_id)`);
|
|
4325
|
-
db.exec(`CREATE TABLE IF NOT EXISTS product_claim_votes (
|
|
4326
|
-
id TEXT PRIMARY KEY,
|
|
4327
|
-
claim_id TEXT NOT NULL,
|
|
4328
|
-
verifier_id TEXT NOT NULL,
|
|
4329
|
-
vote TEXT NOT NULL, -- 'upheld' | 'dismissed' | 'insufficient'
|
|
4330
|
-
evidence_uri TEXT,
|
|
4331
|
-
note TEXT,
|
|
4332
|
-
was_majority INTEGER,
|
|
4333
|
-
voted_at TEXT DEFAULT (datetime('now')),
|
|
4334
|
-
UNIQUE(claim_id, verifier_id)
|
|
4335
|
-
)`);
|
|
4336
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_pcv_claim ON product_claim_votes(claim_id)`);
|
|
4337
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_pcv_verifier ON product_claim_votes(verifier_id)`);
|
|
4338
|
-
// Sprint 2-A: 测评真实性验证(针对 shareables / manifests)
|
|
4339
|
-
// 任何登录用户可对某条评测发起验证(不是真实购买/付费推广/利益相关等)
|
|
4340
|
-
db.exec(`CREATE TABLE IF NOT EXISTS review_claim_tasks (
|
|
4341
|
-
id TEXT PRIMARY KEY,
|
|
4342
|
-
review_type TEXT NOT NULL, -- 'shareable' | 'manifest'
|
|
4343
|
-
review_id TEXT NOT NULL, -- shareable.id 或 manifest.hash
|
|
4344
|
-
product_id TEXT, -- 关联商品(用于显示)
|
|
4345
|
-
reviewer_id TEXT NOT NULL, -- 被诉评测作者
|
|
4346
|
-
claimant_id TEXT NOT NULL,
|
|
4347
|
-
claim_target TEXT NOT NULL, -- 'not_real_purchase' | 'paid_promo' | 'incentivized' | 'misleading' | 'fake' | 'other'
|
|
4348
|
-
claim_text TEXT NOT NULL,
|
|
4349
|
-
evidence_uri TEXT,
|
|
4350
|
-
stake_claimant REAL NOT NULL,
|
|
4351
|
-
deadline_at TEXT NOT NULL,
|
|
4352
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
4353
|
-
ruling TEXT,
|
|
4354
|
-
majority_vote TEXT,
|
|
4355
|
-
resolved_at TEXT,
|
|
4356
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4357
|
-
)`);
|
|
4358
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_rct_status ON review_claim_tasks(status)`);
|
|
4359
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_rct_review ON review_claim_tasks(review_type, review_id)`);
|
|
4360
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_rct_reviewer ON review_claim_tasks(reviewer_id)`);
|
|
4361
|
-
db.exec(`CREATE TABLE IF NOT EXISTS review_claim_votes (
|
|
4362
|
-
id TEXT PRIMARY KEY,
|
|
4363
|
-
claim_id TEXT NOT NULL,
|
|
4364
|
-
verifier_id TEXT NOT NULL,
|
|
4365
|
-
vote TEXT NOT NULL, -- 'upheld' | 'dismissed' | 'insufficient'
|
|
4366
|
-
evidence_uri TEXT,
|
|
4367
|
-
note TEXT,
|
|
4368
|
-
was_majority INTEGER,
|
|
4369
|
-
voted_at TEXT DEFAULT (datetime('now')),
|
|
4370
|
-
UNIQUE(claim_id, verifier_id)
|
|
4371
|
-
)`);
|
|
4372
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_rcv_claim ON review_claim_votes(claim_id)`);
|
|
4373
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_rcv_verifier ON review_claim_votes(verifier_id)`);
|
|
4374
|
-
// Sprint 2-B: 二手成色验证(针对 secondhand_items)
|
|
4375
|
-
db.exec(`CREATE TABLE IF NOT EXISTS secondhand_claim_tasks (
|
|
4376
|
-
id TEXT PRIMARY KEY,
|
|
4377
|
-
sh_item_id TEXT NOT NULL,
|
|
4378
|
-
seller_id TEXT NOT NULL, -- 二手卖家
|
|
4379
|
-
claimant_id TEXT NOT NULL,
|
|
4380
|
-
claim_target TEXT NOT NULL, -- 'condition' | 'images' | 'description' | 'title' | 'price' | 'other'
|
|
4381
|
-
claim_text TEXT NOT NULL,
|
|
4382
|
-
evidence_uri TEXT,
|
|
4383
|
-
stake_claimant REAL NOT NULL,
|
|
4384
|
-
deadline_at TEXT NOT NULL,
|
|
4385
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
4386
|
-
ruling TEXT,
|
|
4387
|
-
majority_vote TEXT,
|
|
4388
|
-
resolved_at TEXT,
|
|
4389
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4390
|
-
)`);
|
|
4391
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_sct_status ON secondhand_claim_tasks(status)`);
|
|
4392
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_sct_item ON secondhand_claim_tasks(sh_item_id)`);
|
|
4393
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_sct_seller ON secondhand_claim_tasks(seller_id)`);
|
|
4394
|
-
db.exec(`CREATE TABLE IF NOT EXISTS secondhand_claim_votes (
|
|
4395
|
-
id TEXT PRIMARY KEY,
|
|
4396
|
-
claim_id TEXT NOT NULL,
|
|
4397
|
-
verifier_id TEXT NOT NULL,
|
|
4398
|
-
vote TEXT NOT NULL,
|
|
4399
|
-
evidence_uri TEXT,
|
|
4400
|
-
note TEXT,
|
|
4401
|
-
was_majority INTEGER,
|
|
4402
|
-
voted_at TEXT DEFAULT (datetime('now')),
|
|
4403
|
-
UNIQUE(claim_id, verifier_id)
|
|
4404
|
-
)`);
|
|
4405
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_scv_claim ON secondhand_claim_votes(claim_id)`);
|
|
4406
|
-
// Sprint 3-A: 拍卖声明(auctions)
|
|
4407
|
-
// 任何非 seller 用户可对 active auction 的合理性发起验证
|
|
4408
|
-
db.exec(`CREATE TABLE IF NOT EXISTS auction_claim_tasks (
|
|
4409
|
-
id TEXT PRIMARY KEY,
|
|
4410
|
-
auction_id TEXT NOT NULL,
|
|
4411
|
-
seller_id TEXT NOT NULL,
|
|
4412
|
-
claimant_id TEXT NOT NULL,
|
|
4413
|
-
claim_target TEXT NOT NULL, -- 'unreasonable_reserve' | 'shill_bidding' | 'collusion' | 'fake_listing' | 'other'
|
|
4414
|
-
claim_text TEXT NOT NULL,
|
|
4415
|
-
evidence_uri TEXT,
|
|
4416
|
-
stake_claimant REAL NOT NULL,
|
|
4417
|
-
deadline_at TEXT NOT NULL,
|
|
4418
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
4419
|
-
ruling TEXT,
|
|
4420
|
-
majority_vote TEXT,
|
|
4421
|
-
resolved_at TEXT,
|
|
4422
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4423
|
-
)`);
|
|
4424
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_act_status ON auction_claim_tasks(status)`);
|
|
4425
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_act_auction ON auction_claim_tasks(auction_id)`);
|
|
4426
|
-
db.exec(`CREATE TABLE IF NOT EXISTS auction_claim_votes (
|
|
4427
|
-
id TEXT PRIMARY KEY,
|
|
4428
|
-
claim_id TEXT NOT NULL,
|
|
4429
|
-
verifier_id TEXT NOT NULL,
|
|
4430
|
-
vote TEXT NOT NULL,
|
|
4431
|
-
evidence_uri TEXT,
|
|
4432
|
-
note TEXT,
|
|
4433
|
-
was_majority INTEGER,
|
|
4434
|
-
voted_at TEXT DEFAULT (datetime('now')),
|
|
4435
|
-
UNIQUE(claim_id, verifier_id)
|
|
4436
|
-
)`);
|
|
4437
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_acv_claim ON auction_claim_votes(claim_id)`);
|
|
4438
|
-
// Sprint 3-B: 慈善许愿声明(wishes)
|
|
4439
|
-
// 任何非 wisher 用户可对 wish 真实性发起验证
|
|
4440
|
-
db.exec(`CREATE TABLE IF NOT EXISTS wish_claim_tasks (
|
|
4441
|
-
id TEXT PRIMARY KEY,
|
|
4442
|
-
wish_id TEXT NOT NULL,
|
|
4443
|
-
wisher_id TEXT NOT NULL,
|
|
4444
|
-
claimant_id TEXT NOT NULL,
|
|
4445
|
-
claim_target TEXT NOT NULL, -- 'fake_identity' | 'fake_story' | 'already_fulfilled' | 'duplicate' | 'inappropriate' | 'other'
|
|
4446
|
-
claim_text TEXT NOT NULL,
|
|
4447
|
-
evidence_uri TEXT,
|
|
4448
|
-
stake_claimant REAL NOT NULL,
|
|
4449
|
-
deadline_at TEXT NOT NULL,
|
|
4450
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
4451
|
-
ruling TEXT,
|
|
4452
|
-
majority_vote TEXT,
|
|
4453
|
-
resolved_at TEXT,
|
|
4454
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
4455
|
-
)`);
|
|
4456
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_wct_status ON wish_claim_tasks(status)`);
|
|
4457
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_wct_wish ON wish_claim_tasks(wish_id)`);
|
|
4458
|
-
db.exec(`CREATE TABLE IF NOT EXISTS wish_claim_votes (
|
|
4459
|
-
id TEXT PRIMARY KEY,
|
|
4460
|
-
claim_id TEXT NOT NULL,
|
|
4461
|
-
verifier_id TEXT NOT NULL,
|
|
4462
|
-
vote TEXT NOT NULL,
|
|
4463
|
-
evidence_uri TEXT,
|
|
4464
|
-
note TEXT,
|
|
4465
|
-
was_majority INTEGER,
|
|
4466
|
-
voted_at TEXT DEFAULT (datetime('now')),
|
|
4467
|
-
UNIQUE(claim_id, verifier_id)
|
|
4468
|
-
)`);
|
|
4469
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_wcv_claim ON wish_claim_votes(claim_id)`);
|
|
3108
|
+
// verifier 禁言 / 永封记录 → server-schema.ts
|
|
3109
|
+
initClaimVerifierSuspensionsSchema(db);
|
|
3110
|
+
// Sprint 1: 商品声明验证 → server-schema.ts
|
|
3111
|
+
initProductClaimSchema(db);
|
|
3112
|
+
// Sprint 2-A: 测评真实性验证 → server-schema.ts
|
|
3113
|
+
initReviewClaimSchema(db);
|
|
3114
|
+
// Sprint 2-B: 二手成色验证 → server-schema.ts
|
|
3115
|
+
initSecondhandClaimSchema(db);
|
|
3116
|
+
// Sprint 3-A: 拍卖声明 → server-schema.ts
|
|
3117
|
+
initAuctionClaimSchema(db);
|
|
3118
|
+
// Sprint 3-B: 慈善许愿声明 → server-schema.ts
|
|
3119
|
+
initWishClaimSchema(db);
|
|
4470
3120
|
}
|
|
4471
3121
|
catch (e) {
|
|
4472
3122
|
console.error('[M7.3 schema claim_verification]', e);
|
|
@@ -4603,24 +3253,23 @@ function computeAgentTrust(apiKey) {
|
|
|
4603
3253
|
? db.prepare(`SELECT COUNT(DISTINCT user_id) as n FROM registration_audit_log WHERE ip_hash = ?`).get(myReg.ip_hash).n
|
|
4604
3254
|
: 0;
|
|
4605
3255
|
const sameIpOthers = Math.max(0, sybilSize - 1); // 排除自己
|
|
4606
|
-
//
|
|
3256
|
+
// 放置同支审计 / 上架限速命中
|
|
4607
3257
|
const crossHits = db.prepare(`SELECT COUNT(*) as n FROM commission_audit_log WHERE buyer_id = ? OR seller_id = ?`).get(user.id, user.id).n;
|
|
4608
3258
|
// 限速命中 — 简化:30 天内 429 状态码次数
|
|
4609
3259
|
const ratelimitHits = db.prepare(`SELECT COUNT(*) as n FROM agent_call_log WHERE api_key = ? AND status_code = 429 AND created_at > datetime('now', '-30 days')`).get(apiKey).n;
|
|
4610
|
-
// 公式
|
|
3260
|
+
// 公式 — #420 P1-2:penalty 系数 / sybil 阈值 / 等级 cutoff 由 protocol_params 驱动(默认 = 原字面量)
|
|
3261
|
+
const t = readAntiAbuseThresholds(db);
|
|
4611
3262
|
const agePts = Math.min(ageDays, 90) * 0.5;
|
|
4612
3263
|
const orderPts = Math.min(completedBuyer + completedSeller, 50) * 0.5;
|
|
4613
3264
|
const sharePts = Math.min(shareConversions, 20) * 1.0;
|
|
4614
3265
|
const diversityPts = Math.min(diversity, 25) * 0.4;
|
|
4615
|
-
const disputeP = -disputeLoss *
|
|
4616
|
-
const sybilP =
|
|
4617
|
-
const crossP = -crossHits *
|
|
4618
|
-
const ratelimitP = -ratelimitHits *
|
|
3266
|
+
const disputeP = -disputeLoss * t.trustDisputePenalty;
|
|
3267
|
+
const sybilP = agentSybilPenalty(sybilSize, t);
|
|
3268
|
+
const crossP = -crossHits * t.trustCrossPenalty;
|
|
3269
|
+
const ratelimitP = -ratelimitHits * t.trustRatelimitPenalty;
|
|
4619
3270
|
const raw = agePts + orderPts + sharePts + diversityPts + disputeP + sybilP + crossP + ratelimitP;
|
|
4620
3271
|
const trust = Math.max(0, Math.round(raw * 100) / 100);
|
|
4621
|
-
const level = trust
|
|
4622
|
-
trust >= 50 ? 'quality' :
|
|
4623
|
-
trust >= 20 ? 'trusted' : 'new';
|
|
3272
|
+
const level = agentTrustLevel(trust, t);
|
|
4624
3273
|
const signals = {
|
|
4625
3274
|
age_days: Math.round(ageDays * 10) / 10,
|
|
4626
3275
|
completed_buyer: completedBuyer,
|
|
@@ -4672,7 +3321,7 @@ function antiCheatHash(input) {
|
|
|
4672
3321
|
return 'unknown';
|
|
4673
3322
|
return createHmac('sha256', MASTER_SEED).update('m3:' + input).digest('hex').slice(0, 24);
|
|
4674
3323
|
}
|
|
4675
|
-
//
|
|
3324
|
+
// 放置同支检测:写入 commission_audit_log(监测+证据;不阻断)
|
|
4676
3325
|
function auditSponsorChainCross(orderId, buyerId, sellerId, buyerSponsorPath) {
|
|
4677
3326
|
if (buyerId === sellerId)
|
|
4678
3327
|
return;
|
|
@@ -4803,7 +3452,7 @@ function extractUrlFromText(text) {
|
|
|
4803
3452
|
return m?.[0] ?? null;
|
|
4804
3453
|
}
|
|
4805
3454
|
// Agent-first 设计:粘贴外链匹配 **只允许精准匹配**,没有 LIKE 兜底。
|
|
4806
|
-
// 模糊匹配与外推荐留给未来独立的"模糊搜索"
|
|
3455
|
+
// 模糊匹配与外推荐留给未来独立的"模糊搜索"入口(精准 trust,0 命中引导用户去 discover,不加 LIKE 兜底)。
|
|
4807
3456
|
function searchByExternalLink(opts) {
|
|
4808
3457
|
const cols = `p.id, p.title, p.description, p.price, p.stock, p.category, p.seller_id,
|
|
4809
3458
|
p.specs, p.brand, p.model, p.handling_hours, p.return_days, p.warranty_days, p.ship_regions, p.fragile,
|
|
@@ -5021,19 +3670,8 @@ db.exec(`
|
|
|
5021
3670
|
used_at TEXT
|
|
5022
3671
|
)
|
|
5023
3672
|
`);
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
id TEXT PRIMARY KEY,
|
|
5027
|
-
product_id TEXT NOT NULL,
|
|
5028
|
-
url TEXT NOT NULL,
|
|
5029
|
-
source TEXT DEFAULT 'manual',
|
|
5030
|
-
verified INTEGER DEFAULT 0,
|
|
5031
|
-
verify_note TEXT,
|
|
5032
|
-
added_at TEXT DEFAULT (datetime('now')),
|
|
5033
|
-
verified_at TEXT,
|
|
5034
|
-
UNIQUE(product_id, url)
|
|
5035
|
-
)
|
|
5036
|
-
`);
|
|
3673
|
+
// 外部链接验证 base → server-schema.ts;ALTER/index/回填 IIFE 刻意留原位(紧跟下方)
|
|
3674
|
+
initProductExternalLinksBaseSchema(db);
|
|
5037
3675
|
try {
|
|
5038
3676
|
db.exec('ALTER TABLE product_external_links ADD COLUMN revoked INTEGER DEFAULT 0');
|
|
5039
3677
|
}
|
|
@@ -5084,58 +3722,12 @@ catch { }
|
|
|
5084
3722
|
console.error('[backfill failed]', e.message);
|
|
5085
3723
|
}
|
|
5086
3724
|
})();
|
|
5087
|
-
// link_challenges 保留用于向后兼容,新流程用 verify_tasks
|
|
5088
|
-
db
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
code TEXT NOT NULL,
|
|
5094
|
-
status TEXT DEFAULT 'pending',
|
|
5095
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
5096
|
-
expires_at TEXT NOT NULL,
|
|
5097
|
-
verified_at TEXT
|
|
5098
|
-
)
|
|
5099
|
-
`);
|
|
5100
|
-
db.exec(`
|
|
5101
|
-
CREATE TABLE IF NOT EXISTS verify_tasks (
|
|
5102
|
-
id TEXT PRIMARY KEY,
|
|
5103
|
-
type TEXT NOT NULL DEFAULT 'code_check',
|
|
5104
|
-
product_id TEXT NOT NULL,
|
|
5105
|
-
url TEXT NOT NULL,
|
|
5106
|
-
code TEXT,
|
|
5107
|
-
verifiers_needed INTEGER NOT NULL DEFAULT 3,
|
|
5108
|
-
reward_per_verifier REAL NOT NULL DEFAULT 0.1,
|
|
5109
|
-
fee_locked REAL NOT NULL DEFAULT 0,
|
|
5110
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
5111
|
-
result TEXT,
|
|
5112
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
5113
|
-
expires_at TEXT NOT NULL,
|
|
5114
|
-
settled_at TEXT
|
|
5115
|
-
)
|
|
5116
|
-
`);
|
|
5117
|
-
db.exec(`
|
|
5118
|
-
CREATE TABLE IF NOT EXISTS verify_submissions (
|
|
5119
|
-
id TEXT PRIMARY KEY,
|
|
5120
|
-
task_id TEXT NOT NULL,
|
|
5121
|
-
verifier_id TEXT NOT NULL,
|
|
5122
|
-
submission TEXT,
|
|
5123
|
-
verdict TEXT,
|
|
5124
|
-
claimed_at TEXT DEFAULT (datetime('now')),
|
|
5125
|
-
submitted_at TEXT,
|
|
5126
|
-
UNIQUE(task_id, verifier_id)
|
|
5127
|
-
)
|
|
5128
|
-
`);
|
|
5129
|
-
db.exec(`
|
|
5130
|
-
CREATE TABLE IF NOT EXISTS verifier_stats (
|
|
5131
|
-
user_id TEXT PRIMARY KEY,
|
|
5132
|
-
verify_rights INTEGER NOT NULL DEFAULT 3,
|
|
5133
|
-
tasks_done INTEGER NOT NULL DEFAULT 0,
|
|
5134
|
-
tasks_correct INTEGER NOT NULL DEFAULT 0,
|
|
5135
|
-
tasks_wrong INTEGER NOT NULL DEFAULT 0,
|
|
5136
|
-
suspended_until TEXT
|
|
5137
|
-
)
|
|
5138
|
-
`);
|
|
3725
|
+
// link_challenges 保留用于向后兼容,新流程用 verify_tasks → server-schema.ts
|
|
3726
|
+
initLinkChallengesSchema(db);
|
|
3727
|
+
initVerifyTasksSchema(db);
|
|
3728
|
+
initVerifySubmissionsSchema(db);
|
|
3729
|
+
// verifier_stats 须在 PERMANENT_ACCOUNTS bootstrap 之前完成 → server-schema.ts
|
|
3730
|
+
initVerifierStatsSchema(db);
|
|
5139
3731
|
(() => {
|
|
5140
3732
|
for (const acc of PERMANENT_ACCOUNTS) {
|
|
5141
3733
|
const apiKey = 'key_perm_' + createHmac('sha256', MASTER_SEED).update(acc.seed).digest('hex');
|
|
@@ -5181,6 +3773,9 @@ const app = express();
|
|
|
5181
3773
|
// — 防伪造(开发:直连 ::1/127 → req.ip 返回 socket IP;生产:信任同机/同 VPC 内的反代)
|
|
5182
3774
|
// — 部署在 Cloudflare/nginx 后面时若不在同 VPC,需改为 ['cloudflare-cidr', ...] 或具体 IP 列表,绝不要写 true
|
|
5183
3775
|
app.set('trust proxy', 'loopback, linklocal, uniquelocal');
|
|
3776
|
+
// Cloudflare-only origin guard (defense-in-depth vs direct-to-origin bypass). OFF by default;
|
|
3777
|
+
// configured via CF_ORIGIN_GUARD_MODE (off|observe|enforce) + CF_ORIGIN_SHARED_SECRET — see cf-origin-guard.ts.
|
|
3778
|
+
app.use(createCfOriginGuard());
|
|
5184
3779
|
app.use(express.json());
|
|
5185
3780
|
// ─── Security headers (CSP + nosniff + frame-options) ─────────
|
|
5186
3781
|
// 注意:现有代码大量使用 inline onclick="..." / style="...",无法启用纯净 CSP
|
|
@@ -5296,32 +3891,23 @@ function invalidateAgentRiskCacheForUser(userId) {
|
|
|
5296
3891
|
function issueAgentStrike(opts) {
|
|
5297
3892
|
const { apiKey, userId, reasonCode } = opts;
|
|
5298
3893
|
const initial = opts.initialSeverity || 'warning';
|
|
3894
|
+
// #420 P1-4:升级阶梯阈值/窗口/过期 由 protocol_params 驱动(默认 = 原 7d/30d/≥1/≥2/24h/7d)
|
|
3895
|
+
const t = readAntiAbuseThresholds(db);
|
|
5299
3896
|
// 看是否需要升级
|
|
5300
3897
|
const warnings7d = db.prepare(`SELECT COUNT(*) as n FROM agent_strikes
|
|
5301
|
-
WHERE api_key = ? AND severity = 'warning' AND issued_at > datetime('now', '
|
|
3898
|
+
WHERE api_key = ? AND severity = 'warning' AND issued_at > datetime('now', '-${t.strikeWarnWindowDays} days')
|
|
5302
3899
|
AND appeal_status NOT IN ('approved')`).get(apiKey).n;
|
|
5303
3900
|
const suspends30d = db.prepare(`SELECT COUNT(*) as n FROM agent_strikes
|
|
5304
|
-
WHERE api_key = ? AND severity = 'suspend_7d' AND issued_at > datetime('now', '
|
|
3901
|
+
WHERE api_key = ? AND severity = 'suspend_7d' AND issued_at > datetime('now', '-${t.strikeSuspendWindowDays} days')
|
|
5305
3902
|
AND appeal_status NOT IN ('approved')`).get(apiKey).n;
|
|
5306
|
-
|
|
5307
|
-
let escalated = false;
|
|
5308
|
-
if (initial === 'warning' && warnings7d >= 1) { // 已有 1 次 warning + 本次新 warning = 累计 2 → 升 suspend
|
|
5309
|
-
severity = 'suspend_7d';
|
|
5310
|
-
escalated = true;
|
|
5311
|
-
}
|
|
5312
|
-
if (initial === 'suspend_7d' || severity === 'suspend_7d') {
|
|
5313
|
-
if (suspends30d >= 2) { // 已有 2 次 suspend + 本次 = 3 → 升 permanent
|
|
5314
|
-
severity = 'permanent';
|
|
5315
|
-
escalated = true;
|
|
5316
|
-
}
|
|
5317
|
-
}
|
|
3903
|
+
const { severity, escalated } = agentStrikeSeverity(initial, warnings7d, suspends30d, t);
|
|
5318
3904
|
// expires_at
|
|
5319
3905
|
let expiresAt = null;
|
|
5320
3906
|
if (severity === 'warning') {
|
|
5321
|
-
expiresAt = new Date(Date.now() +
|
|
3907
|
+
expiresAt = new Date(Date.now() + t.strikeWarnExpiryHours * 3600_000).toISOString().replace('T', ' ').slice(0, 19);
|
|
5322
3908
|
}
|
|
5323
3909
|
else if (severity === 'suspend_7d') {
|
|
5324
|
-
expiresAt = new Date(Date.now() +
|
|
3910
|
+
expiresAt = new Date(Date.now() + t.strikeSuspendExpiryDays * 86400_000).toISOString().replace('T', ' ').slice(0, 19);
|
|
5325
3911
|
}
|
|
5326
3912
|
db.prepare(`INSERT INTO agent_strikes (api_key, user_id, severity, reason_code, reason_detail, reported_by, related_ref, expires_at)
|
|
5327
3913
|
VALUES (?,?,?,?,?,?,?,?)`).run(apiKey, userId, severity, reasonCode, opts.reasonDetail || null, opts.reportedBy || 'system', opts.relatedRef || null, expiresAt);
|
|
@@ -5854,8 +4440,6 @@ registerAuthRegisterRoutes(app, {
|
|
|
5854
4440
|
clientIpHash, clientUaHash,
|
|
5855
4441
|
get VALID_REGIONS() { return VALID_REGIONS; },
|
|
5856
4442
|
pickPreferredSide, joinPowerLeg,
|
|
5857
|
-
get INVITE_ROTATION_HANDLES() { return INVITE_ROTATION_HANDLES; },
|
|
5858
|
-
inviteRotationLookup,
|
|
5859
4443
|
// 邮箱验证优先注册 — issueCode/findActiveCode 是 hoisted 函数声明、isVerificationEmailReady/
|
|
5860
4444
|
// emailDeliveryNotConfigured 是 import,均可在此安全引用;CODE_TTL_MIN/MAX_CODE_ATTEMPTS 是后置 const,
|
|
5861
4445
|
// 走 getter 延迟读避免 TDZ。
|
|
@@ -5894,6 +4478,21 @@ registerTaskProposalsRoutes(app, {
|
|
|
5894
4478
|
db, errorRes,
|
|
5895
4479
|
requireSupportAdmin: (req, res) => requireAdminPermission(req, res, 'support'),
|
|
5896
4480
|
rateLimitOk: (key) => proposalRateLimiter(key),
|
|
4481
|
+
auth, // required auth for proposer-facing /api/me/task-proposals
|
|
4482
|
+
resolveUser: (req) => getUser(req), // optional resolver — links a submission to the logged-in submitter
|
|
4483
|
+
});
|
|
4484
|
+
// PR #18 — build_task create quota-increase requests(requester submit + ROOT-only review/approve/reject/revoke)
|
|
4485
|
+
registerBuildTaskQuotaRoutes(app, {
|
|
4486
|
+
db, errorRes, auth,
|
|
4487
|
+
requireRootAdmin: (req, res) => requireRootAdmin(req, res),
|
|
4488
|
+
});
|
|
4489
|
+
// Phase 2 — admin operator-claim workflow: link an admin SEAT → a real contributor account
|
|
4490
|
+
// (propose → confirm → approve → revoke/supersede). Claim workflow only; writes NO contribution_facts.
|
|
4491
|
+
registerAdminOperatorClaimRoutes(app, {
|
|
4492
|
+
db, errorRes, auth,
|
|
4493
|
+
requireAdmin: (req, res) => requireAdmin(req, res),
|
|
4494
|
+
requireRootAdmin: (req, res) => requireRootAdmin(req, res),
|
|
4495
|
+
consumeGateToken, // unlink REQUEST requires a fresh passkey gate (purpose 'operator_claim_unlink')
|
|
5897
4496
|
});
|
|
5898
4497
|
// RFC-006 Gap 2:贡献者自查看板(build_reputation 独立池)
|
|
5899
4498
|
registerBuildReputationRoutes(app, { db, auth });
|
|
@@ -5908,6 +4507,10 @@ registerContributionIdentityRoutes(app, {
|
|
|
5908
4507
|
// PR5F — Contribution Score v1 evidence READ surface (logged-in self-view; read-only, no score).
|
|
5909
4508
|
// Returns the caller's OWN component evidence wrapped in the PR5A uncommitted-value boundary.
|
|
5910
4509
|
registerContributionScoreRoutes(app, { auth, errorRes });
|
|
4510
|
+
// Contribution read-out V1 — the caller's OWN attributable facts (GitHub + admin coordination), grouped
|
|
4511
|
+
// by source, read-only, wrapped in the uncommitted-value boundary. Attribution is read-time (GitHub
|
|
4512
|
+
// binding overlay + operator-claim as-of); writes nothing, no reward/payout.
|
|
4513
|
+
registerContributionFactsRoutes(app, { db, auth, errorRes });
|
|
5911
4514
|
// #1013 Phase 48: 3 auth/sessions endpoints 已迁出到 routes/auth-sessions.ts
|
|
5912
4515
|
registerAuthSessionsRoutes(app, { db, auth, verifyPassword, recordSession, generateSecureKey });
|
|
5913
4516
|
// 个人资料:查看 API Key + 联系方式
|
|
@@ -5956,8 +4559,8 @@ registerAgentGovernanceRoutes(app, {
|
|
|
5956
4559
|
// 监护人指纹:HMAC(MASTER_SEED) over owner id — 可追溯(协议持 seed)不暴露身份
|
|
5957
4560
|
custodianFingerprint: (ownerId) => createHmac('sha256', MASTER_SEED).update('custodian:' + ownerId).digest('hex').slice(0, 16),
|
|
5958
4561
|
// Phase 4 护照签名:用协议热钱包私钥签(eip191),issuer 地址 = DID 锚点,任何人 ecrecover 可验(闭包→调用时求值,晚于热钱包初始化)
|
|
5959
|
-
signPassport: (message) =>
|
|
5960
|
-
issuerAddress: () =>
|
|
4562
|
+
signPassport: (message) => walletSigner.issuerSignMessage(message),
|
|
4563
|
+
issuerAddress: () => walletSigner.issuerAddress(),
|
|
5961
4564
|
});
|
|
5962
4565
|
// ─── 2026-05-22 COP 飞轮 + COP P0-1 数据导出 ─────────────
|
|
5963
4566
|
// #1013 Phase 39: 2 endpoints (note-prompts + export) 已迁出到 routes/me-data.ts
|
|
@@ -6623,60 +5226,9 @@ registerAdminTokenomicsRoutes(app, {
|
|
|
6623
5226
|
logAdminAction,
|
|
6624
5227
|
});
|
|
6625
5228
|
// system-flags — Phase 107 已迁出
|
|
6626
|
-
// 邀请码轮询 — "issued + registered 自纠偏"模型
|
|
6627
|
-
const INVITE_ROTATION_HANDLES = ['xiaohua', 'mian', 'holden', 'jiayi', 'qingliang'];
|
|
6628
|
-
const INVITE_REBALANCE_THRESHOLD = 5; // max_reg - min_reg ≥ 5 → 进入补齐模式
|
|
6629
|
-
function readInviteStats() {
|
|
6630
|
-
return db.prepare("SELECT slot, issued_count as issued, registered_count as registered FROM invite_rotation_stats ORDER BY slot")
|
|
6631
|
-
.all();
|
|
6632
|
-
}
|
|
6633
|
-
function inviteRotationLookup(handleIdx) {
|
|
6634
|
-
const handle = INVITE_ROTATION_HANDLES[handleIdx];
|
|
6635
|
-
const u = db.prepare("SELECT id, permanent_code, handle, name FROM users WHERE handle = ?").get(handle);
|
|
6636
|
-
if (!u?.permanent_code)
|
|
6637
|
-
return null;
|
|
6638
|
-
return { id: u.id, code: u.permanent_code, handle: u.handle, name: u.name };
|
|
6639
|
-
}
|
|
6640
|
-
// 派号:取 (registered 不均衡 ? min_registered : min_issued);同值取低 slot
|
|
6641
|
-
function pickInviteSlot(stats) {
|
|
6642
|
-
const regs = stats.map(s => s.registered);
|
|
6643
|
-
const maxReg = Math.max(...regs), minReg = Math.min(...regs);
|
|
6644
|
-
const useReg = (maxReg - minReg) >= INVITE_REBALANCE_THRESHOLD;
|
|
6645
|
-
const key = useReg ? 'registered' : 'issued';
|
|
6646
|
-
const minVal = useReg ? minReg : Math.min(...stats.map(s => s.issued));
|
|
6647
|
-
for (const s of stats) {
|
|
6648
|
-
if (s[key] === minVal)
|
|
6649
|
-
return s.slot;
|
|
6650
|
-
}
|
|
6651
|
-
return 0;
|
|
6652
|
-
}
|
|
6653
|
-
// 派号 + issued++ 包在 transaction(better-sqlite3 同步顺序执行)
|
|
6654
|
-
const issueInviteSlot = db.transaction(() => {
|
|
6655
|
-
const stats = readInviteStats();
|
|
6656
|
-
const slot = pickInviteSlot(stats);
|
|
6657
|
-
db.prepare("UPDATE invite_rotation_stats SET issued_count = issued_count + 1 WHERE slot = ?").run(slot);
|
|
6658
|
-
return slot;
|
|
6659
|
-
});
|
|
6660
5229
|
// #1013 Phase 73: GET /api/reviews/recent 已迁出(claim 2 端点也由同模块注册,定义在下游)
|
|
6661
|
-
// B-4: 编辑精选 / 每周推荐
|
|
6662
|
-
db
|
|
6663
|
-
CREATE TABLE IF NOT EXISTS editor_picks (
|
|
6664
|
-
id TEXT PRIMARY KEY,
|
|
6665
|
-
kind TEXT NOT NULL, -- 'product' | 'seller'
|
|
6666
|
-
target_id TEXT NOT NULL,
|
|
6667
|
-
title TEXT, -- 编辑推荐语
|
|
6668
|
-
note TEXT,
|
|
6669
|
-
starts_at TEXT NOT NULL,
|
|
6670
|
-
ends_at TEXT NOT NULL,
|
|
6671
|
-
sort_order INTEGER DEFAULT 0,
|
|
6672
|
-
created_by TEXT,
|
|
6673
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
6674
|
-
)
|
|
6675
|
-
`);
|
|
6676
|
-
try {
|
|
6677
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_ep_active ON editor_picks(kind, ends_at, sort_order)');
|
|
6678
|
-
}
|
|
6679
|
-
catch { }
|
|
5230
|
+
// B-4: 编辑精选 / 每周推荐 → server-schema.ts
|
|
5231
|
+
initEditorPicksSchema(db);
|
|
6680
5232
|
// editor-picks 公开 — Phase 107 已迁出
|
|
6681
5233
|
// #1013 Phase 66: 3 admin/editor-picks endpoints 已迁出
|
|
6682
5234
|
registerAdminEditorPicksRoutes(app, {
|
|
@@ -6738,25 +5290,8 @@ registerAdminOpsRoutes(app, {
|
|
|
6738
5290
|
});
|
|
6739
5291
|
// AI 2 endpoints — Phase 100 已迁出
|
|
6740
5292
|
registerAiRoutes(app, { db, auth, anthropic });
|
|
6741
|
-
// D-3: KYC light —
|
|
6742
|
-
db
|
|
6743
|
-
CREATE TABLE IF NOT EXISTS kyc_records (
|
|
6744
|
-
user_id TEXT PRIMARY KEY,
|
|
6745
|
-
real_name TEXT NOT NULL,
|
|
6746
|
-
id_type TEXT NOT NULL, -- 'passport' | 'national_id' | 'driver_license'
|
|
6747
|
-
id_number_hash TEXT NOT NULL, -- sha256(id_number + MASTER_SEED)
|
|
6748
|
-
id_number_last4 TEXT, -- 末 4 位明文(便于核对)
|
|
6749
|
-
status TEXT NOT NULL DEFAULT 'pending', -- pending / approved / rejected
|
|
6750
|
-
reject_reason TEXT,
|
|
6751
|
-
reviewed_by TEXT,
|
|
6752
|
-
reviewed_at TEXT,
|
|
6753
|
-
submitted_at TEXT DEFAULT (datetime('now'))
|
|
6754
|
-
)
|
|
6755
|
-
`);
|
|
6756
|
-
try {
|
|
6757
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_kyc_status ON kyc_records(status, submitted_at)');
|
|
6758
|
-
}
|
|
6759
|
-
catch { }
|
|
5293
|
+
// D-3: KYC light — 实名认证(轻度,不存原始证件号)→ server-schema.ts
|
|
5294
|
+
initKycRecordsSchema(db);
|
|
6760
5295
|
// KYC 2 endpoints — Phase 97 已迁出
|
|
6761
5296
|
registerKycRoutes(app, { db, auth, MASTER_SEED });
|
|
6762
5297
|
// #1013 Phase 68: 6 admin/kyc+risk endpoints 已迁出
|
|
@@ -6766,11 +5301,9 @@ registerAdminModerationRoutes(app, {
|
|
|
6766
5301
|
authFailures, INTERNAL_AUDITOR_ID, broadcastSystemEvent,
|
|
6767
5302
|
logAdminAction,
|
|
6768
5303
|
});
|
|
6769
|
-
//
|
|
5304
|
+
// 邀请 endpoints — Phase 98 已迁出
|
|
6770
5305
|
registerReferralRoutes(app, {
|
|
6771
5306
|
db, auth,
|
|
6772
|
-
requireProtocolAdmin: (req, res) => requireAdminPermission(req, res, 'protocol'),
|
|
6773
|
-
logAdminAction, issueInviteSlot, inviteRotationLookup,
|
|
6774
5307
|
});
|
|
6775
5308
|
// 推土机权限:是否允许作为 sponsor 拿分享佣金
|
|
6776
5309
|
// 默认:必须有 ≥ 1 笔 completed 订单(verified buyer)才能 sponsor
|
|
@@ -6833,7 +5366,7 @@ registerAdminReportsRoutes(app, {
|
|
|
6833
5366
|
requireArbitrationAdmin: (req, res) => requireAdminPermission(req, res, 'arbitration'),
|
|
6834
5367
|
requireProtocolAdmin: (req, res) => requireAdminPermission(req, res, 'protocol'),
|
|
6835
5368
|
});
|
|
6836
|
-
// ───
|
|
5369
|
+
// ─── 放置树挂靠(中性参与记录:position + 每腿 PV 累计) ───────────────────────
|
|
6837
5370
|
const PV_PROPAGATION_DEPTH_LIMIT = 5000; // PV 累积深度上限(admin 可调,存 system_state 之后)
|
|
6838
5371
|
// (2026-06-04 移除 countSubtreeUsers — 旧实现只数单条脊链, 名实不符;
|
|
6839
5372
|
// team_count 改读增量维护的 users.left_count/right_count, 见 pickPreferredSide + joinPowerLeg)
|
|
@@ -6907,12 +5440,15 @@ function joinPowerLeg(inviterId, side, newUserId) {
|
|
|
6907
5440
|
// ─── 原子能 Cron 结算引擎 ─────────────────────────────────────
|
|
6908
5441
|
// Step 1: 处理 pv_ledger → 累积到上线 total_left/right_pv(最多 5000 层)
|
|
6909
5442
|
function processPvLedger() {
|
|
5443
|
+
// Category C: 纯聚合(pv_ledger → total_left/right_pv 计数,不产生 score/WAZ/权益)= 参与记录 → 默认 ON。
|
|
5444
|
+
if (!participationRecordingActive(db))
|
|
5445
|
+
return 0;
|
|
6910
5446
|
const pending = db.prepare(`SELECT * FROM pv_ledger WHERE processed = 0 ORDER BY created_at ASC LIMIT 1000`).all();
|
|
6911
5447
|
for (const lg of pending) {
|
|
6912
5448
|
try {
|
|
6913
5449
|
const buyer = db.prepare("SELECT placement_id, placement_side, placement_path FROM users WHERE id = ?").get(lg.buyer_id);
|
|
6914
5450
|
if (!buyer?.placement_id || !buyer.placement_side) {
|
|
6915
|
-
// buyer
|
|
5451
|
+
// buyer 不在放置树(无 placement)→ 流水跳过但标 processed
|
|
6916
5452
|
db.prepare("UPDATE pv_ledger SET processed = 1 WHERE id = ?").run(lg.id);
|
|
6917
5453
|
continue;
|
|
6918
5454
|
}
|
|
@@ -6957,258 +5493,17 @@ function processPvLedger() {
|
|
|
6957
5493
|
}
|
|
6958
5494
|
return pending.length;
|
|
6959
5495
|
}
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
};
|
|
6972
|
-
}
|
|
6973
|
-
}
|
|
6974
|
-
return null; // 不达最低档(< 300 PV)→ PV 保留滚动到下一期
|
|
6975
|
-
}
|
|
6976
|
-
// Step 2: 对碰结算 — 单次匹配 + 双侧全清
|
|
6977
|
-
function runBinarySettlement() {
|
|
6978
|
-
const tiers = db.prepare("SELECT tier, pv_threshold, base_score, discount_coef, score_per_hit FROM binary_tier_config WHERE active = 1 ORDER BY pv_threshold DESC").all();
|
|
6979
|
-
if (tiers.length === 0)
|
|
6980
|
-
return 0;
|
|
6981
|
-
const SINGLE_USER_PERIOD_CAP = 1500; // paranoia 上限(实际上 single-match 已天然限速)
|
|
6982
|
-
const dirty = db.prepare(`SELECT id, total_left_pv, total_right_pv FROM users WHERE pv_dirty_at IS NOT NULL`).all();
|
|
6983
|
-
let settled = 0;
|
|
6984
|
-
const now = new Date().toISOString();
|
|
6985
|
-
const periodStart = new Date(Date.now() - 7 * 86400_000).toISOString();
|
|
6986
|
-
for (const u of dirty) {
|
|
6987
|
-
// 注:对碰【分数照常计算累积】,不在此 gate region。PV 经济闸在【兑付】层
|
|
6988
|
-
// (score → WAZ 时按 region pv_enabled 决定是否真实发放;未开启则分数挂账,
|
|
6989
|
-
// 待辖区开启 / 用户迁移到可用辖区再兑现)。2026-06-04 解耦设计。
|
|
6990
|
-
// 周期内已得 Score(paranoia 封顶)
|
|
6991
|
-
const weekScore = db.prepare(`SELECT COALESCE(SUM(score),0) as s FROM binary_score_records WHERE user_id = ? AND created_at > ?`).get(u.id, periodStart).s;
|
|
6992
|
-
if (weekScore >= SINGLE_USER_PERIOD_CAP) {
|
|
6993
|
-
db.prepare("UPDATE users SET pv_dirty_at = NULL WHERE id = ?").run(u.id);
|
|
6994
|
-
continue;
|
|
6995
|
-
}
|
|
6996
|
-
const match = calculate7LevelTaperingScore(u.total_left_pv, u.total_right_pv, tiers);
|
|
6997
|
-
if (!match) {
|
|
6998
|
-
// 不达最低档 → PV 保留滚动到下一期;只清 dirty 标记
|
|
6999
|
-
db.prepare("UPDATE users SET pv_dirty_at = NULL WHERE id = ?").run(u.id);
|
|
7000
|
-
continue;
|
|
7001
|
-
}
|
|
7002
|
-
// 命中 → 记 score + 双侧全清(含溢出归零,按 spec "对碰完成后双侧清零")
|
|
7003
|
-
db.prepare(`INSERT INTO binary_score_records (id, user_id, tier, score, consumed_left_pv, consumed_right_pv, period_start, period_end)
|
|
7004
|
-
VALUES (?,?,?,?,?,?,?,?)`)
|
|
7005
|
-
.run(generateId('bsr'), u.id, match.tier, match.score, match.consumedLeft, match.consumedRight, periodStart, now);
|
|
7006
|
-
db.prepare(`UPDATE global_fund SET total_scores_pending = total_scores_pending + ? WHERE id = 1`).run(match.score);
|
|
7007
|
-
db.prepare("UPDATE users SET total_left_pv = 0, total_right_pv = 0, pv_dirty_at = NULL WHERE id = ?").run(u.id);
|
|
7008
|
-
settled++;
|
|
7009
|
-
}
|
|
7010
|
-
return settled;
|
|
7011
|
-
}
|
|
7012
|
-
// 管理津贴比例(大博主的 L1/L2/L3 对碰获利时,博主额外得 10/5/2%)
|
|
7013
|
-
const MGMT_BONUS_RATES = [0.10, 0.05, 0.02];
|
|
7014
|
-
function executeSafeSettlementCron() {
|
|
7015
|
-
const result = { periodId: '', status: 'noop' };
|
|
7016
|
-
const txn = db.transaction(() => {
|
|
7017
|
-
// 1. 快照基金池余额
|
|
7018
|
-
const fund = db.prepare("SELECT pool_balance FROM global_fund WHERE id = 1").get();
|
|
7019
|
-
const fundBalanceStart = Math.round(Number(fund?.pool_balance ?? 0) * 100) / 100;
|
|
7020
|
-
if (fundBalanceStart <= 0) {
|
|
7021
|
-
result.status = 'empty_pool';
|
|
7022
|
-
return;
|
|
7023
|
-
}
|
|
7024
|
-
// 2. 全网当期 pending Score 汇总
|
|
7025
|
-
// 2026-06-04 解耦:PV 隐藏不关闭 —— 分数照常计算累积。兑付层按 earner【当前 region】
|
|
7026
|
-
// 的 pv_enabled 过滤:未开启的 earner 分数【保留 pending(不入本期分配、不稀释合格者单价)】,
|
|
7027
|
-
// 待辖区开启 / 用户迁移到可用辖区,下期自动兑现。挂账的钱按实际发放扣减(见步骤7)自然留池。
|
|
7028
|
-
const allPending = db.prepare(`SELECT id, user_id, score FROM binary_score_records WHERE settled_at IS NULL`).all();
|
|
7029
|
-
const pending = allPending.filter(r => {
|
|
7030
|
-
const reg = db.prepare("SELECT region FROM users WHERE id = ?").get(r.user_id)?.region || 'global';
|
|
7031
|
-
return regionPvEnabled(reg);
|
|
7032
|
-
});
|
|
7033
|
-
if (pending.length === 0) {
|
|
7034
|
-
result.status = 'no_pending';
|
|
7035
|
-
return;
|
|
7036
|
-
}
|
|
7037
|
-
const totalScores = pending.reduce((s, r) => s + Number(r.score), 0);
|
|
7038
|
-
if (totalScores <= 0) {
|
|
7039
|
-
result.status = 'no_pending';
|
|
7040
|
-
return;
|
|
7041
|
-
}
|
|
7042
|
-
// 3. 4 周历史均值(settlement_periods 中近 28 天 completed 行的 deposited 均值)
|
|
7043
|
-
const histRow = db.prepare(`
|
|
7044
|
-
SELECT AVG(deposited_this_period) as avg_dep
|
|
7045
|
-
FROM settlement_periods
|
|
7046
|
-
WHERE status = 'completed' AND started_at > datetime('now', '-28 days')
|
|
7047
|
-
`).get();
|
|
7048
|
-
const historyAverage = Math.round(Number(histRow?.avg_dep ?? 0) * 100) / 100;
|
|
7049
|
-
// 3.5. R11 风控:基金池水位硬停(防挤兑)
|
|
7050
|
-
// - 相对底线:池子 < 历史均值的 20% → 硬停(已稳定运行后保护机制)
|
|
7051
|
-
// - 绝对底线:池子 < 100 WAZ → 硬停(冷启动期 / 极端情况保护)
|
|
7052
|
-
// 触发时写一条 paused 周期日志(审计透明,便于 admin 监控 + 用户解释)
|
|
7053
|
-
const POOL_FLOOR_RATIO = 0.2;
|
|
7054
|
-
const POOL_FLOOR_ABSOLUTE = 100;
|
|
7055
|
-
let pauseReason = null;
|
|
7056
|
-
if (fundBalanceStart < POOL_FLOOR_ABSOLUTE) {
|
|
7057
|
-
pauseReason = `pool_balance ${fundBalanceStart} < absolute floor ${POOL_FLOOR_ABSOLUTE}`;
|
|
7058
|
-
}
|
|
7059
|
-
else if (historyAverage > 0 && fundBalanceStart < historyAverage * POOL_FLOOR_RATIO) {
|
|
7060
|
-
pauseReason = `pool_balance ${fundBalanceStart} < ${POOL_FLOOR_RATIO * 100}% of hist avg ${historyAverage}`;
|
|
7061
|
-
}
|
|
7062
|
-
if (pauseReason) {
|
|
7063
|
-
const periodId = generateId('sp');
|
|
7064
|
-
db.prepare(`INSERT INTO settlement_periods (
|
|
7065
|
-
period_id, started_at, completed_at, fund_balance_start,
|
|
7066
|
-
history_average, total_scores, status, note
|
|
7067
|
-
) VALUES (?, datetime('now'), datetime('now'), ?, ?, ?, 'paused_low_water', ?)`)
|
|
7068
|
-
.run(periodId, fundBalanceStart, historyAverage, totalScores, pauseReason);
|
|
7069
|
-
Object.assign(result, {
|
|
7070
|
-
periodId, status: 'paused_low_water',
|
|
7071
|
-
fund_balance_start: fundBalanceStart, history_average: historyAverage,
|
|
7072
|
-
total_scores: totalScores, pause_reason: pauseReason,
|
|
7073
|
-
});
|
|
7074
|
-
return;
|
|
7075
|
-
}
|
|
7076
|
-
// 4. 动态拨出率(30% / 50% / 70%)
|
|
7077
|
-
let payoutRate = 0.5;
|
|
7078
|
-
if (historyAverage > 0) {
|
|
7079
|
-
if (fundBalanceStart > historyAverage * 2)
|
|
7080
|
-
payoutRate = 0.7; // 蓄水池极度健康
|
|
7081
|
-
else if (fundBalanceStart < historyAverage * 0.5)
|
|
7082
|
-
payoutRate = 0.3; // 水位见底保护
|
|
7083
|
-
}
|
|
7084
|
-
// 冷启动(无历史)默认 0.5
|
|
7085
|
-
const poolToDistribute = Math.round(fundBalanceStart * payoutRate * 100) / 100;
|
|
7086
|
-
const nValueCash = poolToDistribute / totalScores;
|
|
7087
|
-
// V3 动态分配率:score_per_hit 已经 = 元,cap 是"票面比例"
|
|
7088
|
-
// 池子健康度 = pool / hist_avg
|
|
7089
|
-
// ≥ 2.0 → cap 1.6 (奖励早期用户,多发 60%)
|
|
7090
|
-
// 1.0-2.0 → cap 1.0-1.6 线性插值
|
|
7091
|
-
// < 1.0 → cap 1.0 (保底拿满票面)
|
|
7092
|
-
// 冷启动期(hist=0)默认 1.0
|
|
7093
|
-
let distributionCap = 1.0;
|
|
7094
|
-
if (historyAverage > 0) {
|
|
7095
|
-
const healthRatio = fundBalanceStart / historyAverage;
|
|
7096
|
-
distributionCap = Math.max(1.0, Math.min(1.6, healthRatio));
|
|
7097
|
-
}
|
|
7098
|
-
const effectiveUnitCash = Math.min(nValueCash, distributionCap);
|
|
7099
|
-
// 5. 本期入池额(用于下次的历史均值数据源)
|
|
7100
|
-
const lastPeriodEnd = db.prepare(`SELECT MAX(started_at) as t FROM settlement_periods WHERE status = 'completed'`).get().t;
|
|
7101
|
-
const depositedThisPeriod = Number(db.prepare(lastPeriodEnd
|
|
7102
|
-
? `SELECT COALESCE(SUM(amount_base + amount_l3), 0) as s FROM fund_deposits WHERE deposited_at > ?`
|
|
7103
|
-
: `SELECT COALESCE(SUM(amount_base + amount_l3), 0) as s FROM fund_deposits`).get(...(lastPeriodEnd ? [lastPeriodEnd] : [])).s);
|
|
7104
|
-
// 6. 派发(按 effectiveUnitCash 计算每用户 cash,等额 WAZ 入钱包)
|
|
7105
|
-
let cashDistributed = 0;
|
|
7106
|
-
let bonusPaid = 0;
|
|
7107
|
-
let settledUsers = 0;
|
|
7108
|
-
const mgmtEnabled = db.prepare("SELECT value FROM system_state WHERE key = 'mgmt_bonus_enabled'").get()?.value === '1';
|
|
7109
|
-
for (const r of pending) {
|
|
7110
|
-
const cashDue = Math.round(Number(r.score) * effectiveUnitCash * 100) / 100;
|
|
7111
|
-
if (cashDue <= 0) {
|
|
7112
|
-
db.prepare("UPDATE binary_score_records SET settled_at = datetime('now'), waz_amount = 0 WHERE id = ?").run(r.id);
|
|
7113
|
-
continue;
|
|
7114
|
-
}
|
|
7115
|
-
// V1:1 WAZ = 1 元(场景 B 杠杆延后)
|
|
7116
|
-
const wazAmount = cashDue;
|
|
7117
|
-
// RFC-002 §3.5 PV-pair opt-in gate (PR-1c-b) — symmetric to settleCommission L1/L2/L3 gate
|
|
7118
|
-
// opted-in → normal wallet credit
|
|
7119
|
-
// opted-out + 'deactivate' → 不发放,waz 留在 PV 资金池(forfeit,非慈善)
|
|
7120
|
-
// opted-out + other → escrow(pv_pair),金额从 pool 移入 pv_escrow_reserve(#1106 隔离负债)
|
|
7121
|
-
// 2026-06-04 修双计 bug:deactivate 旧版 redirectToCharity 新增慈善余额,但 waz 因不计入
|
|
7122
|
-
// cashDistributed 已留在 pool → 钱印了两份。现 deactivate 仅标记 settled,钱留 PV 资金池。
|
|
7123
|
-
const optIn = db.prepare("SELECT rewards_opted_in FROM users WHERE id = ?").get(r.user_id)?.rewards_opted_in ?? 0;
|
|
7124
|
-
if (optIn !== 1) {
|
|
7125
|
-
const lastAction = db.prepare("SELECT action FROM rewards_applications WHERE user_id = ? ORDER BY created_at DESC LIMIT 1").get(r.user_id)?.action;
|
|
7126
|
-
const isEscrow = lastAction !== 'deactivate';
|
|
7127
|
-
db.transaction(() => {
|
|
7128
|
-
if (isEscrow) {
|
|
7129
|
-
// never_activated / auto_downgrade → escrow(pv_pair)。
|
|
7130
|
-
// #1106:把 waz 从可分配池移入 pv_escrow_reserve(隔离负债),避免后续周期把这笔预留发给别人、
|
|
7131
|
-
// 到兑付时池里没钱却仍给钱包加钱(凭空印钱)。兑付从 reserve 出、到期退回 pool。
|
|
7132
|
-
const escrowDays = Number(db.prepare("SELECT value FROM protocol_params WHERE key = 'rewards_opt_in.escrow_days'").get()?.value ?? 30);
|
|
7133
|
-
const nowMs = Date.now();
|
|
7134
|
-
db.prepare(`INSERT INTO pending_commission_escrow (recipient_user_id, order_id, amount, attribution_path, status, created_at, expires_at) VALUES (?, NULL, ?, 'pv_pair', 'pending', ?, ?)`)
|
|
7135
|
-
.run(r.user_id, wazAmount, nowMs, nowMs + escrowDays * 86400 * 1000);
|
|
7136
|
-
db.prepare("UPDATE global_fund SET pv_escrow_reserve = pv_escrow_reserve + ? WHERE id = 1").run(wazAmount);
|
|
7137
|
-
}
|
|
7138
|
-
// deactivate:什么都不转,waz 自然留在 pool(cashRetained)
|
|
7139
|
-
db.prepare("UPDATE binary_score_records SET settled_at = datetime('now'), waz_amount = 0 WHERE id = ?").run(r.id);
|
|
7140
|
-
})();
|
|
7141
|
-
// escrow 分支:计入 cashDistributed → 末尾 cashRetained 会把这笔从 pool 扣掉(已移入 reserve)。
|
|
7142
|
-
// deactivate 分支:不计入 → 留在 pool。
|
|
7143
|
-
if (isEscrow)
|
|
7144
|
-
cashDistributed += cashDue;
|
|
7145
|
-
// Do NOT credit wallet, lifetime_score, or count as settledUser — opt-out path
|
|
7146
|
-
continue;
|
|
7147
|
-
}
|
|
7148
|
-
db.prepare("UPDATE wallets SET balance = balance + ?, earned = earned + ? WHERE user_id = ?").run(wazAmount, wazAmount, r.user_id);
|
|
7149
|
-
db.prepare("UPDATE binary_score_records SET settled_at = datetime('now'), waz_amount = ? WHERE id = ?").run(wazAmount, r.id);
|
|
7150
|
-
// V3 用户成长等级:累加 lifetime_score(实际兑现的 WAZ 金额,不是 pending score)
|
|
7151
|
-
db.prepare("UPDATE users SET lifetime_score = COALESCE(lifetime_score, 0) + ? WHERE id = ?").run(wazAmount, r.user_id);
|
|
7152
|
-
cashDistributed += cashDue;
|
|
7153
|
-
settledUsers++;
|
|
7154
|
-
// 管理津贴(默认关闭;仅 mgmt_bonus_enabled='1' 且 ancestor mgmt_bonus_eligible=1 时发)
|
|
7155
|
-
if (!mgmtEnabled)
|
|
7156
|
-
continue;
|
|
7157
|
-
try {
|
|
7158
|
-
const sp = db.prepare("SELECT sponsor_path FROM users WHERE id = ?").get(r.user_id);
|
|
7159
|
-
const ancestors = sp?.sponsor_path ? sp.sponsor_path.split('>').reverse().slice(0, 3) : [];
|
|
7160
|
-
for (let i = 0; i < ancestors.length; i++) {
|
|
7161
|
-
const elig = db.prepare("SELECT mgmt_bonus_eligible FROM users WHERE id = ?").get(ancestors[i]);
|
|
7162
|
-
if (!elig?.mgmt_bonus_eligible)
|
|
7163
|
-
continue;
|
|
7164
|
-
const bonus = Math.round(wazAmount * MGMT_BONUS_RATES[i] * 100) / 100;
|
|
7165
|
-
if (bonus <= 0)
|
|
7166
|
-
continue;
|
|
7167
|
-
const pool = db.prepare("SELECT balance FROM management_bonus_pool WHERE id=1").get();
|
|
7168
|
-
if (pool.balance < bonus)
|
|
7169
|
-
continue;
|
|
7170
|
-
db.prepare("UPDATE management_bonus_pool SET balance = balance - ? WHERE id=1").run(bonus);
|
|
7171
|
-
db.prepare("UPDATE wallets SET balance = balance + ?, earned = earned + ? WHERE user_id = ?").run(bonus, bonus, ancestors[i]);
|
|
7172
|
-
bonusPaid += bonus;
|
|
7173
|
-
}
|
|
7174
|
-
}
|
|
7175
|
-
catch (e) {
|
|
7176
|
-
console.error('[mgmt bonus]', e);
|
|
7177
|
-
}
|
|
7178
|
-
}
|
|
7179
|
-
cashDistributed = Math.round(cashDistributed * 100) / 100;
|
|
7180
|
-
// 7. 沉淀剩余(pool - distributed = surplus 留在基金池,下期使用)
|
|
7181
|
-
const cashRetained = Math.round((fundBalanceStart - cashDistributed) * 100) / 100;
|
|
7182
|
-
db.prepare("UPDATE global_fund SET pool_balance = ?, total_scores_pending = 0, current_n = ?, last_settled_at = datetime('now') WHERE id = 1")
|
|
7183
|
-
.run(cashRetained, effectiveUnitCash);
|
|
7184
|
-
// 8. 周期日志(幂等保障 + 4 周历史源 + 审计)
|
|
7185
|
-
const periodId = generateId('sp');
|
|
7186
|
-
db.prepare(`INSERT INTO settlement_periods (
|
|
7187
|
-
period_id, started_at, completed_at, fund_balance_start, deposited_this_period,
|
|
7188
|
-
history_average, payout_rate, pool_to_distribute, total_scores, n_value_cash,
|
|
7189
|
-
effective_unit_cash, cash_distributed, cash_retained, settled_users, status
|
|
7190
|
-
) VALUES (?, datetime('now'), datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'completed')`)
|
|
7191
|
-
.run(periodId, fundBalanceStart, depositedThisPeriod, historyAverage, payoutRate, poolToDistribute, totalScores, nValueCash, effectiveUnitCash, cashDistributed, cashRetained, settledUsers);
|
|
7192
|
-
Object.assign(result, {
|
|
7193
|
-
periodId, status: 'completed',
|
|
7194
|
-
fund_balance_start: fundBalanceStart, history_average: historyAverage,
|
|
7195
|
-
payout_rate: payoutRate, pool_to_distribute: poolToDistribute,
|
|
7196
|
-
total_scores: totalScores, n_value_cash: Math.round(nValueCash * 100) / 100,
|
|
7197
|
-
effective_unit_cash: Math.round(effectiveUnitCash * 100) / 100,
|
|
7198
|
-
cash_distributed: cashDistributed, cash_retained: cashRetained,
|
|
7199
|
-
settled_users: settledUsers, mgmt_bonus_paid: Math.round(bonusPaid * 100) / 100,
|
|
7200
|
-
});
|
|
7201
|
-
});
|
|
7202
|
-
try {
|
|
7203
|
-
txn();
|
|
7204
|
-
}
|
|
7205
|
-
catch (e) {
|
|
7206
|
-
console.error('[safeSettlement]', e);
|
|
7207
|
-
result.status = 'failed';
|
|
7208
|
-
}
|
|
7209
|
-
return result;
|
|
7210
|
-
}
|
|
7211
|
-
// 启动定时任务(每 1h 处理 ledger + settle;每 24h 安全阀结算)
|
|
5496
|
+
// ─── 匹配奖励结算引擎(Category C)— 已切除 / EXCISED ───────────────────────────
|
|
5497
|
+
// 匹配奖励结算 + 兑付 = REWARD 路径,已从公开代码切除:internal/pv-settlement.ts
|
|
5498
|
+
// 现为永久 no-op stub(runBinarySettlement → 0;executeSafeSettlementCron → disabled,无视 kill-switch)。
|
|
5499
|
+
// 比门控更强 —— 即便翻 matching_rewards_active='1',公开代码也无引擎可跑、不会兑付。完整引擎归档在
|
|
5500
|
+
// docs/modules/pv-settlement-engine.INTERNAL.md(gitignored)+ git 历史;重启需律师/治理放行 + 重接,非翻 flag。
|
|
5501
|
+
// 留在本文件的中性【参与记录】(默认 ON,不受影响):joinPowerLeg(放置树)/ processPvLedger(PV 聚合)/ calculatePv。
|
|
5502
|
+
// 注:工厂签名不变,下游 1h/24h cron 调用点零改动(stub 安全返回)。regionPvEnabled 为函数声明(hoisted)。
|
|
5503
|
+
const { runBinarySettlement, executeSafeSettlementCron } = createPvSettlementEngine({ db, generateId, regionPvEnabled });
|
|
5504
|
+
// 启动定时任务(每 1h 处理 ledger + settle;每 24h 兑付结算)
|
|
5505
|
+
// Category C:1h cron 混了【参与记录】(processPvLedger,默认 ON)+【奖励】(runBinarySettlement,默认 OFF) —
|
|
5506
|
+
// 不加外层守卫,各函数自门控(recording vs rewards)。24h 兑付是纯 REWARD → 外层 matchingRewardsActive 守卫。
|
|
7212
5507
|
setInterval(() => {
|
|
7213
5508
|
try {
|
|
7214
5509
|
processPvLedger();
|
|
@@ -7224,6 +5519,8 @@ setInterval(() => {
|
|
|
7224
5519
|
}
|
|
7225
5520
|
}, 60 * 60_000);
|
|
7226
5521
|
setInterval(() => {
|
|
5522
|
+
if (!matchingRewardsActive(db))
|
|
5523
|
+
return;
|
|
7227
5524
|
try {
|
|
7228
5525
|
const r = executeSafeSettlementCron();
|
|
7229
5526
|
if (r.status === 'completed') {
|
|
@@ -7275,7 +5572,7 @@ registerProfilePlacementRoutes(app, {
|
|
|
7275
5572
|
// shares/dashboard — Phase 110 已迁出
|
|
7276
5573
|
// ─── 推土机轨道:推广统计端点 ─────────────────────────────────
|
|
7277
5574
|
// #1013 Phase 77: 2 promoter endpoints 已迁出
|
|
7278
|
-
registerPromoterRoutes(app, { db, auth, isAllowedSponsor });
|
|
5575
|
+
registerPromoterRoutes(app, { db, auth, isAllowedSponsor, participationRecordingActive: () => participationRecordingActive(db), matchingRewardsActive: () => matchingRewardsActive(db) });
|
|
7279
5576
|
// ─── 成长任务(分享达人养成主线)─────────────────────────────
|
|
7280
5577
|
// #1013 Phase 30: 4 endpoints + catalog + evaluator 已迁出到 routes/growth.ts
|
|
7281
5578
|
registerGrowthRoutes(app, { db, auth });
|
|
@@ -7732,8 +6029,8 @@ registerOrdersCreateRoutes(app, {
|
|
|
7732
6029
|
getProductShareChain, isAllowedSponsor, checkStockAndMaybeDelist, auditSponsorChainCross,
|
|
7733
6030
|
appendOrderEvent, transition, notifyTransition, shouldAutoAccept, ensureCharityRep,
|
|
7734
6031
|
broadcastSystemEvent, resolveInviteCodeRef,
|
|
7735
|
-
signPassport: (message) =>
|
|
7736
|
-
issuerAddress: () =>
|
|
6032
|
+
signPassport: (message) => walletSigner.issuerSignMessage(message),
|
|
6033
|
+
issuerAddress: () => walletSigner.issuerAddress(),
|
|
7737
6034
|
});
|
|
7738
6035
|
// #1013 Phase 45: 卖家配额 + 数据中心 7 endpoints — 2026-05-31 修补:之前 import 了但忘了 register,
|
|
7739
6036
|
// 导致 /api/seller/quota-status + /api/seller/insights 落入 SPA fallback 返回 HTML,前端 JSON.parse 死循环
|
|
@@ -8375,14 +6672,8 @@ setInterval(() => {
|
|
|
8375
6672
|
// #1013 Phase 4: 8 endpoints + 4 helpers 已迁出到 routes/chat.ts
|
|
8376
6673
|
// ============================================================
|
|
8377
6674
|
registerChatRoutes(app, { db, auth, generateId, rateLimitOk });
|
|
8378
|
-
// 初始化导入次数追踪表
|
|
8379
|
-
db
|
|
8380
|
-
CREATE TABLE IF NOT EXISTS import_logs (
|
|
8381
|
-
id TEXT PRIMARY KEY,
|
|
8382
|
-
user_id TEXT NOT NULL,
|
|
8383
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
8384
|
-
)
|
|
8385
|
-
`);
|
|
6675
|
+
// 初始化导入次数追踪表 → server-schema.ts
|
|
6676
|
+
initImportLogsSchema(db);
|
|
8386
6677
|
const FREE_IMPORT_LIMIT = 10;
|
|
8387
6678
|
// #1013 Phase 114: import-product 已迁出
|
|
8388
6679
|
registerImportProductRoutes(app, {
|
|
@@ -8779,8 +7070,8 @@ registerExternalAnchorsRoutes(app, { db, auth });
|
|
|
8779
7070
|
// #1013 Phase 109: checkout/tax-preview + verify-price 已迁出
|
|
8780
7071
|
registerCheckoutHelpersRoutes(app, {
|
|
8781
7072
|
db, auth, generateId, formatProductForAgent,
|
|
8782
|
-
signPassport: (message) =>
|
|
8783
|
-
issuerAddress: () =>
|
|
7073
|
+
signPassport: (message) => walletSigner.issuerSignMessage(message),
|
|
7074
|
+
issuerAddress: () => walletSigner.issuerAddress(),
|
|
8784
7075
|
});
|
|
8785
7076
|
// ─── M8 二手板块 ────────────────────────────────────────────
|
|
8786
7077
|
// #1013 Phase 27: 6 endpoints + 4 SH_* sets + addHours 已迁出到 routes/secondhand.ts
|
|
@@ -8848,8 +7139,10 @@ function getRegionMaxLevels(region) {
|
|
|
8848
7139
|
// 已显式审计且合规允许的地区在 region_config 表里设 2 或 3
|
|
8849
7140
|
return row?.max_levels ?? 1;
|
|
8850
7141
|
}
|
|
8851
|
-
//
|
|
8852
|
-
//
|
|
7142
|
+
// PV 匹配奖励的【区域兑付过滤器】—— 不是奖励总闸。与 max_levels(佣金层级)独立。
|
|
7143
|
+
// 总闸是全局 Category C 双闸(见 pv-kill-switch.ts):实际兑付仍必须同时满足
|
|
7144
|
+
// matching_rewards_active='1' + matching_rewards_activation_cleared='1';本函数只是其后的额外区域过滤。
|
|
7145
|
+
// 未配置 / 未知地区返回 false,表示该地区过滤器不允许兑付(保守默认)。
|
|
8853
7146
|
function regionPvEnabled(region) {
|
|
8854
7147
|
const row = db.prepare("SELECT pv_enabled FROM region_config WHERE region = ?").get(region);
|
|
8855
7148
|
return Number(row?.pv_enabled ?? 0) === 1;
|
|
@@ -9013,10 +7306,13 @@ function getUserLevel(lifetimeScore) {
|
|
|
9013
7306
|
}
|
|
9014
7307
|
// V3 PV 单位:每 100 元成交 = 1 PV(pv_multiplier 默认 1.0)
|
|
9015
7308
|
// MAX_PV_PER_ORDER 1000 防单笔暴增 — 单笔订单最多产生 1000 PV (= 10 万元封顶)
|
|
9016
|
-
// 溢出部分作"协议留存"
|
|
7309
|
+
// 溢出部分作"协议留存",等同基金池入金(预留池,当前无消费方)
|
|
9017
7310
|
const PV_PER_YUAN = 0.01;
|
|
9018
7311
|
const MAX_PV_PER_ORDER = 1000;
|
|
9019
7312
|
function calculatePv(amount, multiplier = 1.0) {
|
|
7313
|
+
// Category C: PV 是【参与记录】(非收益/非兑付)→ 默认 ON,只在 participation_recording 显式关闭时不生成。
|
|
7314
|
+
if (!participationRecordingActive(db))
|
|
7315
|
+
return 0;
|
|
9020
7316
|
// 防御:负值 / NaN / Infinity 直接返回 0(不写入 pv_ledger)
|
|
9021
7317
|
if (!Number.isFinite(amount) || amount <= 0)
|
|
9022
7318
|
return 0;
|
|
@@ -9132,9 +7428,9 @@ function settleOrder(orderId) {
|
|
|
9132
7428
|
}
|
|
9133
7429
|
if (split.stakeToLockU > 0)
|
|
9134
7430
|
applyWalletDelta(db, order.seller_id, { staked: split.stakeToLockU });
|
|
9135
|
-
// 协议费拆分:50%
|
|
9136
|
-
if (split.
|
|
9137
|
-
creditColumns(db, '
|
|
7431
|
+
// 协议费拆分:50% 注入协议储备池,50% 入 sys_protocol 运营
|
|
7432
|
+
if (split.protocolToReserveU > 0)
|
|
7433
|
+
creditColumns(db, 'protocol_reserve_pool', 'id = 1', [], { balance: split.protocolToReserveU });
|
|
9138
7434
|
if (split.protocolToOpsU > 0)
|
|
9139
7435
|
applyWalletDelta(db, 'sys_protocol', { balance: split.protocolToOpsU });
|
|
9140
7436
|
// 推土机分享分润:正常分账 → 钱包;兜底 → commission_reserve(三级公池,独立科目)
|
|
@@ -9529,7 +7825,7 @@ registerEvidenceRoutes(app, { db, auth, detectFraud });
|
|
|
9529
7825
|
// #1013 Phase 1: 7 endpoint handlers 已迁出到 src/pwa/routes/webauthn.ts
|
|
9530
7826
|
// helpers (consumeGateToken / requireHumanPresence) 仍在本文件,被 withdraw/arbitrate/vote 等引用
|
|
9531
7827
|
registerWebauthnRoutes(app, {
|
|
9532
|
-
db, auth, generateId,
|
|
7828
|
+
db, auth, generateId, rateLimitOk,
|
|
9533
7829
|
rpId: WEBAUTHN_RP_ID,
|
|
9534
7830
|
rpName: WEBAUTHN_RP_NAME,
|
|
9535
7831
|
origin: WEBAUTHN_ORIGIN,
|
|
@@ -9695,7 +7991,7 @@ function settleGenericClaim(taskTable, voteTable, claimId) {
|
|
|
9695
7991
|
return { ok: true, majority, ruling: majority };
|
|
9696
7992
|
}
|
|
9697
7993
|
// Sprint 4/5 — claim 结算后的后续影响(声誉 + 目标对象计数 + 自动下架 + voter outlier)
|
|
9698
|
-
//
|
|
7994
|
+
// #420 P1-3:voter outlier 的窗口/暂停/撤销阈值由 protocol_params 驱动(见 checkVerifierOutlier + anti-abuse-thresholds.ts)
|
|
9699
7995
|
const CLAIM_AUTO_SUSPEND_THRESHOLD = 3; // 商品 / 拍卖 / 二手 累计 N 次 upheld → 自动下架
|
|
9700
7996
|
// Wave A-5: 通用 claim 撤回 helper(只有 0 票时 claimant 可撤回,退 stake)
|
|
9701
7997
|
function withdrawClaim(taskTable, voteTable, claimId, userId) {
|
|
@@ -9799,10 +8095,12 @@ function checkVerifierOutlier(verifierId) {
|
|
|
9799
8095
|
const existing = db.prepare(`SELECT type FROM claim_verifier_suspensions WHERE user_id = ? AND type = 'revoked' LIMIT 1`).get(verifierId);
|
|
9800
8096
|
if (existing)
|
|
9801
8097
|
return;
|
|
9802
|
-
//
|
|
8098
|
+
// #420 P1-3:窗口/阈值/暂停时长由 protocol_params 驱动(默认 = 原 180d/≥5/≥3/30d)
|
|
8099
|
+
const t = readAntiAbuseThresholds(db);
|
|
8100
|
+
// 统计窗口内 outlier 票数(跨所有 vote table)
|
|
9803
8101
|
const VOTE_TABLES = ['claim_verification_votes', 'product_claim_votes', 'review_claim_votes', 'secondhand_claim_votes', 'auction_claim_votes', 'wish_claim_votes'];
|
|
9804
8102
|
let outlierCount = 0;
|
|
9805
|
-
const since = new Date(Date.now() -
|
|
8103
|
+
const since = new Date(Date.now() - t.outlierWindowDays * 86400_000).toISOString();
|
|
9806
8104
|
for (const tbl of VOTE_TABLES) {
|
|
9807
8105
|
try {
|
|
9808
8106
|
const n = db.prepare(`SELECT COUNT(*) as n FROM ${tbl} WHERE verifier_id = ? AND was_majority = 0 AND voted_at > ?`).get(verifierId, since).n;
|
|
@@ -9810,26 +8108,27 @@ function checkVerifierOutlier(verifierId) {
|
|
|
9810
8108
|
}
|
|
9811
8109
|
catch { }
|
|
9812
8110
|
}
|
|
9813
|
-
|
|
8111
|
+
const band = verifierOutlierBand(outlierCount, t);
|
|
8112
|
+
if (band === 'revoke') {
|
|
9814
8113
|
// 永久撤销
|
|
9815
8114
|
db.prepare(`INSERT INTO claim_verifier_suspensions (id, user_id, type, until_at, reason, outlier_count) VALUES (?,?,?,NULL,?,?)`)
|
|
9816
|
-
.run(generateId('cvs'), verifierId, 'revoked', `累计 ${outlierCount} 次 outlier
|
|
8115
|
+
.run(generateId('cvs'), verifierId, 'revoked', `累计 ${outlierCount} 次 outlier(${t.outlierWindowDays}d 内)→ 永久撤销 verifier 资格`, outlierCount);
|
|
9817
8116
|
try {
|
|
9818
8117
|
db.prepare(`INSERT INTO notifications (id, user_id, title, body, order_id) VALUES (?,?,?,?,?)`)
|
|
9819
|
-
.run(generateId('ntf'), verifierId, '⚠ Verifier 资格已撤销', `你在
|
|
8118
|
+
.run(generateId('ntf'), verifierId, '⚠ Verifier 资格已撤销', `你在 ${t.outlierWindowDays} 天内累计 ${outlierCount} 次 outlier 投票,按协议规则资格被永久撤销。`, null);
|
|
9820
8119
|
}
|
|
9821
8120
|
catch { }
|
|
9822
8121
|
}
|
|
9823
|
-
else if (
|
|
8122
|
+
else if (band === 'suspend') {
|
|
9824
8123
|
// 临时 suspend,避免重复 suspend
|
|
9825
8124
|
const dup = db.prepare(`SELECT id FROM claim_verifier_suspensions WHERE user_id = ? AND type = 'suspended' AND (until_at IS NULL OR until_at > datetime('now')) LIMIT 1`).get(verifierId);
|
|
9826
8125
|
if (!dup) {
|
|
9827
|
-
const until = new Date(Date.now() +
|
|
8126
|
+
const until = new Date(Date.now() + t.outlierSuspendDays * 86400_000).toISOString();
|
|
9828
8127
|
db.prepare(`INSERT INTO claim_verifier_suspensions (id, user_id, type, until_at, reason, outlier_count) VALUES (?,?,?,?,?,?)`)
|
|
9829
|
-
.run(generateId('cvs'), verifierId, 'suspended', until, `累计 ${outlierCount} 次 outlier
|
|
8128
|
+
.run(generateId('cvs'), verifierId, 'suspended', until, `累计 ${outlierCount} 次 outlier(${t.outlierWindowDays}d 内)→ 暂停 ${t.outlierSuspendDays} 天`, outlierCount);
|
|
9830
8129
|
try {
|
|
9831
8130
|
db.prepare(`INSERT INTO notifications (id, user_id, title, body, order_id) VALUES (?,?,?,?,?)`)
|
|
9832
|
-
.run(generateId('ntf'), verifierId, '⏳ Verifier 资格已暂停', `你在
|
|
8131
|
+
.run(generateId('ntf'), verifierId, '⏳ Verifier 资格已暂停', `你在 ${t.outlierWindowDays} 天内累计 ${outlierCount} 次 outlier 投票,资格暂停 ${t.outlierSuspendDays} 天直至 ${until.slice(0, 10)}。`, null);
|
|
9833
8132
|
}
|
|
9834
8133
|
catch { }
|
|
9835
8134
|
}
|
|
@@ -9915,7 +8214,7 @@ registerPublicUtilsRoutes(app, {
|
|
|
9915
8214
|
db, MASTER_SEED, NODE_ENV, SERVICE_START_MS,
|
|
9916
8215
|
rateLimitOk, generateManifest, getUser, logError,
|
|
9917
8216
|
// #1045 信任锚:Phase 4 同一把签名 key 的地址,公开发布让第三方验真者锚定
|
|
9918
|
-
issuerAddress: () =>
|
|
8217
|
+
issuerAddress: () => walletSigner.issuerAddress(),
|
|
9919
8218
|
});
|
|
9920
8219
|
// ─── 静态文件 + SPA 回退(必须在所有 API 路由之后)────────────
|
|
9921
8220
|
// PWA 壳文件必须 no-cache(否则 CF/浏览器 4h 缓存挡新版本);
|
|
@@ -10041,7 +8340,8 @@ function runEnforcement() {
|
|
|
10041
8340
|
// 2. BASE_RPC_URL=https://mainnet.base.org(或自有 RPC)
|
|
10042
8341
|
// 3. USDC_CONTRACT=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913(Base mainnet USDC)
|
|
10043
8342
|
// 4. WALLET_MASTER_SEED=<高熵随机值,建议来自 KMS / 硬件签名器>
|
|
10044
|
-
// 5. 强烈建议把 HOT_WALLET 切到多签(如 Gnosis Safe
|
|
8343
|
+
// 5. 强烈建议把 HOT_WALLET 切到多签(如 Gnosis Safe)+ KMS 签名器 —— 经 WalletSigner seam
|
|
8344
|
+
// (internal/wallet-signer.ts) 换 LocalSeedSigner → KMS/Safe 实现,见 docs/HOT-WALLET-CUSTODY-MIGRATION.md
|
|
10045
8345
|
// 6. NODE_ENV=production(启用默认 seed 拒启 + bootstrap key 脱敏)
|
|
10046
8346
|
const NETWORK = (process.env.NETWORK || 'testnet').toLowerCase();
|
|
10047
8347
|
const IS_MAINNET = NETWORK === 'mainnet';
|
|
@@ -10060,7 +8360,7 @@ if (IS_MAINNET && (MASTER_SEED === 'webaz-dev-seed-changeme' || !process.env.WAL
|
|
|
10060
8360
|
}
|
|
10061
8361
|
if (IS_MAINNET && !process.env.HOT_WALLET_KMS_ACK) {
|
|
10062
8362
|
console.warn('⚠ 主网热钱包 HOT_WALLET 私钥仍由 MASTER_SEED 派生 — 强烈建议改 KMS / 多签');
|
|
10063
|
-
console.warn(' 设 HOT_WALLET_KMS_ACK=1
|
|
8363
|
+
console.warn(' 设 HOT_WALLET_KMS_ACK=1 表示你已知悉风险继续运行(生产应把 WalletSigner 换成 KMS/多签实现,见 docs/HOT-WALLET-CUSTODY-MIGRATION.md)');
|
|
10064
8364
|
}
|
|
10065
8365
|
const USDC_ABI = parseAbi([
|
|
10066
8366
|
'function transfer(address to, uint256 value) returns (bool)',
|
|
@@ -10074,10 +8374,10 @@ const publicClient = createPublicClient({
|
|
|
10074
8374
|
transport: http(rpcUrl),
|
|
10075
8375
|
});
|
|
10076
8376
|
// ─── 热钱包(归集 + 提现出账)────────────────────────────────────
|
|
10077
|
-
|
|
10078
|
-
const HOT_WALLET_ADDR =
|
|
8377
|
+
// Phase 0: hot-wallet signing via the WalletSigner seam (Phase 1 swaps LocalSeedSigner → KMS here).
|
|
8378
|
+
const HOT_WALLET_ADDR = walletSigner.hotAddress();
|
|
10079
8379
|
const hotWalletClient = createWalletClient({
|
|
10080
|
-
account:
|
|
8380
|
+
account: walletSigner.hotAccount(),
|
|
10081
8381
|
chain: ACTIVE_CHAIN,
|
|
10082
8382
|
transport: http(rpcUrl),
|
|
10083
8383
|
});
|
|
@@ -10099,7 +8399,7 @@ async function sweepToHotWallet(userId, depositAddress) {
|
|
|
10099
8399
|
await publicClient.waitForTransactionReceipt({ hash: ethHash });
|
|
10100
8400
|
// 充值地址把 USDC 转给热钱包
|
|
10101
8401
|
const depClient = createWalletClient({
|
|
10102
|
-
account:
|
|
8402
|
+
account: walletSigner.depositAccount(userId),
|
|
10103
8403
|
chain: ACTIVE_CHAIN,
|
|
10104
8404
|
transport: http(rpcUrl),
|
|
10105
8405
|
});
|
|
@@ -10277,22 +8577,7 @@ function startDepositWatcher() {
|
|
|
10277
8577
|
// 轻量级自建错误上报 — 避免外部 Sentry 依赖
|
|
10278
8578
|
// 后端:进程级 uncaughtException + unhandledRejection
|
|
10279
8579
|
// 前端:POST /api/error-report(window.onerror → 入此表)
|
|
10280
|
-
db
|
|
10281
|
-
CREATE TABLE IF NOT EXISTS error_log (
|
|
10282
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10283
|
-
source TEXT NOT NULL, -- 'server-uncaught' | 'server-rejection' | 'client'
|
|
10284
|
-
message TEXT NOT NULL,
|
|
10285
|
-
stack TEXT,
|
|
10286
|
-
url TEXT, -- 客户端 location.href
|
|
10287
|
-
user_agent TEXT, -- 客户端 UA
|
|
10288
|
-
user_id TEXT, -- 已登录用户(可空)
|
|
10289
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
10290
|
-
)
|
|
10291
|
-
`);
|
|
10292
|
-
try {
|
|
10293
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_error_log_created ON error_log(created_at)');
|
|
10294
|
-
}
|
|
10295
|
-
catch { }
|
|
8580
|
+
initErrorLogSchema(db);
|
|
10296
8581
|
// ─── 治理岗位上岗(W3.5-B,2026-06-02)──────────────────────────
|
|
10297
8582
|
// docs/GOVERNANCE-ONBOARDING.md — arbitrator + verifier 申请 / 上岗 / 卸任 / 申诉
|
|
10298
8583
|
// 1 表:governance_applications(append-only,记录 apply/activate/resign/auto_deactivate/appeal)
|