@seasonkoh/webaz 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/README.zh-CN.md +7 -6
- package/dist/bond-refund-blockers.js +50 -0
- package/dist/bond-slash.js +100 -0
- package/dist/bond-terms.js +31 -0
- package/dist/currency.js +16 -0
- package/dist/deposit-rails.js +52 -0
- package/dist/direct-pay-aml-monitor.js +67 -0
- package/dist/direct-pay-aml-review.js +40 -0
- package/dist/direct-pay-base-bond-entry.js +5 -0
- package/dist/direct-pay-bond-rail-clearance.js +99 -0
- package/dist/direct-pay-cancel-refund.js +160 -0
- package/dist/direct-pay-compliance-ingress.js +145 -0
- package/dist/direct-pay-controls.js +136 -0
- package/dist/direct-pay-create.js +231 -0
- package/dist/direct-pay-deferral-quota.js +43 -0
- package/dist/direct-pay-disclosures.js +50 -0
- package/dist/direct-pay-eligibility.js +46 -0
- package/dist/direct-pay-fee-ar.js +241 -0
- package/dist/direct-pay-fee-prepay-request.js +80 -0
- package/dist/direct-pay-launch-readiness.js +108 -0
- package/dist/direct-pay-launch-summary.js +68 -0
- package/dist/direct-pay-ledger.js +94 -0
- package/dist/direct-pay-returns.js +104 -0
- package/dist/direct-pay-stock.js +9 -0
- package/dist/direct-receive-account-qr.js +77 -0
- package/dist/direct-receive-accounts.js +82 -0
- package/dist/direct-receive-deferral.js +171 -0
- package/dist/direct-receive-deposits.js +365 -0
- package/dist/direct-receive-payment-instruction.js +26 -0
- package/dist/free-shipping.js +37 -0
- package/dist/fx-rates.js +78 -0
- package/dist/layer0-foundation/L0-1-database/schema.js +777 -1
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +1 -0
- package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +15 -5
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +114 -0
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +1 -1
- package/dist/layer1-agent/L1-1-mcp-server/cli.js +64 -0
- package/dist/layer1-agent/L1-1-mcp-server/network-mode.js +11 -0
- package/dist/layer1-agent/L1-1-mcp-server/server.js +99 -32
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +110 -41
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +133 -31
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +16 -4
- package/dist/layer3-trust/L3-1-dispute-engine/mutual-cancel.js +156 -0
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +4 -4
- package/dist/ledger.js +12 -3
- package/dist/mcp.js +23 -4
- package/dist/merchant-bond-domain.js +64 -0
- package/dist/merchant-bond-exposure.js +78 -0
- package/dist/merchant-bond-watcher.js +26 -0
- package/dist/payment-rails.js +29 -0
- package/dist/platform-receive-accounts.js +94 -0
- package/dist/product-verification.js +93 -0
- package/dist/pwa/acp-feed.js +3 -2
- package/dist/pwa/arbitration-read-admin.js +37 -0
- package/dist/pwa/arbitrator-lifecycle.js +100 -0
- package/dist/pwa/contract-fingerprint.js +18 -0
- package/dist/pwa/direct-pay-guards.js +20 -0
- package/dist/pwa/direct-pay-order-redaction.js +42 -0
- package/dist/pwa/endpoint-actions.js +3 -0
- package/dist/pwa/human-presence.js +2 -6
- package/dist/pwa/public/app-account.js +9 -9
- package/dist/pwa/public/app-admin-disputes.js +55 -0
- package/dist/pwa/public/app-agent-appeal.js +90 -0
- package/dist/pwa/public/app-agent-approvals.js +93 -0
- package/dist/pwa/public/app-agent-pair.js +127 -0
- package/dist/pwa/public/app-ai.js +10 -10
- package/dist/pwa/public/app-arbitrator-admin.js +87 -0
- package/dist/pwa/public/app-arbitrator-entry.js +9 -0
- package/dist/pwa/public/app-bond-deferral-ui.js +9 -0
- package/dist/pwa/public/app-bond-refund-ui.js +66 -0
- package/dist/pwa/public/app-bond-slash-ui.js +74 -0
- package/dist/pwa/public/app-bond-terms-ui.js +23 -0
- package/dist/pwa/public/app-bond-ui.js +108 -0
- package/dist/pwa/public/app-chat-poll.js +29 -0
- package/dist/pwa/public/app-contribution-hub.js +23 -0
- package/dist/pwa/public/app-create-kinds.js +17 -0
- package/dist/pwa/public/app-direct-pay-accounts.js +141 -0
- package/dist/pwa/public/app-direct-pay-buyer.js +72 -0
- package/dist/pwa/public/app-direct-pay-cancel-refund.js +72 -0
- package/dist/pwa/public/app-direct-pay-compliance.js +67 -0
- package/dist/pwa/public/app-direct-pay-copy.js +12 -0
- package/dist/pwa/public/app-direct-pay-deferral-admin.js +72 -0
- package/dist/pwa/public/app-direct-pay-deferral.js +61 -0
- package/dist/pwa/public/app-direct-pay-fee-center.js +33 -0
- package/dist/pwa/public/app-direct-pay-fee-history.js +39 -0
- package/dist/pwa/public/app-direct-pay-fee-ops.js +112 -0
- package/dist/pwa/public/app-direct-pay-fee-request.js +81 -0
- package/dist/pwa/public/app-direct-pay-fee-requests-admin.js +70 -0
- package/dist/pwa/public/app-direct-pay-memo.js +14 -0
- package/dist/pwa/public/app-direct-pay-negotiation.js +35 -0
- package/dist/pwa/public/app-direct-pay-pay.js +15 -0
- package/dist/pwa/public/app-direct-pay-paymodal.js +32 -0
- package/dist/pwa/public/app-direct-pay-product-verify.js +103 -0
- package/dist/pwa/public/app-direct-pay-readiness.js +38 -0
- package/dist/pwa/public/app-direct-pay-reconcile.js +19 -0
- package/dist/pwa/public/app-direct-pay-returns.js +56 -0
- package/dist/pwa/public/app-direct-pay-reveal.js +82 -0
- package/dist/pwa/public/app-direct-pay-sales-report.js +70 -0
- package/dist/pwa/public/app-direct-pay-store-verify.js +100 -0
- package/dist/pwa/public/app-direct-pay.js +226 -0
- package/dist/pwa/public/app-discover.js +20 -20
- package/dist/pwa/public/app-dispute-close-ui.js +38 -0
- package/dist/pwa/public/app-external-links.js +32 -0
- package/dist/pwa/public/app-free-shipping-ui.js +29 -0
- package/dist/pwa/public/app-gmv-rail-split.js +12 -0
- package/dist/pwa/public/app-listing-commerce-ui.js +72 -0
- package/dist/pwa/public/app-listings.js +4 -4
- package/dist/pwa/public/app-mutual-cancel.js +54 -0
- package/dist/pwa/public/app-notif-templates-orders.js +43 -0
- package/dist/pwa/public/app-notif-templates.js +22 -0
- package/dist/pwa/public/app-order-accept-ui.js +158 -0
- package/dist/pwa/public/app-order-errors.js +50 -0
- package/dist/pwa/public/app-order-labels.js +19 -0
- package/dist/pwa/public/app-order-rail-filter.js +26 -0
- package/dist/pwa/public/app-platform-receive-accounts.js +140 -0
- package/dist/pwa/public/app-poll-governor.js +22 -0
- package/dist/pwa/public/app-prelaunch-waz.js +39 -0
- package/dist/pwa/public/app-price.js +55 -0
- package/dist/pwa/public/app-product-media.js +15 -0
- package/dist/pwa/public/app-profile.js +10 -10
- package/dist/pwa/public/app-purchase-terms-ui.js +68 -0
- package/dist/pwa/public/app-sale-regions-ui.js +38 -0
- package/dist/pwa/public/app-seller.js +5 -5
- package/dist/pwa/public/app-shop.js +19 -19
- package/dist/pwa/public/app-trade-tax-ui.js +33 -0
- package/dist/pwa/public/app.js +264 -295
- package/dist/pwa/public/i18n.js +1068 -197
- package/dist/pwa/public/index.html +58 -0
- package/dist/pwa/public/openapi.json +1845 -653
- package/dist/pwa/public/style.css +3 -0
- package/dist/pwa/routes/admin-analytics.js +4 -2
- package/dist/pwa/routes/admin-direct-receive-deposits.js +533 -0
- package/dist/pwa/routes/admin-protocol-params.js +16 -0
- package/dist/pwa/routes/admin-reports.js +31 -5
- package/dist/pwa/routes/agent-governance.js +1 -1
- package/dist/pwa/routes/agent-grants.js +253 -32
- package/dist/pwa/routes/analytics.js +2 -0
- package/dist/pwa/routes/arbitrator.js +67 -14
- package/dist/pwa/routes/bond-seller.js +162 -0
- package/dist/pwa/routes/buyer-feeds.js +2 -1
- package/dist/pwa/routes/chat.js +6 -1
- package/dist/pwa/routes/dashboards.js +2 -1
- package/dist/pwa/routes/direct-pay-availability.js +196 -0
- package/dist/pwa/routes/direct-pay-cancel-refund.js +111 -0
- package/dist/pwa/routes/direct-pay-disclosure-acks.js +79 -0
- package/dist/pwa/routes/direct-pay-pending-accept.js +222 -0
- package/dist/pwa/routes/direct-pay-returns.js +74 -0
- package/dist/pwa/routes/direct-pay-timeouts.js +248 -0
- package/dist/pwa/routes/direct-receive-accounts.js +155 -0
- package/dist/pwa/routes/direct-receive-payment-instructions.js +45 -0
- package/dist/pwa/routes/disputes-read.js +20 -6
- package/dist/pwa/routes/disputes-write.js +91 -33
- package/dist/pwa/routes/external-anchors.js +4 -2
- package/dist/pwa/routes/fee-prepay-requests.js +66 -0
- package/dist/pwa/routes/fx.js +12 -0
- package/dist/pwa/routes/governance-onboarding.js +46 -8
- package/dist/pwa/routes/leaderboard.js +47 -9
- package/dist/pwa/routes/listings.js +2 -2
- package/dist/pwa/routes/logistics.js +6 -2
- package/dist/pwa/routes/manifests.js +38 -0
- package/dist/pwa/routes/me-data.js +5 -2
- package/dist/pwa/routes/mutual-cancel.js +62 -0
- package/dist/pwa/routes/orders-action.js +297 -16
- package/dist/pwa/routes/orders-create.js +21 -6
- package/dist/pwa/routes/orders-read.js +106 -8
- package/dist/pwa/routes/p2p-products.js +2 -2
- package/dist/pwa/routes/platform-receive-accounts.js +111 -0
- package/dist/pwa/routes/products-create.js +15 -4
- package/dist/pwa/routes/products-links.js +34 -0
- package/dist/pwa/routes/products-list.js +2 -1
- package/dist/pwa/routes/products-update.js +37 -3
- package/dist/pwa/routes/profile-identity.js +4 -2
- package/dist/pwa/routes/promoter.js +3 -0
- package/dist/pwa/routes/referral.js +4 -0
- package/dist/pwa/routes/returns.js +60 -4
- package/dist/pwa/routes/rewards-apply.js +10 -5
- package/dist/pwa/routes/rewards-clearing-mature.js +96 -0
- package/dist/pwa/routes/rewards-escrow-expire.js +6 -2
- package/dist/pwa/routes/seller-directpay-report.js +110 -0
- package/dist/pwa/routes/seller-quota.js +5 -1
- package/dist/pwa/routes/shipping-templates.js +219 -0
- package/dist/pwa/routes/shops.js +2 -1
- package/dist/pwa/routes/snf.js +4 -1
- package/dist/pwa/routes/url-claim.js +2 -2
- package/dist/pwa/routes/users-public.js +8 -0
- package/dist/pwa/routes/wallet-read.js +3 -0
- package/dist/pwa/routes/webauthn.js +3 -3
- package/dist/pwa/server.js +112 -132
- package/dist/runtime/agent-grant-scopes.js +69 -1
- package/dist/runtime/webaz-schema-helpers.js +161 -3
- package/dist/sale-regions.js +116 -0
- package/dist/shipping-templates.js +119 -0
- package/dist/store-verification.js +77 -0
- package/dist/trade-tax.js +99 -0
- package/dist/trade-terms.js +76 -0
- package/dist/version.js +1 -1
- package/package.json +128 -2
package/dist/pwa/server.js
CHANGED
|
@@ -22,12 +22,15 @@ import crypto from 'crypto';
|
|
|
22
22
|
import { initDatabase, generateId } from '../layer0-foundation/L0-1-database/schema.js';
|
|
23
23
|
import { setSeamDb } from '../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
24
24
|
import { initSystemUser, transition, getOrderStatus, checkTimeouts, settleFault } from '../layer0-foundation/L0-2-state-machine/engine.js';
|
|
25
|
+
import { genuineSalePredicate } from '../layer0-foundation/L0-2-state-machine/genuine-sale.js';
|
|
26
|
+
import { settleDirectPayFeeAtCompletion } from '../direct-pay-fee-ar.js'; // RFC-018 PR4 genuine-sale SSOT + Direct Pay Rail 1 平台费链下应收(完成时释放遗留模拟 stake + accrue)
|
|
25
27
|
import { endpointToAction, endpointToReadAction } from './endpoint-actions.js';
|
|
26
28
|
import { AGENT_RATE_PER_MIN_DEFAULTS, CROSS_USER_READ_DAILY_CAP, MASS_ACTION_TYPES, MASS_ACTION_DAILY_CAPS } from './limits.js';
|
|
27
29
|
// #420 P1-2/P1-3/P1-4 — 反滥用阈值单一真相源(governance-adjustable protocol_params)+ 纯决策函数
|
|
28
30
|
import { ANTI_ABUSE_PARAMS, readAntiAbuseThresholds, agentTrustLevel, agentSybilPenalty, agentStrikeSeverity, verifierOutlierBand } from './anti-abuse-thresholds.js';
|
|
31
|
+
import { DIRECT_PAY_CONTROL_PARAMS, DIRECT_PAY_AML_PARAMS } from '../direct-pay-controls.js'; // PR-4a 控制面 + PR-6D AML 监控 param 默认 seed(均 fail-closed / inert)
|
|
29
32
|
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';
|
|
33
|
+
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, initPendingCommissionEscrowSchema } from './server-schema.js';
|
|
31
34
|
// RFC-014 PR4 — 正常成交结算走整数 base-units + allocate + 绝对值落库。
|
|
32
35
|
import { toUnits, toDecimal, mulRate, allocate } from '../money.js';
|
|
33
36
|
import { applyWalletDelta, creditColumns } from '../ledger.js';
|
|
@@ -38,6 +41,14 @@ import { ensureEvidenceColumns, uploadEvidence, listEvidence as listEvidenceFile
|
|
|
38
41
|
import { cleanupOrphanNotePhotos, } from '../layer2-business/L2-notes/note-photo-storage.js';
|
|
39
42
|
import { initAnchorRegistrySchema, retireAnchorsByTarget, reclaimRetiredAnchors, retireIdleAnchors, userReferralVolume, computeTierLetter, } from '../layer2-business/L2-anchor-registry/anchor-registry.js';
|
|
40
43
|
import { initDisputeSchema, createDispute, respondToDispute, arbitrateDispute, getOrderDispute, getDisputeDetails, getOpenDisputes, checkDisputeTimeouts, initEvidenceRequestSchema, requestEvidence, getEvidenceRequests, addPartyEvidence, } from '../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
|
|
44
|
+
import { initMutualCancelSchema } from '../layer3-trust/L3-1-dispute-engine/mutual-cancel.js';
|
|
45
|
+
import { registerMutualCancelRoutes } from './routes/mutual-cancel.js';
|
|
46
|
+
import { initDirectPayCancelRefundSchema } from '../direct-pay-cancel-refund.js';
|
|
47
|
+
import { registerDirectPayCancelRefundRoutes } from './routes/direct-pay-cancel-refund.js';
|
|
48
|
+
import { registerDirectPayReturnsRoutes } from './routes/direct-pay-returns.js';
|
|
49
|
+
import { registerDirectPayPendingAcceptRoutes } from './routes/direct-pay-pending-accept.js';
|
|
50
|
+
import { registerShippingTemplateRoutes } from './routes/shipping-templates.js';
|
|
51
|
+
import { registerBondSellerRoutes } from './routes/bond-seller.js'; // 协商取消(disputed) + 直付取消退款握手(accepted):域 schema + 端点
|
|
41
52
|
import { initNotificationSchema, notifyTransition, setPushCallback, scanDeadlineReminders, } from '../layer2-business/L2-6-notifications/notification-engine.js';
|
|
42
53
|
import { initSkillMarketSchema } from '../layer4-economics/L4-4-skill-market/skill-listing-engine.js';
|
|
43
54
|
import { computeAgentPassport } from '../layer1-agent/L1-2-identity/agent-passport.js';
|
|
@@ -84,6 +95,7 @@ import { registerClaimVerifyRoutes, processClaimTaskQueue, isEligibleClaimVerifi
|
|
|
84
95
|
import { registerFollowsRoutes } from './routes/follows.js';
|
|
85
96
|
// Leaderboard (#1013 Phase 11) — 单 endpoint 8 kinds(products/creators/buyers/sellers/value/agents/arbitrators/verifiers)
|
|
86
97
|
import { registerLeaderboardRoutes } from './routes/leaderboard.js';
|
|
98
|
+
import { registerFxRoutes } from './routes/fx.js'; // FX = display-only USDC→local rates
|
|
87
99
|
// Shareables 互动 (#1013 Phase 12) — click/like/comments/bookmark 8 endpoints
|
|
88
100
|
import { registerShareablesInteractionsRoutes } from './routes/shareables-interactions.js';
|
|
89
101
|
// Shareables CRUD (#1013 Phase 13) — 11 endpoints (notes-photo + create + me + creator-stats + by-* + feed + detail + PATCH + DELETE)
|
|
@@ -150,10 +162,13 @@ import { registerExternalAnchorsRoutes } from './routes/external-anchors.js';
|
|
|
150
162
|
import { registerAnchorsRoutes } from './routes/anchors.js';
|
|
151
163
|
// 仲裁员申请 (#1013 Phase 44) — 4 user + 3 admin endpoints
|
|
152
164
|
import { registerArbitratorRoutes } from './routes/arbitrator.js';
|
|
165
|
+
import { isEligibleArbitrator as isEligibleArbitratorImpl } from './arbitrator-lifecycle.js'; // PR-B: active-whitelist-only 授权源
|
|
153
166
|
// Governance onboarding (W3.5-B 实施 #1093 阶段 1) — 2 user endpoints
|
|
154
167
|
import { registerGovernanceOnboardingRoutes } from './routes/governance-onboarding.js';
|
|
155
168
|
import { startAutoDeactivateCron, runAutoDeactivateSweep } from './routes/governance-auto-deactivate.js';
|
|
156
169
|
import { startEscrowExpireCron } from './routes/rewards-escrow-expire.js';
|
|
170
|
+
import { startDirectPayTimeoutCron } from './routes/direct-pay-timeouts.js';
|
|
171
|
+
import { startClearingMatureCron } from './routes/rewards-clearing-mature.js';
|
|
157
172
|
import { startAutoDowngradeCron } from './routes/rewards-auto-downgrade.js';
|
|
158
173
|
import { registerRewardsApplyRoutes } from './routes/rewards-apply.js';
|
|
159
174
|
// 卖家配额 + 数据中心 (#1013 Phase 45) — 4 user + 3 admin
|
|
@@ -242,8 +257,17 @@ import { registerOrdersReadRoutes } from './routes/orders-read.js';
|
|
|
242
257
|
import { registerOrdersActionRoutes } from './routes/orders-action.js';
|
|
243
258
|
// Orders 下单端点 (#1013 Phase 85) — 1 endpoint (338-line POST /api/orders)
|
|
244
259
|
import { registerOrdersCreateRoutes } from './routes/orders-create.js';
|
|
260
|
+
import { registerDirectPayDisclosureAckRoutes } from './routes/direct-pay-disclosure-acks.js'; // PR-4d: Direct Pay 风险披露 ack 端点(薄 adapter)
|
|
261
|
+
import { registerDirectReceivePaymentInstructionRoutes } from './routes/direct-receive-payment-instructions.js';
|
|
262
|
+
import { registerDirectReceiveAccountsRoutes } from './routes/direct-receive-accounts.js';
|
|
263
|
+
import { registerFeePrepayRequestRoutes } from './routes/fee-prepay-requests.js'; // PR-4f-a instruction CRUD + Phase C1 multi-account+QR + 平台服务费预充值申请(卖家侧)
|
|
264
|
+
import { registerDirectPayAvailabilityRoutes } from './routes/direct-pay-availability.js';
|
|
265
|
+
import { registerSellerDirectPayReportRoutes } from './routes/seller-directpay-report.js'; // PR-4a 可用性 + 卖家直付销售统计/对账/平台费明细(只读)
|
|
266
|
+
import { registerAdminDirectReceiveDepositsRoutes } from './routes/admin-direct-receive-deposits.js';
|
|
267
|
+
import { registerPlatformReceiveAccountsRoutes } from './routes/platform-receive-accounts.js'; // PR-4b-3 ROOT 生产保证金 + 平台多收款方式 admin 管理(fee 预充值申请流前置)
|
|
245
268
|
// Disputes 读端点 (#1013 Phase 86) — 5 endpoints (list/similar/detail/evidence-list/parties)
|
|
246
269
|
import { registerDisputesReadRoutes } from './routes/disputes-read.js';
|
|
270
|
+
import { isArbitrationReadAdmin } from './arbitration-read-admin.js'; // 争议详情只读 admin 门(与后台列表授权一致)
|
|
247
271
|
// Disputes 写端点 (#1013 Phase 87) — 5 endpoints (respond/arbitrate/add-evidence/evidence-blob/request-evidence)
|
|
248
272
|
import { registerDisputesWriteRoutes } from './routes/disputes-write.js';
|
|
249
273
|
// Products 声明 (#1013 Phase 88) — 2 endpoints (POST claim + GET claims)
|
|
@@ -384,11 +408,14 @@ const settleClaimTask = (taskId) => settleClaimTaskRaw(db, generateId, taskId);
|
|
|
384
408
|
const notifyEligibleVerifiers = (args) => notifyEligibleVerifiersRaw(db, generateId, args);
|
|
385
409
|
initSystemUser(db);
|
|
386
410
|
initDisputeSchema(db);
|
|
411
|
+
initMutualCancelSchema(db);
|
|
412
|
+
initDirectPayCancelRefundSchema(db); // + mutual_cancel_proposals / direct_pay_cancel_requests 表
|
|
387
413
|
initNotificationSchema(db);
|
|
388
414
|
initSkillSchema(db);
|
|
389
415
|
initSkillMarketSchema(db);
|
|
390
416
|
initReputationSchema(db);
|
|
391
417
|
initOrderChainSchema(db);
|
|
418
|
+
initPendingCommissionEscrowSchema(db); // RFC-018 PR1: relocated from inline (was ~8250) so all escrow schema is built in this early batch — see helper. matures_at + reversed added (schema-only).
|
|
392
419
|
initBuildFeedbackSchema(db); // RFC-004 build_feedback
|
|
393
420
|
initBuildTasksSchema(db); // RFC-006 build_tasks(协调层)
|
|
394
421
|
initBuildTaskAgentMetadataSchema(db); // PR9B — agent-ready task metadata satellite(schema only;FUTURE-TASK-BOARD-V1-DESIGN #326)
|
|
@@ -831,10 +858,10 @@ const DEFAULT_PARAMS = [
|
|
|
831
858
|
// ⚠️ Codex #111:stake-required 模式(=1)【尚未实现】—— 下单不锁 stake、settleFault 仍按 stake_backing=0 不没收,
|
|
832
859
|
// 开启会给出虚假"真没收"协议语义。故 max 锁 0(不可开启);待 Phase 3 钱路径迁移实现真锁(下单原子锁 balance→staked)再放开。
|
|
833
860
|
{ key: 'require_seller_stake', value: '0', type: 'number', description: 'RFC-008 是否要求卖家质押(0=起步免赔付/零门槛)。⚠️ stake-required(=1)未实现、暂锁 0 不可开启,见 Phase 3', category: 'fee', min: 0, max: 0 },
|
|
834
|
-
// RFC-008 stage 2:违约罚没率,【与质押率解耦】(
|
|
835
|
-
// 背书订单:penalty = fault_penalty_rate × total,先扣 staked(封顶背书)再扣自由 balance(责任自负,真可执行)。
|
|
861
|
+
// RFC-008 stage 2:违约罚没率,【与质押率解耦】(低质押=低摩擦+高罚没=强威慑)。背书订单:penalty=rate×total,先扣 staked(封顶背书)再扣自由 balance(责任自负)。
|
|
836
862
|
// 起步免赔付(stake_backing=0):仍 0 没收,绝不碰新商家自由余额。settleFault 按订单 stake_backing 判定。
|
|
837
863
|
{ key: 'fault_penalty_rate', value: '0.30', type: 'number', description: 'RFC-008 违约罚没率(与质押率解耦;背书订单 staked 不足扣自由 balance;起步免赔付订单不适用)', category: 'fee', min: 0, max: 0.50 },
|
|
864
|
+
{ key: 'trade.platform_region_blocklist', value: '[]', type: 'json', description: '跨境 S1 平台合规禁售地区(JSON 数组大写区码,如 ["KP"]):建单 PRODUCT_RESTRICTED 硬拒,商家不可放宽;默认空。admin PATCH 有 JSON+区码校验', category: 'system' },
|
|
838
865
|
// RFC-007 stage 3:客观理由拒单的【举证窗口】小时数。卖家声称客观无责拒单 → 临时判责,此窗口内可开仲裁(stage 5)举证;
|
|
839
866
|
// 到期无人仲裁 → 自动终结为违约。窗口内买家 escrow 暂不退(随终结/翻案一次性结算),0=不给窗口(直接违约)。
|
|
840
867
|
{ key: 'decline_contest_window_hours', value: '24', type: 'number', description: 'RFC-007 客观拒单举证窗口(小时);到期未仲裁则终结为违约', category: 'limit', min: 0, max: 168 },
|
|
@@ -917,6 +944,7 @@ const DEFAULT_PARAMS = [
|
|
|
917
944
|
// #420 P1-2/P1-3/P1-4:反滥用阈值(agent 信任公式 / strike 阶梯 / verifier outlier)→ 治理可调。
|
|
918
945
|
// 默认值 === 抽取前硬编码字面量(单一真相源在 anti-abuse-thresholds.ts;测试强制校验一致)。
|
|
919
946
|
...ANTI_ABUSE_PARAMS,
|
|
947
|
+
...DIRECT_PAY_CONTROL_PARAMS, ...DIRECT_PAY_AML_PARAMS,
|
|
920
948
|
];
|
|
921
949
|
for (const p of DEFAULT_PARAMS) {
|
|
922
950
|
try {
|
|
@@ -1360,11 +1388,7 @@ try {
|
|
|
1360
1388
|
}
|
|
1361
1389
|
catch { }
|
|
1362
1390
|
// 2026-05-22: 复合索引 — GET /api/shares/dashboard bought_products 5 子查询性能优化
|
|
1363
|
-
// note_count / first_note_id
|
|
1364
|
-
try {
|
|
1365
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_share_owner_order_type ON shareables(owner_id, related_order_id, type, status)");
|
|
1366
|
-
}
|
|
1367
|
-
catch { }
|
|
1391
|
+
// note_count / first_note_id 用的 idx_share_owner_order_type 已移至下方 related_order_id ALTER 之后(此处列尚不存在,先建=fresh 库永缺该索引)
|
|
1368
1392
|
// product_share_count 用:owner_id + related_product_id + status(避免回表)
|
|
1369
1393
|
try {
|
|
1370
1394
|
db.exec("CREATE INDEX IF NOT EXISTS idx_share_owner_product ON shareables(owner_id, related_product_id, status)");
|
|
@@ -1410,6 +1434,10 @@ try {
|
|
|
1410
1434
|
db.exec("CREATE INDEX IF NOT EXISTS idx_share_order ON shareables(related_order_id) WHERE related_order_id IS NOT NULL");
|
|
1411
1435
|
}
|
|
1412
1436
|
catch { }
|
|
1437
|
+
try {
|
|
1438
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_share_owner_order_type ON shareables(owner_id, related_order_id, type, status)");
|
|
1439
|
+
}
|
|
1440
|
+
catch { }
|
|
1413
1441
|
// 笔记图片 hash 索引 / Wave A 购物表(心愿单 · 商品Q&A · 优惠券)→ server-schema.ts
|
|
1414
1442
|
// 纯幂等建表/建索引 DDL,原位调用保持 boot 顺序不变
|
|
1415
1443
|
initNotePhotoIndexSchema(db);
|
|
@@ -1655,13 +1683,12 @@ catch { }
|
|
|
1655
1683
|
for (const stmt of [
|
|
1656
1684
|
'ALTER TABLE products ADD COLUMN claim_loss_count INTEGER DEFAULT 0',
|
|
1657
1685
|
'ALTER TABLE secondhand_items ADD COLUMN claim_loss_count INTEGER DEFAULT 0',
|
|
1658
|
-
'ALTER TABLE auctions ADD COLUMN claim_loss_count INTEGER DEFAULT 0',
|
|
1659
1686
|
]) {
|
|
1660
1687
|
try {
|
|
1661
1688
|
db.exec(stmt);
|
|
1662
1689
|
}
|
|
1663
1690
|
catch { }
|
|
1664
|
-
}
|
|
1691
|
+
} // auctions 的同名 ALTER 在 auctions CREATE 之后(此处表尚不存在,先跑=fresh 库永缺列 — 铁律:ALTER AFTER CREATE)
|
|
1665
1692
|
// P2P 原生商店 — 卖家本地节点存详情,WebAZ 只锚定 hash + 关键字段
|
|
1666
1693
|
for (const stmt of [
|
|
1667
1694
|
'ALTER TABLE products ADD COLUMN p2p_mode INTEGER DEFAULT 0',
|
|
@@ -2006,10 +2033,10 @@ try {
|
|
|
2006
2033
|
read INTEGER DEFAULT 0,
|
|
2007
2034
|
created_at TEXT DEFAULT (datetime('now')),
|
|
2008
2035
|
wish_id TEXT,
|
|
2009
|
-
actions TEXT
|
|
2036
|
+
actions TEXT, template_key TEXT, params TEXT
|
|
2010
2037
|
);
|
|
2011
|
-
INSERT INTO notifications_new (id, user_id, order_id, type, title, body, read, created_at, wish_id, actions)
|
|
2012
|
-
SELECT id, user_id, order_id, type, title, body, read, created_at, wish_id, actions FROM notifications;
|
|
2038
|
+
INSERT INTO notifications_new (id, user_id, order_id, type, title, body, read, created_at, wish_id, actions, template_key, params)
|
|
2039
|
+
SELECT id, user_id, order_id, type, title, body, read, created_at, wish_id, actions, template_key, params FROM notifications;
|
|
2013
2040
|
DROP TABLE notifications;
|
|
2014
2041
|
ALTER TABLE notifications_new RENAME TO notifications;
|
|
2015
2042
|
CREATE INDEX IF NOT EXISTS idx_notif_user ON notifications(user_id, read, created_at DESC);
|
|
@@ -2407,11 +2434,15 @@ try {
|
|
|
2407
2434
|
db.exec('CREATE INDEX IF NOT EXISTS idx_auctions_deadline ON auctions(status, deadline_at)');
|
|
2408
2435
|
}
|
|
2409
2436
|
catch { }
|
|
2410
|
-
// AUC P1
|
|
2437
|
+
// AUC P1:反狙击延长上限;claim_loss_count 从 Sprint 4 数组移来(那里先于本表 CREATE,fresh 库静默失败)
|
|
2411
2438
|
try {
|
|
2412
2439
|
db.exec('ALTER TABLE auctions ADD COLUMN max_extends INTEGER DEFAULT 10');
|
|
2413
2440
|
}
|
|
2414
2441
|
catch { }
|
|
2442
|
+
try {
|
|
2443
|
+
db.exec('ALTER TABLE auctions ADD COLUMN claim_loss_count INTEGER DEFAULT 0');
|
|
2444
|
+
}
|
|
2445
|
+
catch { }
|
|
2415
2446
|
try {
|
|
2416
2447
|
db.exec('ALTER TABLE auctions ADD COLUMN extends_used INTEGER DEFAULT 0');
|
|
2417
2448
|
}
|
|
@@ -2957,7 +2988,7 @@ catch { }
|
|
|
2957
2988
|
const upd = db.prepare(`UPDATE products SET
|
|
2958
2989
|
last_sold_at = (SELECT MAX(COALESCE(updated_at, created_at)) FROM orders WHERE product_id = ? AND status = 'completed'),
|
|
2959
2990
|
first_sold_at = (SELECT MIN(COALESCE(updated_at, created_at)) FROM orders WHERE product_id = ? AND status = 'completed'),
|
|
2960
|
-
completion_count = (SELECT COUNT(1) FROM orders WHERE product_id = ? AND
|
|
2991
|
+
completion_count = (SELECT COUNT(1) FROM orders WHERE product_id = ? AND ${genuineSalePredicate('orders')}),
|
|
2961
2992
|
dispute_loss_count = (
|
|
2962
2993
|
SELECT COUNT(1) FROM disputes d JOIN orders o ON o.id = d.order_id
|
|
2963
2994
|
WHERE o.product_id = ? AND d.ruling_type IN ('refund_buyer','partial_refund')
|
|
@@ -3233,8 +3264,8 @@ function computeAgentTrust(apiKey) {
|
|
|
3233
3264
|
if (!user)
|
|
3234
3265
|
return null;
|
|
3235
3266
|
const ageDays = Math.max(0, (Date.now() - new Date(user.created_at.replace(' ', 'T') + 'Z').getTime()) / 86400_000);
|
|
3236
|
-
const completedBuyer = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND
|
|
3237
|
-
const completedSeller = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE seller_id = ? AND
|
|
3267
|
+
const completedBuyer = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND ${genuineSalePredicate('orders')}`).get(user.id).n;
|
|
3268
|
+
const completedSeller = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE seller_id = ? AND ${genuineSalePredicate('orders')}`).get(user.id).n;
|
|
3238
3269
|
const disputeLoss = db.prepare(`SELECT COUNT(*) as n FROM disputes WHERE defendant_id = ? AND ruling_type IN ('refund_buyer','partial_refund')`).get(user.id).n;
|
|
3239
3270
|
// 商品分享带来的真实成交(创作者贡献)
|
|
3240
3271
|
const shareConversions = db.prepare(`
|
|
@@ -4108,15 +4139,15 @@ app.use((req, res, next) => {
|
|
|
4108
4139
|
action, cap: capCheck.cap, used: capCheck.used, level: lvlForCap,
|
|
4109
4140
|
});
|
|
4110
4141
|
}
|
|
4111
|
-
// Phase 3c
|
|
4142
|
+
// Phase 3c:风险闸 — 高风险 agent 敏感写降速/暂停。真人豁免(Tina 案二连,同 #241 原则):绑 Passkey=真人,敏感操作已有 Passkey 仪式把守;风险分含结构罚(同 IP 注册簇)+30 天半衰期,"30 秒重试"对真人=死锁
|
|
4112
4143
|
const agentRisk = riskInfo.risk;
|
|
4113
|
-
if (agentRisk >= 100) {
|
|
4144
|
+
if (agentRisk >= 100 && !riskInfo.hasPasskey) {
|
|
4114
4145
|
return void res.status(403).json({
|
|
4115
4146
|
error: 'agent 风险分已达上限,敏感操作已暂停 — 请在「我的 Agents」查看并申诉',
|
|
4116
4147
|
error_code: 'AGENT_RISK_SUSPENDED', action, risk: agentRisk,
|
|
4117
4148
|
});
|
|
4118
4149
|
}
|
|
4119
|
-
if (agentRisk >= 70) {
|
|
4150
|
+
if (agentRisk >= 70 && !riskInfo.hasPasskey) {
|
|
4120
4151
|
res.setHeader('Retry-After', '30');
|
|
4121
4152
|
return void res.status(429).json({
|
|
4122
4153
|
error: `agent 风险分偏高(${agentRisk}/100),敏感操作已降速,请 30 秒后重试`,
|
|
@@ -4156,7 +4187,8 @@ app.use((req, res, next) => {
|
|
|
4156
4187
|
}
|
|
4157
4188
|
}
|
|
4158
4189
|
}
|
|
4159
|
-
|
|
4190
|
+
if (riskInfo.hasPasskey || req.path === '/api/signaling/poll' || req.path === '/api/snf/pending')
|
|
4191
|
+
return next(); // 分档 rate limit 前的真人豁免(Tina 案修复):绑 Passkey=真人本人/监护(PWA 页面加载+轮询天然高频,agent 约束原则=真人豁免,不进速率桶不触发 rate_limit_abuse strike);高频轮询端点亦不计 agent 配额
|
|
4160
4192
|
const repRow = db.prepare(`SELECT level FROM agent_reputation WHERE api_key = ?`).get(apiKey);
|
|
4161
4193
|
const level = repRow?.level || 'new';
|
|
4162
4194
|
const cap = getAgentRateCap(level);
|
|
@@ -4460,6 +4492,14 @@ registerAgentReputationRoutes(app, {
|
|
|
4460
4492
|
});
|
|
4461
4493
|
// #1013 Phase 47: 6 公开用户主页 endpoints 已迁出到 routes/users-public.ts
|
|
4462
4494
|
registerUsersPublicRoutes(app, { db, auth, noteAuthenticityBadges });
|
|
4495
|
+
registerDirectPayDisclosureAckRoutes(app, { db, auth, generateId, consumeGateToken }); // PR-4d
|
|
4496
|
+
registerDirectReceivePaymentInstructionRoutes(app, { db, auth, generateId });
|
|
4497
|
+
registerDirectReceiveAccountsRoutes(app, { db, auth, generateId, consumeGateToken });
|
|
4498
|
+
registerFeePrepayRequestRoutes(app, { db, auth, generateId }); // PR-4f-a + Phase C1 + fee 预充值申请
|
|
4499
|
+
registerDirectPayAvailabilityRoutes(app, { db, auth, getProtocolParam, generateId });
|
|
4500
|
+
registerSellerDirectPayReportRoutes(app, { db, auth }); // PR-4a + 缓交 + 卖家直付销售统计/对账/平台费明细(只读)
|
|
4501
|
+
registerAdminDirectReceiveDepositsRoutes(app, { db, requireRootAdmin: (req, res) => requireRootAdmin(req, res), consumeGateToken, logAdminAction, getProtocolParam });
|
|
4502
|
+
registerPlatformReceiveAccountsRoutes(app, { db, requireRootAdmin: (req, res) => requireRootAdmin(req, res), generateId, consumeGateToken, logAdminAction }); // PR-4b-3 + readiness + 平台多收款方式 admin CRUD(root+Passkey)
|
|
4463
4503
|
// RFC-004 build_feedback — agent-native "use → build" 反馈管道
|
|
4464
4504
|
registerBuildFeedbackRoutes(app, {
|
|
4465
4505
|
db, auth,
|
|
@@ -5652,7 +5692,7 @@ function checkVerifierEligibility(userId) {
|
|
|
5652
5692
|
const ageDays = Math.floor((Date.now() - new Date(user.created_at).getTime()) / 86400_000);
|
|
5653
5693
|
items.push({ key: 'age', label: '账户年龄 ≥ 60 天', current: ageDays, required: 60, ok: ageDays >= 60 });
|
|
5654
5694
|
items.push({ key: 'email', label: '邮箱已验证', current: user.email_verified ? '✓' : '✗', required: '✓', ok: !!user.email_verified });
|
|
5655
|
-
const orders = db.prepare(
|
|
5695
|
+
const orders = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE (buyer_id = ? OR seller_id = ?) AND ${genuineSalePredicate('orders')}`).get(userId, userId).n;
|
|
5656
5696
|
items.push({ key: 'orders', label: '完成订单 ≥ 20 笔', current: orders, required: 20, ok: orders >= 20 });
|
|
5657
5697
|
const disputeLost = db.prepare(`
|
|
5658
5698
|
SELECT COUNT(*) as n FROM disputes
|
|
@@ -5799,7 +5839,7 @@ function checkArbitratorEligibility(userId) {
|
|
|
5799
5839
|
const ageDays = Math.floor((Date.now() - new Date(user.created_at).getTime()) / 86400_000);
|
|
5800
5840
|
items.push({ key: 'age', label: '账户年龄 ≥ 90 天', current: ageDays, required: 90, ok: ageDays >= 90 });
|
|
5801
5841
|
items.push({ key: 'email', label: '邮箱已验证', current: user.email_verified ? '✓' : '✗', required: '✓', ok: !!user.email_verified });
|
|
5802
|
-
const orders = db.prepare(
|
|
5842
|
+
const orders = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE (buyer_id = ? OR seller_id = ?) AND ${genuineSalePredicate('orders')}`).get(userId, userId).n;
|
|
5803
5843
|
items.push({ key: 'orders', label: '完成订单 ≥ 50 笔', current: orders, required: 50, ok: orders >= 50 });
|
|
5804
5844
|
const disputeLost = db.prepare(`
|
|
5805
5845
|
SELECT COUNT(*) as n FROM disputes
|
|
@@ -5827,25 +5867,20 @@ function getArbitratorState(userId) {
|
|
|
5827
5867
|
state = 'rejected';
|
|
5828
5868
|
if (wl)
|
|
5829
5869
|
state = 'approved';
|
|
5830
|
-
|
|
5870
|
+
// PR-E:真实运行时能力字段。前端必须以 can_arbitrate 为准(state='approved' 不区分 suspended/revoked)。
|
|
5871
|
+
const arbitrator_status = wl ? (wl.status ?? 'active') : 'none'; // active / suspended / revoked / none
|
|
5872
|
+
return { state, can_arbitrate: isEligibleArbitrator(userId).ok, arbitrator_status, application: app ?? null, whitelist: wl ?? null };
|
|
5831
5873
|
}
|
|
5832
|
-
//
|
|
5874
|
+
// 仲裁员身份判定 — PR-B:唯一授权源 = active arbitrator_whitelist(role='arbitrator' 旁路已移除)。域逻辑见 arbitrator-lifecycle.ts。
|
|
5833
5875
|
function isEligibleArbitrator(userId) {
|
|
5834
|
-
|
|
5835
|
-
if (!u)
|
|
5836
|
-
return { ok: false, reason: '用户不存在' };
|
|
5837
|
-
if (u.role === 'arbitrator')
|
|
5838
|
-
return { ok: true, via: 'role' };
|
|
5839
|
-
const wl = db.prepare("SELECT user_id FROM arbitrator_whitelist WHERE user_id = ?").get(userId);
|
|
5840
|
-
if (wl)
|
|
5841
|
-
return { ok: true, via: 'whitelist' };
|
|
5842
|
-
return { ok: false, reason: '非仲裁员 — 需 role=arbitrator(内部)或 arbitrator_whitelist(外部)' };
|
|
5876
|
+
return isEligibleArbitratorImpl(db, userId);
|
|
5843
5877
|
}
|
|
5844
5878
|
// #1013 Phase 44: 4 user + 3 admin arbitrator endpoints 已迁出到 routes/arbitrator.ts
|
|
5845
5879
|
registerArbitratorRoutes(app, {
|
|
5846
5880
|
db, generateId, auth,
|
|
5847
5881
|
requireArbitrationAdmin: (req, res) => requireAdminPermission(req, res, 'arbitration'),
|
|
5848
5882
|
checkArbitratorEligibility, getArbitratorState, errorRes, logAdminAction,
|
|
5883
|
+
consumeGateToken, // PR-B: grant/suspend/reinstate/revoke 的 purpose-bound 真人 Passkey
|
|
5849
5884
|
ARB_STAKE_REQUIRED, ARB_APP_REJECT_COOLDOWN_DAYS,
|
|
5850
5885
|
});
|
|
5851
5886
|
// Governance onboarding (W3.5-B #1093) — apply + quiz + cases + admin activation + resign/appeal + auto_deactivate audit
|
|
@@ -6019,7 +6054,7 @@ registerOrdersReadRoutes(app, { db, auth, getOrderStatus, getOrderChain, verifyO
|
|
|
6019
6054
|
registerOrdersActionRoutes(app, {
|
|
6020
6055
|
db, auth, isTrustedRole, generateId, transition, notifyTransition,
|
|
6021
6056
|
settleOrder, settleFault, detectFraud, createDispute, checkTimeouts, recordViolationReputation,
|
|
6022
|
-
broadcastSystemEvent,
|
|
6057
|
+
broadcastSystemEvent, consumeGateToken,
|
|
6023
6058
|
});
|
|
6024
6059
|
// #1013 Phase 85: POST /api/orders 巨型事务已迁出
|
|
6025
6060
|
registerOrdersCreateRoutes(app, {
|
|
@@ -6043,7 +6078,7 @@ registerSellerQuotaRoutes(app, {
|
|
|
6043
6078
|
registerDisputesReadRoutes(app, {
|
|
6044
6079
|
db, auth, errorRes,
|
|
6045
6080
|
getOpenDisputes, getDisputeDetails, getEvidenceRequests, listEvidenceFiles,
|
|
6046
|
-
isEligibleArbitrator,
|
|
6081
|
+
isEligibleArbitrator, isArbitrationAdmin: isArbitrationReadAdmin, // 后者=争议详情只读 admin 门,与后台列表 /admin/disputes 同款授权(单一真相源 arbitration-read-admin.ts)
|
|
6047
6082
|
});
|
|
6048
6083
|
// #1013 Phase 87: 5 disputes 写端点已迁出(包括 234 行 arbitrate)
|
|
6049
6084
|
// FUND_BASE_RATE 是 const 函数在文件下游定义;用 getter 包一层避免 TDZ
|
|
@@ -6056,8 +6091,14 @@ registerDisputesWriteRoutes(app, {
|
|
|
6056
6091
|
FUND_BASE_RATE: () => FUND_BASE_RATE(),
|
|
6057
6092
|
settleCommission, depositToFund, calculatePv,
|
|
6058
6093
|
recordDisputeReputation, issueAgentStrike, publishDisputeCase, logAdminAction, snfSend,
|
|
6059
|
-
getProtocolParam,
|
|
6094
|
+
getProtocolParam, notifyTransition,
|
|
6060
6095
|
});
|
|
6096
|
+
registerMutualCancelRoutes(app, { db, auth, generateId, errorRes });
|
|
6097
|
+
registerDirectPayCancelRefundRoutes(app, { db, auth, generateId, errorRes, consumeGateToken });
|
|
6098
|
+
registerDirectPayReturnsRoutes(app, { db, auth, generateId, errorRes, consumeGateToken });
|
|
6099
|
+
registerDirectPayPendingAcceptRoutes(app, { db, auth, errorRes, getProtocolParam });
|
|
6100
|
+
registerShippingTemplateRoutes(app, { db, auth, errorRes });
|
|
6101
|
+
registerBondSellerRoutes(app, { db, auth, generateId, errorRes, getProtocolParam }); // 协商取消(disputed)+ 直付取消退款握手(accepted):关单手术均内部 db.transaction 原子
|
|
6061
6102
|
// lightAuthGuard:轻量 Authorization 头守门(在 raw 解析之前挡掉无 auth 请求)
|
|
6062
6103
|
// 被 Phase 13 shareables(视频上传)+ Phase 87 disputes evidence-blob 共享
|
|
6063
6104
|
function lightAuthGuard(req, res, next) {
|
|
@@ -6134,8 +6175,8 @@ function awardBidAndCreateOrder(rfq, winner) {
|
|
|
6134
6175
|
db.prepare(`
|
|
6135
6176
|
INSERT INTO products (id, seller_id, title, description, price, stock, status, images,
|
|
6136
6177
|
ship_regions, handling_hours, commission_rate, category_id, stake_amount,
|
|
6137
|
-
listing_id, fulfillment_type, eta_hours, freshness_ts, cold_start_remaining)
|
|
6138
|
-
VALUES (?,?,?,?,?,?,'active','[]',?,?,?,?,0,?,?,?,datetime('now')
|
|
6178
|
+
listing_id, fulfillment_type, eta_hours, freshness_ts, cold_start_remaining, currency)
|
|
6179
|
+
VALUES (?,?,?,?,?,?,'active','[]',?,?,?,?,0,?,?,?,datetime('now'),?,'WAZ')
|
|
6139
6180
|
`).run(productId, sellerId, String(rfq.title), `[RFQ ${rfqId}] ` + (rfq.notes ? String(rfq.notes).slice(0, 200) : '协议撮合订单'), price, qty, String(rfq.region_required || '全国'), 24, 0, 'cat_default', lstId, fulfillmentType, etaHours, 30);
|
|
6140
6181
|
}
|
|
6141
6182
|
}
|
|
@@ -6589,8 +6630,8 @@ function settleAuctionInner(aucId) {
|
|
|
6589
6630
|
productId = generateId('p');
|
|
6590
6631
|
db.prepare(`
|
|
6591
6632
|
INSERT INTO products (id, seller_id, title, description, price, stock, status, images,
|
|
6592
|
-
ship_regions, handling_hours, commission_rate, category_id, stake_amount, listing_id, freshness_ts, cold_start_remaining)
|
|
6593
|
-
VALUES (?,?,?,?,?,?,'active','[]',?,?,?,?,0,?,datetime('now')
|
|
6633
|
+
ship_regions, handling_hours, commission_rate, category_id, stake_amount, listing_id, freshness_ts, cold_start_remaining, currency)
|
|
6634
|
+
VALUES (?,?,?,?,?,?,'active','[]',?,?,?,?,0,?,datetime('now'),?,'WAZ')
|
|
6594
6635
|
`).run(productId, sellerId, String(auc.title), `[AUC ${aucId}] ` + (auc.notes ? String(auc.notes).slice(0, 200) : '拍卖成交'), price, qty, '全国', 24, 0.10, 'cat_default', auc.listing_id ? String(auc.listing_id) : null, 30);
|
|
6595
6636
|
}
|
|
6596
6637
|
// 5) 截止期 — 拍卖订单走标准 17 天链(拍卖结束后再谈履约时效)
|
|
@@ -6718,7 +6759,7 @@ const TASK_DEFS = {
|
|
|
6718
6759
|
};
|
|
6719
6760
|
// 计算用户当前任务进度(不写库,纯读)
|
|
6720
6761
|
function computeTaskProgress(userId) {
|
|
6721
|
-
const completed = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND
|
|
6762
|
+
const completed = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND ${genuineSalePredicate('orders')}`).get(userId).n;
|
|
6722
6763
|
const ratingsGiven = db.prepare(`SELECT COUNT(*) as n FROM order_ratings WHERE buyer_id = ?`).get(userId).n;
|
|
6723
6764
|
const ratingsReceived = db.prepare(`SELECT COUNT(*) as n FROM order_ratings WHERE seller_id = ?`).get(userId).n;
|
|
6724
6765
|
const follows = db.prepare(`SELECT COUNT(*) as n FROM follows WHERE follower_id = ?`).get(userId).n;
|
|
@@ -6910,6 +6951,7 @@ registerShareablesInteractionsRoutes(app, {
|
|
|
6910
6951
|
parseMentions, notifyMentions,
|
|
6911
6952
|
});
|
|
6912
6953
|
registerLeaderboardRoutes(app, { db, internalAuditorId: INTERNAL_AUDITOR_ID, rateLimitOk });
|
|
6954
|
+
registerFxRoutes(app, { rateLimitOk });
|
|
6913
6955
|
// like-status / bookmark-status / bookmarked-shareables 已迁出 (#1013 Phase 12)
|
|
6914
6956
|
// ─── Manifest Registry API(原生 P2P 内容索引,零字节存储)─────
|
|
6915
6957
|
// ============================================================
|
|
@@ -7187,28 +7229,19 @@ function settleCommission(orderId, effectiveBase) {
|
|
|
7187
7229
|
{ level: 2, beneficiary: l2Uid },
|
|
7188
7230
|
{ level: 3, beneficiary: l3Uid },
|
|
7189
7231
|
];
|
|
7190
|
-
// #7 Commission source_type
|
|
7191
|
-
//
|
|
7192
|
-
function resolveSourceType(uid) {
|
|
7193
|
-
if (!uid)
|
|
7194
|
-
return 'sponsor';
|
|
7195
|
-
const attr = db.prepare(`
|
|
7196
|
-
SELECT shareable_id FROM product_share_attribution
|
|
7197
|
-
WHERE product_id = ? AND sharer_id = ? AND shareable_id IS NOT NULL
|
|
7198
|
-
ORDER BY created_at DESC LIMIT 1
|
|
7199
|
-
`).get(order.product_id, uid);
|
|
7200
|
-
if (!attr)
|
|
7201
|
-
return 'sponsor';
|
|
7202
|
-
const sh = db.prepare(`SELECT type FROM shareables WHERE id = ?`).get(attr.shareable_id);
|
|
7203
|
-
if (!sh)
|
|
7204
|
-
return 'sponsor';
|
|
7205
|
-
return sh.type === 'note' ? 'note' : 'link';
|
|
7206
|
-
}
|
|
7232
|
+
// #7 Commission source_type resolution moved to module-level commissionSourceType() — RFC-018
|
|
7233
|
+
// Option A writes commission_records at MATURATION (not here), so source_type is stamped there.
|
|
7207
7234
|
// 2026-06-04:所有兜底统一入 commission_reserve(三级公池,独立科目,只进不出)。
|
|
7208
7235
|
// commission 不再回流 global_fund(PV 资金)—— 三套科目解耦,redirected 始终 0。
|
|
7209
7236
|
let toCommissionReserve = 0; // → commission_reserve(仅作日志/返回信息用)
|
|
7210
7237
|
// 三级金额一次性 allocate(精确求和 ≡ poolU);各级再按 gate 路由到钱包/公池/escrow。
|
|
7211
7238
|
const levelAmtU = allocate(poolU, [LEVEL_RATES[1], LEVEL_RATES[2], LEVEL_RATES[3]]);
|
|
7239
|
+
// RFC-018 clearing window: opted-in commission accrues to pending_commission_escrow and matures
|
|
7240
|
+
// after the product's return window + buffer. Anchor = now (settleCommission runs at order→completed).
|
|
7241
|
+
const nowMsClearing = Date.now();
|
|
7242
|
+
const returnDaysClearing = Number(db.prepare("SELECT return_days FROM products WHERE id = ?").get(order.product_id)?.return_days ?? 7);
|
|
7243
|
+
const clearingBufferDays = Number(db.prepare("SELECT value FROM protocol_params WHERE key = 'settlement.clearing_buffer_days'").get()?.value ?? 2);
|
|
7244
|
+
const maturesAtClearing = nowMsClearing + Math.max(0, returnDaysClearing + clearingBufferDays) * 86400 * 1000;
|
|
7212
7245
|
for (const { level, beneficiary } of recipients) {
|
|
7213
7246
|
const amountU = levelAmtU[level - 1];
|
|
7214
7247
|
const amount = toDecimal(amountU);
|
|
@@ -7254,15 +7287,17 @@ function settleCommission(orderId, effectiveBase) {
|
|
|
7254
7287
|
catch (e) { /* UNIQUE 冲突 — settleCommission 重入幂等 */ }
|
|
7255
7288
|
continue;
|
|
7256
7289
|
}
|
|
7257
|
-
// ⑤
|
|
7290
|
+
// ⑤ RFC-018 clearing (Option A): opted-in commission ACCRUES to pending_commission_escrow with a
|
|
7291
|
+
// matures_at — it is NOT credited to the wallet now and writes NO commission_records yet. The
|
|
7292
|
+
// maturation cron (rewards-clearing-mature) re-validates the order is still genuinely closed, then
|
|
7293
|
+
// writes commission_records + credits the wallet. A return in-window reverses the pending row
|
|
7294
|
+
// (proportionally) — never paid, never clawed back. expires_at is a NOT-NULL filler = matures_at;
|
|
7295
|
+
// the escrow-expire cron ignores rows where matures_at IS NOT NULL (i.e. clearing rows).
|
|
7258
7296
|
try {
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
VALUES (?,?,?,?,?,?,?,?,?,?)`)
|
|
7262
|
-
.run(generateId('comm'), orderId, beneficiary, order.buyer_id, level, amount, rate, region, routeSource, srcType);
|
|
7263
|
-
applyWalletDelta(db, beneficiary, { balance: amountU, earned: amountU });
|
|
7297
|
+
db.prepare(`INSERT INTO pending_commission_escrow (recipient_user_id, order_id, amount, attribution_path, status, created_at, expires_at, matures_at) VALUES (?,?,?,?,'pending',?,?,?)`)
|
|
7298
|
+
.run(beneficiary, orderId, amount, `L${level}`, nowMsClearing, maturesAtClearing, maturesAtClearing);
|
|
7264
7299
|
}
|
|
7265
|
-
catch (e) { /* UNIQUE
|
|
7300
|
+
catch (e) { /* UNIQUE (recipient,order,path) — settleCommission re-entry idempotent */ }
|
|
7266
7301
|
}
|
|
7267
7302
|
db.prepare("UPDATE orders SET settled_commission_at = datetime('now') WHERE id = ?").run(orderId);
|
|
7268
7303
|
// redirected 恒为 0:commission 兜底全部入 commission_reserve,不再回流 global_fund(PV 资金)。
|
|
@@ -7378,6 +7413,10 @@ function settleOrder(orderId) {
|
|
|
7378
7413
|
// 内部 try/catch 用于非关键 hook(settlePinRewards / metrics 更新)失败不回滚资金主流程
|
|
7379
7414
|
db.transaction(() => {
|
|
7380
7415
|
const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(orderId);
|
|
7416
|
+
if (order && order.payment_rail === 'direct_p2p') {
|
|
7417
|
+
settleDirectPayFeeAtCompletion(db, order, generateId('dpfr'));
|
|
7418
|
+
return;
|
|
7419
|
+
} // Rail1 直付:跳过 escrow 结算;平台费=链下应收(释放遗留模拟 stake + accrue,fail-closed 同原子边界)
|
|
7381
7420
|
const total = order.total_amount;
|
|
7382
7421
|
const isSecondhand = order.source === 'secondhand';
|
|
7383
7422
|
const isInPerson = order.fulfillment_mode === 'in_person';
|
|
@@ -8704,72 +8743,10 @@ try {
|
|
|
8704
8743
|
db.exec('CREATE INDEX IF NOT EXISTS idx_rewards_apps_action ON rewards_applications(user_id, action, created_at DESC)');
|
|
8705
8744
|
}
|
|
8706
8745
|
catch { }
|
|
8707
|
-
//
|
|
8708
|
-
//
|
|
8709
|
-
//
|
|
8710
|
-
//
|
|
8711
|
-
// in settleCommission (not by NOT NULL constraint).
|
|
8712
|
-
db.exec(`
|
|
8713
|
-
CREATE TABLE IF NOT EXISTS pending_commission_escrow (
|
|
8714
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8715
|
-
recipient_user_id TEXT NOT NULL,
|
|
8716
|
-
order_id TEXT, -- NULL for pv_pair (PR-1c-b)
|
|
8717
|
-
amount REAL NOT NULL, -- WAZ amount
|
|
8718
|
-
attribution_path TEXT NOT NULL, -- 'L1' | 'L2' | 'L3' | 'pv_pair' | etc.
|
|
8719
|
-
status TEXT NOT NULL DEFAULT 'pending', -- 'pending' | 'settled' | 'expired'
|
|
8720
|
-
created_at INTEGER NOT NULL,
|
|
8721
|
-
expires_at INTEGER NOT NULL,
|
|
8722
|
-
settled_at INTEGER,
|
|
8723
|
-
expired_to_charity_at INTEGER,
|
|
8724
|
-
FOREIGN KEY (recipient_user_id) REFERENCES users(id),
|
|
8725
|
-
FOREIGN KEY (order_id) REFERENCES orders(id)
|
|
8726
|
-
)
|
|
8727
|
-
`);
|
|
8728
|
-
(function migrateEscrowOrderIdNullable() {
|
|
8729
|
-
const cols = db.prepare("PRAGMA table_info(pending_commission_escrow)").all();
|
|
8730
|
-
const orderIdCol = cols.find(c => c.name === 'order_id');
|
|
8731
|
-
if (!orderIdCol || orderIdCol.notnull === 0)
|
|
8732
|
-
return; // already nullable (or table missing entirely)
|
|
8733
|
-
console.log('[pc-escrow-migrate] order_id is NOT NULL — recreating table to allow NULL for pv_pair');
|
|
8734
|
-
db.exec('PRAGMA foreign_keys = OFF');
|
|
8735
|
-
db.transaction(() => {
|
|
8736
|
-
db.exec(`
|
|
8737
|
-
CREATE TABLE pending_commission_escrow_new (
|
|
8738
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8739
|
-
recipient_user_id TEXT NOT NULL,
|
|
8740
|
-
order_id TEXT,
|
|
8741
|
-
amount REAL NOT NULL,
|
|
8742
|
-
attribution_path TEXT NOT NULL,
|
|
8743
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
8744
|
-
created_at INTEGER NOT NULL,
|
|
8745
|
-
expires_at INTEGER NOT NULL,
|
|
8746
|
-
settled_at INTEGER,
|
|
8747
|
-
expired_to_charity_at INTEGER,
|
|
8748
|
-
FOREIGN KEY (recipient_user_id) REFERENCES users(id),
|
|
8749
|
-
FOREIGN KEY (order_id) REFERENCES orders(id)
|
|
8750
|
-
)
|
|
8751
|
-
`);
|
|
8752
|
-
db.exec('INSERT INTO pending_commission_escrow_new SELECT * FROM pending_commission_escrow');
|
|
8753
|
-
db.exec('DROP TABLE pending_commission_escrow');
|
|
8754
|
-
db.exec('ALTER TABLE pending_commission_escrow_new RENAME TO pending_commission_escrow');
|
|
8755
|
-
})();
|
|
8756
|
-
db.exec('PRAGMA foreign_keys = ON');
|
|
8757
|
-
})();
|
|
8758
|
-
try {
|
|
8759
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_escrow_recipient ON pending_commission_escrow(recipient_user_id, status, expires_at)');
|
|
8760
|
-
}
|
|
8761
|
-
catch { }
|
|
8762
|
-
try {
|
|
8763
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_escrow_expiry ON pending_commission_escrow(status, expires_at)');
|
|
8764
|
-
}
|
|
8765
|
-
catch { }
|
|
8766
|
-
// PR-1c-a: UNIQUE (recipient, order, path) defends against double-insert if settleCommission ever retries
|
|
8767
|
-
// Note: NULL order_id (PR-1c-b pv_pair) is distinct in SQLite UNIQUE — idempotency for pv_pair relies
|
|
8768
|
-
// on binary_score_records.settled_at instead (source-side dedup).
|
|
8769
|
-
try {
|
|
8770
|
-
db.exec('CREATE UNIQUE INDEX IF NOT EXISTS uniq_escrow_recipient_order_path ON pending_commission_escrow(recipient_user_id, order_id, attribution_path)');
|
|
8771
|
-
}
|
|
8772
|
-
catch { }
|
|
8746
|
+
// pending_commission_escrow schema (table + migration + indexes) lives in
|
|
8747
|
+
// initPendingCommissionEscrowSchema (early helper batch above) — RFC-018 PR1 relocated it there so
|
|
8748
|
+
// all of its DDL is built once, never half-inline (fresh-DB silent-fail铁律). The backfill below
|
|
8749
|
+
// only READS the table; it still runs after the build (early batch precedes this line).
|
|
8773
8750
|
// Codex #69 P1:pv_escrow_reserve(#1106 隔离负债账)回填历史 pending pv_pair escrow —— 按 delta 对账,不全量转。
|
|
8774
8751
|
// 该列在 global_fund 上新增(line ~2319);#1106 之后新建的 pv_pair escrow 结算时【已】pv_escrow_reserve += wazAmount,
|
|
8775
8752
|
// 但加列【之前】产生的 pending pv_pair 从没进过 reserve。升级窗口里两者混存。
|
|
@@ -8819,6 +8796,7 @@ const RFC002_PARAMS = [
|
|
|
8819
8796
|
{ key: 'rewards_opt_in.min_completed_orders', value: '1', type: 'number', description: 'RFC-002 §3.2:申请 rewards opt-in 的最小已完成订单数', category: 'rewards', min: 0, max: 100, metaRuleLocked: false },
|
|
8820
8797
|
{ key: 'rewards_opt_in.require_passkey', value: '1', type: 'number', description: 'RFC-002 §3.3:申请 / 关闭是否需 Passkey(1=必须,0=允许 password)— META-RULE LOCKED,降低需 60d meta-rule track', category: 'rewards', min: 0, max: 1, metaRuleLocked: true },
|
|
8821
8798
|
{ key: 'rewards_opt_in.escrow_days', value: '30', type: 'number', description: 'RFC-002 §3.5b:pending commission escrow 过期天数(过期后流入 charity_fund)', category: 'rewards', min: 7, max: 180, metaRuleLocked: false },
|
|
8799
|
+
{ key: 'settlement.clearing_buffer_days', value: '2', type: 'number', description: 'RFC-018:佣金清算期 = 商品 return_days + 此 buffer(成熟才入钱包;窗内退货则冲销,永不 clawback)', category: 'rewards', min: 0, max: 30, metaRuleLocked: false },
|
|
8822
8800
|
{ key: 'rewards_opt_in.consent_delay_seconds', value: '8', type: 'number', description: 'RFC-002 §3.3:server-side 8s 反诱导延迟 — META-RULE LOCKED,降低需 60d meta-rule track', category: 'rewards', min: 0, max: 60, metaRuleLocked: true },
|
|
8823
8801
|
{ key: 'rewards_opt_in.reconfirm_grace_days', value: '14', type: 'number', description: 'RFC-002 §3.10:major consent 变更后用户重新确认 grace 期(过期 auto_downgrade)', category: 'rewards', min: 3, max: 90, metaRuleLocked: false },
|
|
8824
8802
|
];
|
|
@@ -8907,6 +8885,8 @@ app.listen(PORT, () => {
|
|
|
8907
8885
|
startAutoDeactivateCron({ db, generateId, getProtocolParam });
|
|
8908
8886
|
// #1090 RFC-002 PR-1c-a: escrow expire cron (every 1h)
|
|
8909
8887
|
startEscrowExpireCron({ db, redirectToCommissionReserve });
|
|
8888
|
+
startDirectPayTimeoutCron({ db });
|
|
8889
|
+
startClearingMatureCron({ db }); // RFC-018: pay/hold matured clearing commission (logic in the cron module)
|
|
8910
8890
|
// #1090 RFC-002 PR-3 slice 2: auto_downgrade cron (every 24h)
|
|
8911
8891
|
// Triggered when a new major consent text is published; opted-in users
|
|
8912
8892
|
// who don't reconfirm within reconfirm_grace_days (14d default) get
|
|
@@ -27,6 +27,13 @@ export const SAFE_SCOPES = [
|
|
|
27
27
|
'list_product_draft',
|
|
28
28
|
'product_publish_request',
|
|
29
29
|
'draft_order',
|
|
30
|
+
// Seller-scoped read/draft surfaces (Catalog Agent role) — read the seller's own catalog + propose
|
|
31
|
+
// drafts/pricing/publish-REQUESTS. NONE of these publish, accept orders, ship, or move money.
|
|
32
|
+
'seller_profile_read',
|
|
33
|
+
'seller_products_read',
|
|
34
|
+
'seller_inventory_read',
|
|
35
|
+
'seller_product_draft',
|
|
36
|
+
'seller_pricing_suggestion',
|
|
30
37
|
];
|
|
31
38
|
/**
|
|
32
39
|
* Real actions that WILL require a live Passkey each time — but are NOT yet gated
|
|
@@ -119,10 +126,71 @@ export function grantIsActive(grant, nowIso) {
|
|
|
119
126
|
}
|
|
120
127
|
/** Short-lived bearer policy for SAFE scopes (RFC-020 bearer-first; PoP before risk/longer). */
|
|
121
128
|
export const GRANT_TTL_DEFAULT_SEC = 3600; // 1h
|
|
122
|
-
export const GRANT_TTL_MAX_SEC = 24 * 3600; // 24h cap — short-lived only
|
|
129
|
+
export const GRANT_TTL_MAX_SEC = 24 * 3600; // 24h cap — short-lived only (legacy webaz_pair bearer)
|
|
123
130
|
export function clampTtlSeconds(requested) {
|
|
124
131
|
const n = Number(requested);
|
|
125
132
|
if (!Number.isFinite(n) || n <= 0)
|
|
126
133
|
return GRANT_TTL_DEFAULT_SEC;
|
|
127
134
|
return Math.min(Math.floor(n), GRANT_TTL_MAX_SEC);
|
|
128
135
|
}
|
|
136
|
+
export const PERMISSION_BUNDLES = {
|
|
137
|
+
catalog_agent: {
|
|
138
|
+
key: 'catalog_agent',
|
|
139
|
+
label: 'Catalog Agent',
|
|
140
|
+
scopes: ['read_public', 'profile_read', 'search', 'seller_profile_read', 'seller_products_read', 'seller_inventory_read', 'seller_product_draft', 'seller_pricing_suggestion', 'product_publish_request'],
|
|
141
|
+
human_summary: '选品、商品草稿、库存字段检查、价格建议、上架请求。它不能发布商品、不能接单、不能发货、不能动用资金。',
|
|
142
|
+
human_summary_en: 'Sourcing, product drafts, inventory-field checks, pricing suggestions, and publish REQUESTS. It cannot publish products, accept orders, ship, or move funds.',
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
/** Resolve a bundle key → its safe scopes + human summary, or null if unknown. */
|
|
146
|
+
export function resolveBundle(key) {
|
|
147
|
+
return (typeof key === 'string' && Object.prototype.hasOwnProperty.call(PERMISSION_BUNDLES, key)) ? PERMISSION_BUNDLES[key] : null;
|
|
148
|
+
}
|
|
149
|
+
/** INVARIANT check (acceptance #7): a bundle must be all-safe. Returns the offending non-safe scopes. */
|
|
150
|
+
export function bundleNonSafeScopes(bundle) {
|
|
151
|
+
return bundle.scopes.filter(s => classifyScope(s) !== 'safe');
|
|
152
|
+
}
|
|
153
|
+
// Load-time assertion: no bundle may ship with a risk/never/unknown scope (fail LOUD at import, not runtime).
|
|
154
|
+
for (const b of Object.values(PERMISSION_BUNDLES)) {
|
|
155
|
+
const bad = bundleNonSafeScopes(b);
|
|
156
|
+
if (bad.length)
|
|
157
|
+
throw new Error(`Permission bundle '${b.key}' contains non-safe scope(s): ${bad.join(', ')} — bundles are safe-only (RFC-020)`);
|
|
158
|
+
}
|
|
159
|
+
export const DURATION_SECONDS = { '1h': 3600, '24h': 86400, '7d': 604800, '30d': 2592000 };
|
|
160
|
+
/**
|
|
161
|
+
* Which durations a set of scopes may be granted for, capped by the HIGHEST risk tier present:
|
|
162
|
+
* - never-delegable / unknown → [] (cannot be granted at all)
|
|
163
|
+
* - risk (high) → ['once'] (single Passkey each time; NEVER long-term)
|
|
164
|
+
* - safe (low/medium) → once…30d (long-term ok)
|
|
165
|
+
*/
|
|
166
|
+
export function allowedDurationsForScopes(scopes) {
|
|
167
|
+
const tiers = scopes.map(classifyScope);
|
|
168
|
+
if (tiers.some(t => t === 'never_delegable' || t === 'unknown'))
|
|
169
|
+
return [];
|
|
170
|
+
if (tiers.some(t => t === 'risk'))
|
|
171
|
+
return ['once'];
|
|
172
|
+
return ['once', '1h', '24h', '7d', '30d'];
|
|
173
|
+
}
|
|
174
|
+
export function durationAllowedForScopes(scopes, duration) {
|
|
175
|
+
return typeof duration === 'string' && allowedDurationsForScopes(scopes).includes(duration);
|
|
176
|
+
}
|
|
177
|
+
/** Suggested default: 7d for long-term-eligible safe scopes; else the safest available (once). */
|
|
178
|
+
export function suggestedDurationForScopes(scopes) {
|
|
179
|
+
const a = allowedDurationsForScopes(scopes);
|
|
180
|
+
return a.includes('7d') ? '7d' : (a[0] ?? 'once');
|
|
181
|
+
}
|
|
182
|
+
/** Grant lifetime in seconds for an approved (non-'once') duration. 'once' handled separately (single-use). */
|
|
183
|
+
export function durationToSeconds(duration) {
|
|
184
|
+
return duration === 'once' ? 0 : DURATION_SECONDS[duration];
|
|
185
|
+
}
|
|
186
|
+
/** Coarse risk label for the human-facing request card. */
|
|
187
|
+
export function riskLevelForScopes(scopes) {
|
|
188
|
+
const tiers = scopes.map(classifyScope);
|
|
189
|
+
if (tiers.some(t => t === 'never_delegable' || t === 'unknown'))
|
|
190
|
+
return 'blocked';
|
|
191
|
+
if (tiers.some(t => t === 'risk'))
|
|
192
|
+
return 'high';
|
|
193
|
+
if (scopes.includes('product_publish_request'))
|
|
194
|
+
return 'medium'; // request-only (still human-gated to publish), flag as medium
|
|
195
|
+
return 'low';
|
|
196
|
+
}
|