@seasonkoh/webaz 0.1.24 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
- package/dist/layer0-foundation/L0-1-database/db.js +65 -0
- package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
- package/dist/layer1-agent/L1-1-mcp-server/server.js +165 -64
- package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
- package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
- package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
- package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
- package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +173 -0
- package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
- package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
- package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +10 -2
- package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
- package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
- package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
- package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
- package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
- package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
- package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
- package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
- package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
- package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
- package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
- package/dist/pwa/acp-feed.js +13 -1
- package/dist/pwa/contract-fingerprint.js +2 -0
- package/dist/pwa/endpoint-actions.js +5 -1
- package/dist/pwa/goal-index.js +8 -8
- package/dist/pwa/human-presence.js +62 -0
- package/dist/pwa/public/app.js +575 -68
- package/dist/pwa/public/i18n.js +29 -20
- package/dist/pwa/public/index.html +1 -0
- package/dist/pwa/public/openapi.json +2 -2
- package/dist/pwa/rate-limit.js +22 -0
- package/dist/pwa/routes/account-deletion.js +15 -13
- package/dist/pwa/routes/addresses.js +10 -9
- package/dist/pwa/routes/admin-admins.js +13 -14
- package/dist/pwa/routes/admin-analytics.js +109 -69
- package/dist/pwa/routes/admin-catalog.js +13 -11
- package/dist/pwa/routes/admin-editor-picks.js +15 -10
- package/dist/pwa/routes/admin-events.js +5 -3
- package/dist/pwa/routes/admin-health.js +2 -1
- package/dist/pwa/routes/admin-moderation.js +26 -29
- package/dist/pwa/routes/admin-ops.js +22 -21
- package/dist/pwa/routes/admin-protocol-params.js +16 -19
- package/dist/pwa/routes/admin-reports.js +23 -21
- package/dist/pwa/routes/admin-tokenomics.js +26 -25
- package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
- package/dist/pwa/routes/admin-users-query.js +54 -53
- package/dist/pwa/routes/admin-verifier-flow.js +82 -41
- package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
- package/dist/pwa/routes/admin-wallet-ops.js +7 -5
- package/dist/pwa/routes/agent-buy.js +46 -22
- package/dist/pwa/routes/agent-governance.js +52 -56
- package/dist/pwa/routes/ai.js +7 -5
- package/dist/pwa/routes/analytics.js +43 -41
- package/dist/pwa/routes/anchors.js +19 -20
- package/dist/pwa/routes/announcements.js +13 -13
- package/dist/pwa/routes/arbitrator.js +97 -31
- package/dist/pwa/routes/auction.js +153 -114
- package/dist/pwa/routes/auth-login.js +6 -4
- package/dist/pwa/routes/auth-read.js +11 -9
- package/dist/pwa/routes/auth-register.js +35 -20
- package/dist/pwa/routes/auth-sessions.js +12 -11
- package/dist/pwa/routes/blocklist.js +16 -15
- package/dist/pwa/routes/build-feedback.js +10 -9
- package/dist/pwa/routes/build-reputation.js +6 -2
- package/dist/pwa/routes/build-tasks.js +45 -13
- package/dist/pwa/routes/buyer-feeds.js +27 -25
- package/dist/pwa/routes/cart.js +16 -15
- package/dist/pwa/routes/charity.js +212 -150
- package/dist/pwa/routes/chat.js +42 -43
- package/dist/pwa/routes/checkin-tasks.js +10 -9
- package/dist/pwa/routes/checkout-helpers.js +12 -10
- package/dist/pwa/routes/claim-initiators.js +34 -14
- package/dist/pwa/routes/claim-verify.js +86 -53
- package/dist/pwa/routes/claim-voting.js +43 -18
- package/dist/pwa/routes/contribution-identity.js +147 -0
- package/dist/pwa/routes/contribution-score.js +19 -0
- package/dist/pwa/routes/coupons.js +19 -16
- package/dist/pwa/routes/dashboards.js +18 -16
- package/dist/pwa/routes/dispute-cases.js +25 -24
- package/dist/pwa/routes/disputes-read.js +45 -51
- package/dist/pwa/routes/disputes-write.js +124 -61
- package/dist/pwa/routes/evidence.js +9 -9
- package/dist/pwa/routes/external-anchors.js +13 -12
- package/dist/pwa/routes/feedback.js +29 -33
- package/dist/pwa/routes/flash-sales.js +18 -16
- package/dist/pwa/routes/follows.js +25 -24
- package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
- package/dist/pwa/routes/governance-onboarding.js +70 -59
- package/dist/pwa/routes/group-buys.js +22 -22
- package/dist/pwa/routes/growth.js +33 -30
- package/dist/pwa/routes/import-product.js +12 -10
- package/dist/pwa/routes/kyc.js +9 -8
- package/dist/pwa/routes/leaderboard.js +20 -18
- package/dist/pwa/routes/listings.js +23 -22
- package/dist/pwa/routes/logistics.js +10 -8
- package/dist/pwa/routes/manifests.js +27 -27
- package/dist/pwa/routes/me-data.js +23 -21
- package/dist/pwa/routes/notifications.js +7 -6
- package/dist/pwa/routes/offers.js +30 -12
- package/dist/pwa/routes/orders-action.js +33 -17
- package/dist/pwa/routes/orders-create.js +75 -20
- package/dist/pwa/routes/orders-read.js +21 -20
- package/dist/pwa/routes/p2p-products.js +30 -18
- package/dist/pwa/routes/payments-governance.js +61 -56
- package/dist/pwa/routes/peers.js +9 -8
- package/dist/pwa/routes/pin-receipts.js +13 -13
- package/dist/pwa/routes/products-aliases.js +12 -10
- package/dist/pwa/routes/products-claims.js +36 -17
- package/dist/pwa/routes/products-create.js +53 -38
- package/dist/pwa/routes/products-crud.js +17 -16
- package/dist/pwa/routes/products-links.js +49 -26
- package/dist/pwa/routes/products-list.js +6 -4
- package/dist/pwa/routes/products-meta.js +40 -39
- package/dist/pwa/routes/products-update.js +19 -5
- package/dist/pwa/routes/profile-credentials.js +14 -16
- package/dist/pwa/routes/profile-identity.js +14 -13
- package/dist/pwa/routes/profile-location.js +7 -6
- package/dist/pwa/routes/profile-placement.js +19 -17
- package/dist/pwa/routes/profile-prefs.js +11 -11
- package/dist/pwa/routes/promoter.js +55 -49
- package/dist/pwa/routes/public-build-tasks.js +19 -0
- package/dist/pwa/routes/public-utils.js +108 -46
- package/dist/pwa/routes/push.js +16 -15
- package/dist/pwa/routes/ratings.js +30 -30
- package/dist/pwa/routes/recover-key.js +13 -12
- package/dist/pwa/routes/referral.js +37 -32
- package/dist/pwa/routes/reputation.js +3 -2
- package/dist/pwa/routes/returns.js +76 -73
- package/dist/pwa/routes/reviews.js +41 -18
- package/dist/pwa/routes/rewards-apply.js +16 -15
- package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
- package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
- package/dist/pwa/routes/rfqs.js +163 -85
- package/dist/pwa/routes/search.js +16 -14
- package/dist/pwa/routes/secondhand.js +25 -22
- package/dist/pwa/routes/seller-quota.js +24 -26
- package/dist/pwa/routes/share-redirects.js +59 -55
- package/dist/pwa/routes/shareables-interactions.js +34 -35
- package/dist/pwa/routes/shareables.js +55 -51
- package/dist/pwa/routes/shop-referral.js +57 -0
- package/dist/pwa/routes/shops.js +20 -18
- package/dist/pwa/routes/signaling.js +10 -9
- package/dist/pwa/routes/skill-market.js +16 -16
- package/dist/pwa/routes/skills.js +15 -14
- package/dist/pwa/routes/snf.js +14 -13
- package/dist/pwa/routes/tags.js +10 -9
- package/dist/pwa/routes/task-proposals.js +45 -0
- package/dist/pwa/routes/trial.js +69 -51
- package/dist/pwa/routes/trusted-kpi.js +20 -18
- package/dist/pwa/routes/url-claim.js +67 -28
- package/dist/pwa/routes/users-public.js +62 -60
- package/dist/pwa/routes/variants.js +12 -13
- package/dist/pwa/routes/verifier-user.js +61 -21
- package/dist/pwa/routes/verify-tasks.js +49 -25
- package/dist/pwa/routes/waitlist.js +16 -15
- package/dist/pwa/routes/wallet-read.js +74 -36
- package/dist/pwa/routes/wallet-write.js +12 -9
- package/dist/pwa/routes/webauthn.js +25 -26
- package/dist/pwa/routes/webhooks.js +26 -26
- package/dist/pwa/routes/welcome.js +45 -50
- package/dist/pwa/routes/wishlist-qa.js +29 -32
- package/dist/pwa/server.js +237 -81
- package/dist/version.js +1 -1
- package/package.json +47 -2
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import crypto from 'crypto';
|
|
16
16
|
import { generateId } from '../L0-1-database/schema.js';
|
|
17
|
+
import { dbOne, dbAll } from '../L0-1-database/db.js'; // RFC-016 异步 seam(纯读)
|
|
17
18
|
export function initOrderChainSchema(db) {
|
|
18
19
|
db.exec(`
|
|
19
20
|
CREATE TABLE IF NOT EXISTS order_events (
|
|
@@ -42,10 +43,11 @@ export function initOrderChainSchema(db) {
|
|
|
42
43
|
}
|
|
43
44
|
catch { }
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
+
// RFC-016 Phase 1:纯读 → 异步 seam(db 参数保留签名兼容;调用点 orders-read.ts 均 inTx=false,非状态机写路径)。
|
|
47
|
+
export async function listOrderEventsSince(_db, userId, since, limit) {
|
|
46
48
|
const lim = Math.min(200, Math.max(1, Math.floor(limit) || 50));
|
|
47
49
|
const sinceRid = since && /^\d+$/.test(since) ? Number(since) : 0;
|
|
48
|
-
const rows =
|
|
50
|
+
const rows = await dbAll(`
|
|
49
51
|
SELECT e.rowid AS rid, e.order_id, e.seq, e.event_type, e.from_status, e.to_status, e.actor_role,
|
|
50
52
|
e.event_hash, e.prev_event_hash, e.signed_at, e.created_at
|
|
51
53
|
FROM order_events e
|
|
@@ -54,7 +56,7 @@ export function listOrderEventsSince(db, userId, since, limit) {
|
|
|
54
56
|
AND e.rowid > ?
|
|
55
57
|
ORDER BY e.rowid ASC
|
|
56
58
|
LIMIT ?
|
|
57
|
-
|
|
59
|
+
`, [userId, userId, userId, sinceRid, lim]);
|
|
58
60
|
const events = rows.map(r => ({
|
|
59
61
|
cursor: String(r.rid),
|
|
60
62
|
order_id: r.order_id, seq: r.seq, event_type: r.event_type,
|
|
@@ -118,16 +120,16 @@ export function appendOrderEvent(db, args) {
|
|
|
118
120
|
return { id, seq, event_hash: eventHash };
|
|
119
121
|
}
|
|
120
122
|
// 验证整条链 — 仲裁时或任何审计场景可调
|
|
121
|
-
export function verifyOrderChain(
|
|
122
|
-
const rows =
|
|
123
|
-
FROM order_events WHERE order_id = ? ORDER BY seq ASC
|
|
123
|
+
export async function verifyOrderChain(_db, orderId) {
|
|
124
|
+
const rows = await dbAll(`SELECT seq, prev_event_hash, event_hash, payload_json, signature, actor_id
|
|
125
|
+
FROM order_events WHERE order_id = ? ORDER BY seq ASC`, [orderId]);
|
|
124
126
|
if (rows.length === 0)
|
|
125
127
|
return { ok: false, total: 0, verified: 0, reason: 'empty_chain' };
|
|
126
128
|
// 修复 ultrareview bug_007:transition() 的 appendOrderEvent 是 try-catch 软失败
|
|
127
129
|
// (legacy actor 缺 api_key 等场景),但 order_state_history 仍会 commit。
|
|
128
130
|
// 检测:chain 行数应该至少等于 history 行数(chain ≥ history,因为可能有 open genesis 事件无 history)
|
|
129
131
|
// 不等就说明有 silent drop,链不完整 — UI 不应再显示"验证通过"
|
|
130
|
-
const histCount =
|
|
132
|
+
const histCount = await dbOne(`SELECT COUNT(*) as n FROM order_state_history WHERE order_id = ?`, [orderId]);
|
|
131
133
|
if (histCount && rows.length < histCount.n) {
|
|
132
134
|
return { ok: false, total: rows.length, verified: 0, reason: 'chain_incomplete', history_count: histCount.n };
|
|
133
135
|
}
|
|
@@ -143,7 +145,7 @@ export function verifyOrderChain(db, orderId) {
|
|
|
143
145
|
return { ok: false, total: rows.length, verified: r.seq, firstBrokenSeq: r.seq, reason: 'event_hash_mismatch' };
|
|
144
146
|
}
|
|
145
147
|
// 3. signature 用 actor api_key 验
|
|
146
|
-
const actor =
|
|
148
|
+
const actor = await dbOne('SELECT api_key FROM users WHERE id = ?', [r.actor_id]);
|
|
147
149
|
if (!actor)
|
|
148
150
|
return { ok: false, total: rows.length, verified: r.seq, firstBrokenSeq: r.seq, reason: 'actor_not_found' };
|
|
149
151
|
const reSig = computeEventSignature(r.payload_json, actor.api_key);
|
|
@@ -154,10 +156,10 @@ export function verifyOrderChain(db, orderId) {
|
|
|
154
156
|
}
|
|
155
157
|
return { ok: true, total: rows.length, verified: rows.length };
|
|
156
158
|
}
|
|
157
|
-
export function getOrderChain(
|
|
158
|
-
const rows =
|
|
159
|
+
export async function getOrderChain(_db, orderId) {
|
|
160
|
+
const rows = await dbAll(`SELECT seq, event_type, from_status, to_status, actor_id, actor_role, signed_at,
|
|
159
161
|
event_hash, prev_event_hash, signature, payload_json
|
|
160
|
-
FROM order_events WHERE order_id = ? ORDER BY seq ASC
|
|
162
|
+
FROM order_events WHERE order_id = ? ORDER BY seq ASC`, [orderId]);
|
|
161
163
|
return rows.map(r => ({
|
|
162
164
|
seq: r.seq,
|
|
163
165
|
event_type: r.event_type,
|
|
@@ -269,7 +269,7 @@ export const ORDER_STATE_MEANINGS = {
|
|
|
269
269
|
fault_buyer: { zh: '买家违约(超时未付,终态)', en: 'buyer fault (payment timeout, terminal)' },
|
|
270
270
|
fault_seller: { zh: '卖家违约(超时未接/发 或 主动拒单)', en: 'seller fault (accept/ship timeout or active decline)' },
|
|
271
271
|
fault_logistics: { zh: '物流违约', en: 'logistics fault' },
|
|
272
|
-
declined_nofault: { zh: '
|
|
272
|
+
declined_nofault: { zh: '卖家无责拒单裁定(仲裁认定客观),待系统结算 → completed;全退买家+退卖家质押,零罚没', en: 'seller no-fault decline (arbitration-cleared), pending system settlement → completed; full refund + stake returned, no forfeit' },
|
|
273
273
|
resolved_for_seller: { zh: '仲裁裁卖家胜诉,资金释放(终态)', en: 'arbitration ruled for seller, funds released (terminal)' },
|
|
274
274
|
refunded_partial: { zh: '仲裁裁部分退款(终态)', en: 'arbitration partial refund (terminal)' },
|
|
275
275
|
refunded_full: { zh: '仲裁裁全额退款,订单作废(终态)', en: 'arbitration full refund, order voided (terminal)' },
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* 2. HTTP GET:<server>/api/manifest
|
|
11
11
|
* 3. webaz_info 工具返回值中的 manifest 字段
|
|
12
12
|
*/
|
|
13
|
+
import { dbOne } from '../L0-1-database/db.js'; // RFC-016 异步 seam
|
|
13
14
|
export const MANIFEST_VERSION = '0.1.0';
|
|
14
15
|
export const MANIFEST_URI = 'webaz://protocol/manifest';
|
|
15
16
|
// ─── 协议常量(与状态机保持同步)────────────────────────────
|
|
@@ -256,9 +257,9 @@ const AGENT_GUIDE = {
|
|
|
256
257
|
},
|
|
257
258
|
};
|
|
258
259
|
// ─── 生成 Manifest ────────────────────────────────────────────
|
|
259
|
-
export function generateManifest(db) {
|
|
260
|
-
// 如果传入 db
|
|
261
|
-
const stats = db ? getLiveStats(
|
|
260
|
+
export async function generateManifest(db) {
|
|
261
|
+
// 如果传入 db(=要求附加实时统计),走异步 seam 读取。db 仅作"是否含 live_stats"开关,实际连接来自 setSeamDb。
|
|
262
|
+
const stats = db ? await getLiveStats() : null;
|
|
262
263
|
return {
|
|
263
264
|
$schema: 'https://dcp-protocol.io/schema/manifest/v1',
|
|
264
265
|
$uri: MANIFEST_URI,
|
|
@@ -288,15 +289,16 @@ export function generateManifest(db) {
|
|
|
288
289
|
live_stats: stats,
|
|
289
290
|
};
|
|
290
291
|
}
|
|
291
|
-
function getLiveStats(
|
|
292
|
+
async function getLiveStats() {
|
|
292
293
|
try {
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
const
|
|
294
|
+
const n = async (sql, params = []) => ((await dbOne(sql, params))?.n ?? 0);
|
|
295
|
+
const users = await n('SELECT COUNT(*) as n FROM users WHERE role != ?', ['system']);
|
|
296
|
+
const products = await n("SELECT COUNT(*) as n FROM products WHERE status = 'active'");
|
|
297
|
+
const orders = await n('SELECT COUNT(*) as n FROM orders');
|
|
298
|
+
const completed = await n("SELECT COUNT(*) as n FROM orders WHERE status = 'completed'");
|
|
299
|
+
const disputes = await n('SELECT COUNT(*) as n FROM disputes');
|
|
300
|
+
const skills = await n("SELECT COUNT(*) as n FROM skills WHERE active = 1");
|
|
301
|
+
const totalVolume = ((await dbOne("SELECT COALESCE(SUM(total_amount),0) as v FROM orders WHERE status = 'completed'"))?.v ?? 0);
|
|
300
302
|
return { users, active_products: products, total_orders: orders, completed_orders: completed, total_disputes: disputes, active_skills: skills, total_volume_dcp: totalVolume };
|
|
301
303
|
}
|
|
302
304
|
catch {
|
|
@@ -22,6 +22,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
22
22
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, // #B.1 a — MCP 三大原语之 Prompts
|
|
23
23
|
GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
24
24
|
import { initDatabase, generateId } from '../../layer0-foundation/L0-1-database/schema.js';
|
|
25
|
+
import { setSeamDb } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam(本进程注入)
|
|
25
26
|
import { transition, getOrderStatus, initSystemUser, } from '../../layer0-foundation/L0-2-state-machine/engine.js';
|
|
26
27
|
import { initDisputeSchema, createDispute, respondToDispute, getDisputeDetails, getOrderDispute, getOpenDisputes, } from '../../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
|
|
27
28
|
import { initNotificationSchema, notifyTransition, getNotifications, getUnreadCount, markRead, } from '../../layer2-business/L2-6-notifications/notification-engine.js';
|
|
@@ -184,6 +185,7 @@ function modeBanner() {
|
|
|
184
185
|
}
|
|
185
186
|
// ─── 初始化 ──────────────────────────────────────────────────
|
|
186
187
|
const db = initDatabase();
|
|
188
|
+
setSeamDb(db); // RFC-016 Phase 1:注入异步 DB seam(本进程)—— 共享引擎迁 seam 后 MCP 进程也能用,否则 dbOne/dbAll 抛"未初始化"
|
|
187
189
|
initSystemUser(db);
|
|
188
190
|
initDisputeSchema(db);
|
|
189
191
|
initNotificationSchema(db);
|
|
@@ -323,7 +325,7 @@ No auth required, no parameters needed.
|
|
|
323
325
|
|
|
324
326
|
Roles: buyer (browse/order/confirm) | seller (list/accept/ship) | logistics (pickup/transit/deliver) | reviewer (reviews) | arbitrator (disputes/rulings).
|
|
325
327
|
|
|
326
|
-
⚠️ **MCP register limitations (anti-bot, by design)**: does NOT set placement_id/sponsor_id — referral/PV chain NOT built via MCP. To build chain: user must arrive via webaz_share_link \`?ref=<
|
|
328
|
+
⚠️ **MCP register limitations (anti-bot, by design)**: does NOT set placement_id/sponsor_id — referral/PV chain NOT built via MCP. To build chain: user must arrive via webaz_share_link \`/i/<permanent_code>\` (or \`?ref=<permanent_code>\`) URL clicked in browser (PWA flow). region defaults 'global'; valid: singapore/china/usa/malaysia/indonesia/thailand/vietnam/taiwan/hk/global.`,
|
|
327
329
|
inputSchema: {
|
|
328
330
|
type: 'object',
|
|
329
331
|
properties: {
|
|
@@ -864,7 +866,7 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
|
|
|
864
866
|
name: 'webaz_referral',
|
|
865
867
|
description: `View your referral status: 3-tier commission team + earnings + invite links + points-matching tier progress + L1 share permission gate + **rewards_status (RFC-002 §3.5 opt-in state + pending escrow)**.
|
|
866
868
|
|
|
867
|
-
⚠️ **Opt-in required (RFC-002)**: rewards default = off. \`rewards_status\` field returns 4-state {opted_in | never_activated | auto_downgraded | deactivated} + pending_escrow tally. Opted-out users still see attribution + tree
|
|
869
|
+
⚠️ **Opt-in required (RFC-002)**: rewards default = off. \`rewards_status\` field returns 4-state {opted_in | never_activated | auto_downgraded | deactivated} + pending_escrow tally. Opted-out users still see attribution + tree. Commission destination differs by state: **never_activated / auto_downgraded** → held in pending_commission_escrow, recoverable by (re-)activating within the window via PWA #me; **deactivated** (active opt-out) → future L1/L2/L3 commission goes to commission_reserve / protocol reserve, NOT escrow and NOT recoverable (re-applying only affects future commission).
|
|
868
870
|
|
|
869
871
|
⚠️ **Consent required**: generating referral links / promoting on a human user's behalf needs the user's explicit authorization. Agent **MUST get explicit consent** before generating referral links / promoting. Do NOT auto-recruit.`,
|
|
870
872
|
inputSchema: {
|
|
@@ -879,7 +881,7 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
|
|
|
879
881
|
name: 'webaz_share_link',
|
|
880
882
|
description: `Generate product share link with your referral attached. Open in any social platform (TikTok/WeChat/Telegram). Clicker registers/buys → counts toward your 3-tier commission (if verified buyer) + points-matching.
|
|
881
883
|
|
|
882
|
-
⚠️ **Opt-in required (RFC-002 §3.5)**: this is a valuation-layer action. Caller must have \`rewards_opted_in=1\` (
|
|
884
|
+
⚠️ **Opt-in required (RFC-002 §3.5)**: this is a valuation-layer (rewards) action, not a contribution gate. Caller must have \`rewards_opted_in=1\` (rewards / share-commission opt-in). Opted-out users get \`{error: 'rewards_opt_in_required', missing_requirements, next_steps}\` — direct user to PWA #me to apply.
|
|
883
885
|
|
|
884
886
|
⚠️ **Consent required**: this builds a referral chain on the user's behalf. Agent acting for a human user **MUST get explicit consent**. Do NOT auto-generate. See webaz_info.commission_model.`,
|
|
885
887
|
inputSchema: {
|
|
@@ -1426,28 +1428,44 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
|
|
|
1426
1428
|
},
|
|
1427
1429
|
{
|
|
1428
1430
|
name: 'webaz_contribute',
|
|
1429
|
-
description: `Coordinate building WebAZ itself (RFC-006) — a
|
|
1431
|
+
description: `Coordinate building WebAZ itself (RFC-006 / RFC-017) — a public task board so contributors don't collide. Check BEFORE starting work on an area. 协调"谁在做什么"防撞车.
|
|
1432
|
+
|
|
1433
|
+
Discovery + suggesting need NO api_key (anyone / any agent can browse and propose). Claiming + submitting need an api_key (a real, accountable identity).
|
|
1430
1434
|
|
|
1431
1435
|
Actions:
|
|
1432
|
-
- list_open (default): open tasks (opt. area
|
|
1433
|
-
-
|
|
1434
|
-
-
|
|
1435
|
-
-
|
|
1436
|
-
-
|
|
1436
|
+
- list_open (default): open public tasks (opt. filters: area / risk_level / auto_claimable / required_capabilities / agent_capabilities / max_duration_minutes / estimated_context_size / estimated_agent_budget — estimated_agent_budget is a resource/effort estimate, NOT a payment). Each task carries its execution boundary + the trusted canonical contribution target. NO api_key needed.
|
|
1437
|
+
- detail: one task's full execution boundary (allowed/forbidden paths, prohibited actions, acceptance criteria, verification commands, deliverables, definition_of_done) + the canonical repo to PR to + a copy-ready agent_handoff. NO api_key needed.
|
|
1438
|
+
- suggest: propose a NEW task (title + summary/reason; opt. area/expected_outcome/source_ref/github_login). It enters the maintainer inbox — it is a suggestion, NOT a contribution fact / reward / participation, and never auto-becomes a task. NO api_key needed.
|
|
1439
|
+
- claim: take an open task (api_key); provenance=human|ai_assisted|ai_authored (self-declared, not detected); auto-expires ~7d if not submitted. Returns a handoff — point a coding agent at it; the human needn't know git but stays accountable (Passkey).
|
|
1440
|
+
- submit: mark in_review with pr_ref + verification_summary (api_key). The PR's base repo MUST be the canonical WebAZ repo, and a verification_summary (what you ran/verified) is REQUIRED — both server-enforced. A human maintainer reviews next; done ≠ merge.
|
|
1441
|
+
- status: tasks you hold (api_key).
|
|
1442
|
+
- profile: your build dashboard — KPI/tier/restrictions+appeal, private self-view (api_key).
|
|
1437
1443
|
|
|
1438
|
-
Coordinates + records only — NO merge/reward; acceptance (done) = human maintainer. build_reputation is a SEPARATE pool, never gates verifier/arbitrator.
|
|
1444
|
+
Coordinates + records only — NO merge/reward; acceptance (done) = human maintainer. Contribution value is uncommitted (RFC-017 I-12). build_reputation is a SEPARATE pool, never gates verifier/arbitrator. NETWORK only (contribution is a real-network act; sandbox has nothing to coordinate with).`,
|
|
1439
1445
|
inputSchema: {
|
|
1440
1446
|
type: 'object',
|
|
1441
1447
|
properties: {
|
|
1442
|
-
action: { type: 'string', enum: ['list_open', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | claim | submit | status | profile' },
|
|
1443
|
-
api_key: { type: 'string', description:
|
|
1444
|
-
task_id: { type: 'string', description: 'claim / submit: the task id' },
|
|
1445
|
-
area: { type: 'string', description: 'list_open:
|
|
1448
|
+
action: { type: 'string', enum: ['list_open', 'detail', 'suggest', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | detail | suggest | claim | submit | status | profile' },
|
|
1449
|
+
api_key: { type: 'string', description: 'claim/submit/status/profile: your api_key (accountable identity). NOT needed for list_open/detail/suggest.' },
|
|
1450
|
+
task_id: { type: 'string', description: 'detail / claim / submit: the task id' },
|
|
1451
|
+
area: { type: 'string', description: 'list_open: area filter / suggest: suggested area (e.g. search / docs / mcp)' },
|
|
1452
|
+
risk_level: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'list_open: optional risk filter' },
|
|
1453
|
+
auto_claimable: { type: 'boolean', description: 'list_open: optional filter — only auto-claimable (true) or manual-claim (false) tasks' },
|
|
1454
|
+
required_capabilities: { type: 'string', description: 'list_open: optional filter — comma-separated; matches tasks that REQUIRE ALL of the listed capabilities (superset/AND match on the task requirement). For "tasks my agent can do", use agent_capabilities instead.' },
|
|
1455
|
+
agent_capabilities: { type: 'string', description: 'list_open: optional filter — capabilities your agent HAS (comma-separated); matches tasks whose required_capabilities are a SUBSET of these, i.e. tasks your agent can actually do' },
|
|
1456
|
+
max_duration_minutes: { type: 'number', description: 'list_open: optional filter — only tasks whose estimated max duration fits within this many minutes (your idle time)' },
|
|
1457
|
+
estimated_context_size: { type: 'string', enum: ['small', 'medium', 'large'], description: 'list_open: optional filter — task estimated context size' },
|
|
1458
|
+
estimated_agent_budget: { type: 'string', enum: ['minimal', 'small', 'moderate', 'large', 'xlarge'], description: 'list_open: optional filter — task estimated agent budget (resource/effort estimate, not a payment)' },
|
|
1446
1459
|
provenance: { type: 'string', enum: ['human', 'ai_assisted', 'ai_authored'], description: 'claim: self-declared authorship (default human)' },
|
|
1447
|
-
pr_ref: { type: 'string', description: 'submit: your PR link or number' },
|
|
1460
|
+
pr_ref: { type: 'string', description: 'submit: your PR link or number (must target the canonical repo)' },
|
|
1461
|
+
verification_summary: { type: 'string', description: 'submit (REQUIRED): summarize what you ran/verified — the task verification_commands you ran and their results' },
|
|
1448
1462
|
note: { type: 'string', description: 'submit: optional note' },
|
|
1463
|
+
title: { type: 'string', description: 'suggest: task title (≥3 chars)' },
|
|
1464
|
+
summary: { type: 'string', description: 'suggest: why it is worth doing / what it solves (the reason)' },
|
|
1465
|
+
expected_outcome: { type: 'string', description: 'suggest: optional — what should be true when done' },
|
|
1466
|
+
source_ref: { type: 'string', description: 'suggest: optional reference link (reference only; does NOT set the target repo)' },
|
|
1467
|
+
proposer_github_login: { type: 'string', description: 'suggest: optional — your GitHub login' },
|
|
1449
1468
|
},
|
|
1450
|
-
required: ['api_key'],
|
|
1451
1469
|
},
|
|
1452
1470
|
},
|
|
1453
1471
|
];
|
|
@@ -1489,19 +1507,100 @@ async function handleFeedback(args) {
|
|
|
1489
1507
|
},
|
|
1490
1508
|
});
|
|
1491
1509
|
}
|
|
1492
|
-
// RFC-006
|
|
1493
|
-
|
|
1510
|
+
// RFC-006 断点1(b)交接:从【可信】canonical 目标(API 响应里,绝不硬编码/不取自 task metadata)构造"怎么真正
|
|
1511
|
+
// 动手"。人的编码 agent 做 git/PR;Passkey 真人担责。sandbox 运行 / 本地草稿不算正式参与。
|
|
1512
|
+
function buildContributeHandoff(cct, taskId) {
|
|
1513
|
+
const c = (cct ?? {});
|
|
1514
|
+
const repoUrl = c.canonical_github_url || 'https://github.com/seasonsagents-art/webaz';
|
|
1515
|
+
const baseRepo = c.expected_pr_base_repo || c.canonical_repository_full_name || 'seasonsagents-art/webaz';
|
|
1516
|
+
const baseBranch = c.base_branch || 'main';
|
|
1517
|
+
return {
|
|
1518
|
+
canonical_repo: baseRepo,
|
|
1519
|
+
repo: repoUrl,
|
|
1520
|
+
base_branch: baseBranch,
|
|
1521
|
+
start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
|
|
1522
|
+
do_the_work: 'Point a coding agent (e.g. Claude Code) at the repo on a single-topic branch. The buyer/shopping agent is not the coding agent — hand off to one.',
|
|
1523
|
+
submit_pr: `Open a PR whose BASE repo is ${baseRepo} (${repoUrl}), base branch ${baseBranch}. If any target repo differs from this canonical repo, STOP and ask the human — never contribute to a non-canonical repository.`,
|
|
1524
|
+
pr_flow: 'Commit with DCO sign-off (git commit -s). If AI-authored, mark the PR per the meta-rule. Humans merge — no auto-merge.',
|
|
1525
|
+
then: `When the PR is open, report it back: webaz_contribute action=submit task_id=${taskId} pr_ref=#<N> verification_summary="<the verification_commands you ran + their results>". Both pr_ref and verification_summary are required.`,
|
|
1526
|
+
not_participation: 'A sandbox run or a local-only draft is NOT participation and is NOT a contribution; only a merged PR (or recognized issue/task/RFC) on the canonical repo enters the contribution record.',
|
|
1527
|
+
human_note: "You don't need to know git — your coding agent does it; you (the Passkey-bound human) stay accountable.",
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
// RFC-006/RFC-017: webaz_contribute — 协调"谁在做什么"。NETWORK only. Discovery + suggest 无需 key(打公开
|
|
1531
|
+
// 端口 #329/#331);claim/submit/status/profile 需 key(真实可问责身份,走受 #330 守卫的 member 端口)。
|
|
1532
|
+
export async function handleContribute(args) {
|
|
1494
1533
|
const action = args.action || 'list_open';
|
|
1495
1534
|
const apiKey = args.api_key;
|
|
1496
|
-
if (!apiKey)
|
|
1497
|
-
return { error: 'api_key required' };
|
|
1498
1535
|
if (toolBackend('webaz_contribute') !== 'network') {
|
|
1499
1536
|
return {
|
|
1500
1537
|
_mode: 'sandbox',
|
|
1501
|
-
error: 'SANDBOX 模式无协调对象 —— 协调要在真实项目上才有意义。请设 WEBAZ_API_KEY 切到 NETWORK
|
|
1538
|
+
error: 'SANDBOX 模式无协调对象 —— 协调要在真实项目上才有意义。请设 WEBAZ_API_KEY 切到 NETWORK 模式(或不设 key 默认 network_readonly 也可浏览/建议)。 / Coordination needs the live network; sandbox has nothing to coordinate with.',
|
|
1502
1539
|
error_code: 'CONTRIBUTE_NEEDS_NETWORK',
|
|
1503
1540
|
};
|
|
1504
1541
|
}
|
|
1542
|
+
// ── keyless discovery + suggest (public surface; same trusted canonical target as the PWA) ──
|
|
1543
|
+
if (action === 'list_open') {
|
|
1544
|
+
// public endpoint already restricts to audience=public + status=open; only pass the optional filters.
|
|
1545
|
+
const qs = new URLSearchParams();
|
|
1546
|
+
if (args.area)
|
|
1547
|
+
qs.set('area', String(args.area));
|
|
1548
|
+
if (args.risk_level)
|
|
1549
|
+
qs.set('risk_level', String(args.risk_level));
|
|
1550
|
+
if (args.auto_claimable !== undefined)
|
|
1551
|
+
qs.set('auto_claimable', String(Boolean(args.auto_claimable)));
|
|
1552
|
+
if (args.required_capabilities)
|
|
1553
|
+
qs.set('required_capabilities', String(args.required_capabilities));
|
|
1554
|
+
if (args.agent_capabilities !== undefined)
|
|
1555
|
+
qs.set('agent_capabilities', String(args.agent_capabilities)); // forward even '' so the route fail-closes (typed 400), never silently returns the full list
|
|
1556
|
+
if (args.max_duration_minutes !== undefined)
|
|
1557
|
+
qs.set('max_duration_minutes', String(args.max_duration_minutes));
|
|
1558
|
+
if (args.estimated_context_size)
|
|
1559
|
+
qs.set('estimated_context_size', String(args.estimated_context_size));
|
|
1560
|
+
if (args.estimated_agent_budget)
|
|
1561
|
+
qs.set('estimated_agent_budget', String(args.estimated_agent_budget));
|
|
1562
|
+
const q = qs.toString();
|
|
1563
|
+
const r = await apiCall('/api/public/build-tasks' + (q ? '?' + q : ''));
|
|
1564
|
+
if (!r.error)
|
|
1565
|
+
r._next = 'Pick a task, then: webaz_contribute action=detail task_id=<id> for its full execution boundary + the canonical repo to PR to; then action=claim task_id=<id> api_key=<key> to take it (claiming needs an account).';
|
|
1566
|
+
return r;
|
|
1567
|
+
}
|
|
1568
|
+
if (action === 'detail') {
|
|
1569
|
+
const tid = args.task_id;
|
|
1570
|
+
if (!tid)
|
|
1571
|
+
return { error: 'task_id required for action=detail' };
|
|
1572
|
+
const r = await apiCall('/api/public/build-tasks/' + encodeURIComponent(tid));
|
|
1573
|
+
if (!r.error && r.task)
|
|
1574
|
+
r.agent_handoff = buildContributeHandoff(r.canonical_contribution_target, tid);
|
|
1575
|
+
return r;
|
|
1576
|
+
}
|
|
1577
|
+
if (action === 'suggest') {
|
|
1578
|
+
const title = (args.title ?? '').trim();
|
|
1579
|
+
const summary = (args.summary ?? args.note ?? '').trim();
|
|
1580
|
+
if (title.length < 3)
|
|
1581
|
+
return { error: 'title required (≥3 chars) for action=suggest' };
|
|
1582
|
+
if (summary.length < 1)
|
|
1583
|
+
return { error: 'summary (the reason) required for action=suggest' };
|
|
1584
|
+
const r = await apiCall('/api/public/task-proposals', {
|
|
1585
|
+
method: 'POST',
|
|
1586
|
+
body: {
|
|
1587
|
+
title, summary,
|
|
1588
|
+
suggested_area: args.area ?? args.suggested_area,
|
|
1589
|
+
expected_outcome: args.expected_outcome,
|
|
1590
|
+
source_ref: args.source_ref,
|
|
1591
|
+
proposer_github_login: args.proposer_github_login,
|
|
1592
|
+
},
|
|
1593
|
+
});
|
|
1594
|
+
// typed errors (RATE_LIMITED / DUPLICATE_PROPOSAL / validation) are already mapped by apiCall; the
|
|
1595
|
+
// success response already carries the route-level `proposal_notice` (suggestion ≠ contribution/reward).
|
|
1596
|
+
return r;
|
|
1597
|
+
}
|
|
1598
|
+
// ── participation: a real, accountable identity is required ──
|
|
1599
|
+
if (!apiKey)
|
|
1600
|
+
return {
|
|
1601
|
+
error: `api_key required for action=${action} — set WEBAZ_API_KEY (request an invite at ${WEBAZ_API_URL}/#welcome). Discovery (list_open / detail) and suggest work WITHOUT a key.`,
|
|
1602
|
+
error_code: 'API_KEY_REQUIRED',
|
|
1603
|
+
};
|
|
1505
1604
|
if (action === 'status')
|
|
1506
1605
|
return apiCall('/api/build-tasks?mine=1', { apiKey });
|
|
1507
1606
|
if (action === 'profile')
|
|
@@ -1513,31 +1612,25 @@ async function handleContribute(args) {
|
|
|
1513
1612
|
const r = await apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/claim', {
|
|
1514
1613
|
method: 'POST', apiKey, body: { provenance: args.provenance },
|
|
1515
1614
|
});
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
if (!r.error) {
|
|
1519
|
-
r.handoff = {
|
|
1520
|
-
repo: 'https://github.com/seasonsagents-art/webaz',
|
|
1521
|
-
start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
|
|
1522
|
-
do_the_work: 'Point a coding agent (e.g. Claude Code) at the repo; work on a single-topic branch. The buyer/shopping agent is not the coding agent — hand off to one.',
|
|
1523
|
-
pr_flow: 'Commit with DCO sign-off (git commit -s). If AI-authored, add 🤖🤖🤖 to the PR title + a meta-rule trace. Humans merge — no auto-merge.',
|
|
1524
|
-
then: `When the PR is open, report it back: webaz_contribute action=submit task_id=${tid} pr_ref=#<N>.`,
|
|
1525
|
-
human_note: "You don't need to know git — your coding agent does it; you (the Passkey-bound human) stay accountable.",
|
|
1526
|
-
};
|
|
1527
|
-
}
|
|
1615
|
+
if (!r.error)
|
|
1616
|
+
r.handoff = buildContributeHandoff(r.canonical_contribution_target, tid);
|
|
1528
1617
|
return r;
|
|
1529
1618
|
}
|
|
1530
1619
|
if (action === 'submit') {
|
|
1531
1620
|
const tid = args.task_id;
|
|
1532
1621
|
if (!tid)
|
|
1533
1622
|
return { error: 'task_id required for action=submit' };
|
|
1534
|
-
|
|
1535
|
-
|
|
1623
|
+
const vs = (args.verification_summary ?? '').trim();
|
|
1624
|
+
if (vs.length < 1)
|
|
1625
|
+
return { error: 'verification_summary required for action=submit — summarize what you ran/verified (the task verification_commands and their results)', error_code: 'VERIFICATION_SUMMARY_REQUIRED' };
|
|
1626
|
+
const r = await apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/submit', {
|
|
1627
|
+
method: 'POST', apiKey, body: { pr_ref: args.pr_ref, note: args.note, verification_summary: vs },
|
|
1536
1628
|
});
|
|
1629
|
+
if (!r.error)
|
|
1630
|
+
r._next = 'A human maintainer reviews next — acceptance (done) is manual and done ≠ merge. Track it with webaz_contribute action=status.';
|
|
1631
|
+
return r;
|
|
1537
1632
|
}
|
|
1538
|
-
|
|
1539
|
-
const q = args.area ? '?status=open&area=' + encodeURIComponent(String(args.area)) : '?status=open';
|
|
1540
|
-
return apiCall('/api/build-tasks' + q, { apiKey });
|
|
1633
|
+
return { error: 'unknown action: ' + action };
|
|
1541
1634
|
}
|
|
1542
1635
|
async function handleInfo() {
|
|
1543
1636
|
const summary = getManifestSummary();
|
|
@@ -1641,10 +1734,10 @@ async function handleInfo() {
|
|
|
1641
1734
|
split: '7:2:1 — L1 70% / L2 20% / L3 10% of an order\'s commission_pool',
|
|
1642
1735
|
jurisdiction_tiers: 'Tiers are graded by the order region\'s max_levels — NOT a uniform 3 tiers everywhere. e.g. global region max_levels=1 → L1 only; singapore (etc.) max_levels=3 → up to L3. A region may also be 0 (no commission tiers; pool → community fund).',
|
|
1643
1736
|
attribution: 'EXPLICIT per-order — commission goes to the promoter attributed at purchase time, not derived from the buyer\'s sponsor chain.',
|
|
1644
|
-
how_to_attribute: 'L1: webaz_place_order(promoter_api_key) records the direct promoter. Full L2/L3 chain requires the buyer to arrive via a webaz_share_link ?ref
|
|
1737
|
+
how_to_attribute: 'L1: webaz_place_order(promoter_api_key) records the direct promoter. Full L2/L3 chain requires the buyer to arrive via a webaz_share_link /i/<permanent_code> (?ref=<permanent_code>) URL clicked in a browser (builds product_share_attribution).',
|
|
1645
1738
|
redirect_rules: 'chain_gap (no L / invalid sponsor) → charity_fund; level beyond the region cap → global_fund.',
|
|
1646
1739
|
l1_gate: 'the promoter must be a verified buyer (≥1 completed order) to receive commission, otherwise that share redirects.',
|
|
1647
|
-
opt_in: 'Participation is opt-in (RFC-002): default = off. A user applies (Passkey + ≥1 completed order); attribution is always recorded
|
|
1740
|
+
opt_in: 'Participation is opt-in (RFC-002): default = off. A user applies (Passkey + ≥1 completed order); attribution is always recorded. Commission destination is state-dependent: never_activated / auto_downgraded → held in pending_commission_escrow (30d grace), recoverable by (re-)activating within the window, else swept to commission_reserve; deactivated (active opt-out) → future commission goes directly to commission_reserve, NOT escrow and NOT recoverable. Never to charity_fund. See docs/rfcs/RFC-002-rewards-opt-in.md.',
|
|
1648
1741
|
},
|
|
1649
1742
|
// QA 轮 3 FAIL:roles 漏 reviewer。register 工具支持 5 个角色,info 必须列全。
|
|
1650
1743
|
roles: {
|
|
@@ -1957,8 +2050,8 @@ async function handleSearch(args) {
|
|
|
1957
2050
|
: '没有找到匹配的商品';
|
|
1958
2051
|
return { found: 0, message: hint, products: [], matched_by: 'strict_no_match' };
|
|
1959
2052
|
}
|
|
1960
|
-
const enriched = products.map((p) => {
|
|
1961
|
-
const boost = getSearchBoost(db, p.seller_id);
|
|
2053
|
+
const enriched = await Promise.all(products.map(async (p) => {
|
|
2054
|
+
const boost = await getSearchBoost(db, p.seller_id);
|
|
1962
2055
|
const rep_level = p.rep_level || 'new';
|
|
1963
2056
|
const rep_points = Number(p.rep_points) || 0;
|
|
1964
2057
|
const completion = Number(p.completion_count) || 0;
|
|
@@ -1983,7 +2076,7 @@ async function handleSearch(args) {
|
|
|
1983
2076
|
const firstSaleBoost = firstSold && (Date.now() - new Date(firstSold.replace(' ', 'T') + 'Z').getTime()) < 14 * 86400_000 ? 5 : 0;
|
|
1984
2077
|
const score = completion * 0.5 + rep_points * 0.1 + sharer * 2.0 + freshness + firstSaleBoost - dispute * 5.0;
|
|
1985
2078
|
return { ...p, _boost: boost, _rep_level: rep_level, _rep_points: rep_points, _score: score, _freshness: freshness, _first_sale_boost: firstSaleBoost };
|
|
1986
|
-
});
|
|
2079
|
+
}));
|
|
1987
2080
|
const sorted = sortMode === 'trending'
|
|
1988
2081
|
? enriched.sort((a, b) => b._score - a._score || b._boost - a._boost).slice(0, limit)
|
|
1989
2082
|
: enriched.slice(0, limit);
|
|
@@ -2212,7 +2305,7 @@ async function handleListProduct(args) {
|
|
|
2212
2305
|
// stake 在 place_order 那一刻按"该订单总额 × stake_rate"现锁,订单结算时退该笔,违约时扣该笔。
|
|
2213
2306
|
// 这样每个 active 订单都有独立 stake 担保,多 stock 商品也不会被空头薅。
|
|
2214
2307
|
// product.stake_amount 字段保留为"indicative rate × price"(前端展示用),不强制 lock。
|
|
2215
|
-
const stakeDiscount = getStakeDiscount(db, user.id);
|
|
2308
|
+
const stakeDiscount = await getStakeDiscount(db, user.id);
|
|
2216
2309
|
const stakeRate = Math.max(0.05, 0.15 - stakeDiscount); // 最低 5%,声誉越高折扣越大
|
|
2217
2310
|
const stakeAmount = Math.round(price * stakeRate * 100) / 100; // indicative only; actual lock per-order
|
|
2218
2311
|
const id = generateId('prd');
|
|
@@ -2694,8 +2787,8 @@ async function handleNotifications(args) {
|
|
|
2694
2787
|
return auth;
|
|
2695
2788
|
const { user } = auth;
|
|
2696
2789
|
const onlyUnread = args.unread === true;
|
|
2697
|
-
const notifs = getNotifications(db, user.id, onlyUnread, 30);
|
|
2698
|
-
const unread = getUnreadCount(db, user.id);
|
|
2790
|
+
const notifs = await getNotifications(db, user.id, onlyUnread, 30);
|
|
2791
|
+
const unread = await getUnreadCount(db, user.id);
|
|
2699
2792
|
if (args.mark_read) {
|
|
2700
2793
|
markRead(db, user.id);
|
|
2701
2794
|
}
|
|
@@ -2782,9 +2875,9 @@ async function handleDispute(args) {
|
|
|
2782
2875
|
// ── 查看争议详情 ────────────────────────────────────────────
|
|
2783
2876
|
if (action === 'view') {
|
|
2784
2877
|
let dispute = args.dispute_id
|
|
2785
|
-
? getDisputeDetails(db, args.dispute_id)
|
|
2878
|
+
? await getDisputeDetails(db, args.dispute_id)
|
|
2786
2879
|
: args.order_id
|
|
2787
|
-
? getOrderDispute(db, args.order_id)
|
|
2880
|
+
? await getOrderDispute(db, args.order_id)
|
|
2788
2881
|
: null;
|
|
2789
2882
|
if (!dispute)
|
|
2790
2883
|
return { error: '找不到争议记录,请提供 dispute_id 或 order_id' };
|
|
@@ -2824,7 +2917,7 @@ async function handleDispute(args) {
|
|
|
2824
2917
|
if (user.role !== 'arbitrator') {
|
|
2825
2918
|
return { error: '只有仲裁员可以查看所有待处理争议' };
|
|
2826
2919
|
}
|
|
2827
|
-
const disputes = getOpenDisputes(db);
|
|
2920
|
+
const disputes = await getOpenDisputes(db);
|
|
2828
2921
|
return {
|
|
2829
2922
|
open_count: disputes.length,
|
|
2830
2923
|
disputes: disputes.map(d => ({
|
|
@@ -2848,7 +2941,7 @@ async function handleDispute(args) {
|
|
|
2848
2941
|
// 如有证据描述,先创建证据记录
|
|
2849
2942
|
const evidenceIds = [];
|
|
2850
2943
|
if (args.evidence_description) {
|
|
2851
|
-
const dispute = getDisputeDetails(db, args.dispute_id);
|
|
2944
|
+
const dispute = await getDisputeDetails(db, args.dispute_id);
|
|
2852
2945
|
if (dispute) {
|
|
2853
2946
|
const eid = generateId('evt');
|
|
2854
2947
|
db.prepare(`
|
|
@@ -3006,7 +3099,7 @@ async function handleSkill(args) {
|
|
|
3006
3099
|
if (!('error' in a))
|
|
3007
3100
|
userId = a.user.id;
|
|
3008
3101
|
}
|
|
3009
|
-
const skills = listSkills(db, {
|
|
3102
|
+
const skills = await listSkills(db, {
|
|
3010
3103
|
skillType: args.skill_type,
|
|
3011
3104
|
query: args.query,
|
|
3012
3105
|
subscriberId: userId,
|
|
@@ -3064,7 +3157,7 @@ async function handleSkill(args) {
|
|
|
3064
3157
|
}
|
|
3065
3158
|
// ── 我发布的 Skill ────────────────────────────────────────
|
|
3066
3159
|
if (action === 'my_skills') {
|
|
3067
|
-
const skills = getMySkills(db, user.id);
|
|
3160
|
+
const skills = await getMySkills(db, user.id);
|
|
3068
3161
|
return {
|
|
3069
3162
|
total: skills.length,
|
|
3070
3163
|
skills: skills.map(formatSkillForAgent),
|
|
@@ -3073,7 +3166,7 @@ async function handleSkill(args) {
|
|
|
3073
3166
|
}
|
|
3074
3167
|
// ── 我订阅的 Skill ────────────────────────────────────────
|
|
3075
3168
|
if (action === 'my_subs') {
|
|
3076
|
-
const skills = getMySubscriptions(db, user.id);
|
|
3169
|
+
const skills = await getMySubscriptions(db, user.id);
|
|
3077
3170
|
return {
|
|
3078
3171
|
total: skills.length,
|
|
3079
3172
|
subscriptions: skills.map(formatSkillForAgent),
|
|
@@ -3373,10 +3466,14 @@ async function handleReferral(args) {
|
|
|
3373
3466
|
const tiers = db.prepare("SELECT tier, pv_threshold, score_per_hit FROM binary_tier_config WHERE active=1 ORDER BY tier ASC").all();
|
|
3374
3467
|
const pair = Math.min(Number(me?.total_left_pv ?? 0), Number(me?.total_right_pv ?? 0));
|
|
3375
3468
|
const nextTier = tiers.find(t => t.pv_threshold > pair);
|
|
3469
|
+
// invite / share links use permanent_code ONLY — never usr_xxx. (sandbox users have one from register.)
|
|
3470
|
+
const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
|
|
3376
3471
|
return {
|
|
3377
3472
|
user_id: userId,
|
|
3378
3473
|
name: user.name,
|
|
3379
|
-
|
|
3474
|
+
invite_code: permaCode,
|
|
3475
|
+
invite_unavailable_reason: permaCode ? null : 'permanent_code_missing — re-register or contact support',
|
|
3476
|
+
base_referral_link: permaCode ? `/i/${permaCode}` : null, // 仅推土机
|
|
3380
3477
|
region: user.region ?? 'global',
|
|
3381
3478
|
permissions: {
|
|
3382
3479
|
can_earn_l1_commission: canL1,
|
|
@@ -3391,10 +3488,10 @@ async function handleReferral(args) {
|
|
|
3391
3488
|
grand_total: byLevel[1].total + byLevel[2].total + byLevel[3].total,
|
|
3392
3489
|
},
|
|
3393
3490
|
binary: {
|
|
3394
|
-
left_invite_link:
|
|
3395
|
-
right_invite_link:
|
|
3396
|
-
platform_left: `/?placement=${
|
|
3397
|
-
platform_right: `/?placement=${
|
|
3491
|
+
left_invite_link: permaCode ? `/i/${permaCode}-L` : null,
|
|
3492
|
+
right_invite_link: permaCode ? `/i/${permaCode}-R` : null,
|
|
3493
|
+
platform_left: permaCode ? `/?placement=${permaCode}&side=left` : null, // 仅 PV 条线
|
|
3494
|
+
platform_right: permaCode ? `/?placement=${permaCode}&side=right` : null,
|
|
3398
3495
|
total_left_pv: Number(me?.total_left_pv ?? 0),
|
|
3399
3496
|
total_right_pv: Number(me?.total_right_pv ?? 0),
|
|
3400
3497
|
pair_volume: pair,
|
|
@@ -3414,7 +3511,7 @@ async function handleReferral(args) {
|
|
|
3414
3511
|
}
|
|
3415
3512
|
else if (lastAction === 'deactivate') {
|
|
3416
3513
|
state = 'deactivated';
|
|
3417
|
-
note = 'You actively deactivated rewards. Future commissions
|
|
3514
|
+
note = 'You actively deactivated rewards. Future L1/L2/L3 commissions go to commission_reserve / protocol reserve, not charity_fund and not pending escrow. Re-applying only affects future commissions.';
|
|
3418
3515
|
}
|
|
3419
3516
|
else if (lastAction === 'auto_downgrade') {
|
|
3420
3517
|
state = 'auto_downgraded';
|
|
@@ -3432,7 +3529,7 @@ async function handleReferral(args) {
|
|
|
3432
3529
|
note,
|
|
3433
3530
|
pending_escrow: { count: pending.n, total_amount: pending.total },
|
|
3434
3531
|
expired_to_charity: { count: expired.n, total_amount: expired.total },
|
|
3435
|
-
spec: 'RFC-002 §3.5 — rewards opt-in
|
|
3532
|
+
spec: 'RFC-002 §3.5 — rewards / share-commission opt-in (RFC-002)',
|
|
3436
3533
|
};
|
|
3437
3534
|
})(),
|
|
3438
3535
|
tip: canL1
|
|
@@ -3480,10 +3577,10 @@ async function handleShareLink(args) {
|
|
|
3480
3577
|
missing.push('application_not_submitted');
|
|
3481
3578
|
return {
|
|
3482
3579
|
error: 'rewards_opt_in_required',
|
|
3483
|
-
message: 'Share-link generation is a valuation-layer action — requires
|
|
3580
|
+
message: 'Share-link generation is a valuation-layer (rewards / share-link) action, NOT a contribution gate — requires rewards / share-commission opt-in (RFC-002 §3.5)',
|
|
3484
3581
|
missing_requirements: missing,
|
|
3485
3582
|
next_steps: [
|
|
3486
|
-
'Open PWA #me → tap "
|
|
3583
|
+
'Open PWA #me → tap "申请分享分润 / Enable share-commission opt-in"',
|
|
3487
3584
|
'Read the 8-second disclosure (cannot skip)',
|
|
3488
3585
|
'Submit application — pre-checks run server-side',
|
|
3489
3586
|
],
|
|
@@ -3522,7 +3619,11 @@ async function handleShareLink(args) {
|
|
|
3522
3619
|
const override = db.prepare("SELECT l1_share_override FROM users WHERE id = ?").get(userId)?.l1_share_override ?? 0;
|
|
3523
3620
|
const canL1 = override === 1 || (override === 0 && completed > 0);
|
|
3524
3621
|
const rate = Number(product.commission_rate ?? 0);
|
|
3525
|
-
|
|
3622
|
+
// share ref uses permanent_code ONLY — never usr_xxx
|
|
3623
|
+
const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
|
|
3624
|
+
if (!permaCode)
|
|
3625
|
+
return { error: 'permanent_code_missing — cannot build a share link; re-register or contact support', error_code: 'PERMANENT_CODE_MISSING' };
|
|
3626
|
+
const link = `/?ref=${permaCode}&side=${side}#order-product/${productId}`;
|
|
3526
3627
|
return {
|
|
3527
3628
|
product: { id: product.id, title: product.title, price: product.price, commission_rate: rate },
|
|
3528
3629
|
share_link: link,
|
|
@@ -4706,7 +4807,7 @@ export async function startMCPServer() {
|
|
|
4706
4807
|
if (request.params.uri !== MANIFEST_URI) {
|
|
4707
4808
|
throw new Error(`未知资源:${request.params.uri}`);
|
|
4708
4809
|
}
|
|
4709
|
-
const manifest = generateManifest(db);
|
|
4810
|
+
const manifest = await generateManifest(db);
|
|
4710
4811
|
return {
|
|
4711
4812
|
contents: [
|
|
4712
4813
|
{
|