@seasonkoh/webaz 0.1.25 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -2
- package/NOTICE +24 -3
- package/README.md +74 -328
- package/README.zh-CN.md +419 -0
- package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +21 -0
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +8 -3
- package/dist/layer1-agent/L1-1-mcp-server/auth.js +13 -1
- package/dist/layer1-agent/L1-1-mcp-server/server.js +164 -177
- package/dist/layer2-business/L2-9-contribution/admin-coordination-ingestion-engine.js +181 -0
- package/dist/layer2-business/L2-9-contribution/admin-coordination-resolver.js +114 -0
- package/dist/layer2-business/L2-9-contribution/admin-coordination-store.js +251 -0
- package/dist/layer2-business/L2-9-contribution/admin-operator-claim-workflow.js +390 -0
- package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +33 -0
- package/dist/layer2-business/L2-9-contribution/build-task-participation.js +6 -2
- package/dist/layer2-business/L2-9-contribution/build-task-quota.js +337 -0
- package/dist/layer2-business/L2-9-contribution/build-task-read.js +25 -2
- package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +58 -8
- package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +1 -1
- package/dist/layer2-business/L2-9-contribution/contribution-facts-read.js +66 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +360 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +29 -4
- package/dist/ledger.js +1 -1
- package/dist/pwa/admin-audit.js +38 -0
- package/dist/pwa/admin-bearer-auth.js +21 -0
- package/dist/pwa/anti-abuse-thresholds.js +135 -0
- package/dist/pwa/cf-origin-guard.js +33 -0
- package/dist/pwa/contract-fingerprint.js +1 -0
- package/dist/pwa/data/onboarding-cases.js +2 -2
- package/dist/pwa/data/onboarding-quiz.js +1 -1
- package/dist/pwa/economic-participation.js +2 -2
- package/dist/pwa/email-delivery.js +127 -0
- package/dist/pwa/integration-contract.js +46 -4
- package/dist/pwa/internal/pv-settlement.js +12 -0
- package/dist/pwa/internal/wallet-signer.js +26 -0
- package/dist/pwa/public/app.js +1607 -912
- package/dist/pwa/public/i18n.js +284 -68
- package/dist/pwa/public/index.html +1 -1
- package/dist/pwa/public/openapi.json +4760 -2769
- package/dist/pwa/public/whitepaper/en/index.html +153 -0
- package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
- package/dist/pwa/pv-kill-switch.js +31 -0
- package/dist/pwa/routes/admin-admins.js +48 -1
- package/dist/pwa/routes/admin-analytics.js +1 -10
- package/dist/pwa/routes/admin-atomic.js +7 -14
- package/dist/pwa/routes/admin-moderation.js +25 -1
- package/dist/pwa/routes/admin-operator-claims.js +280 -0
- package/dist/pwa/routes/admin-ops.js +13 -2
- package/dist/pwa/routes/admin-reports.js +4 -26
- package/dist/pwa/routes/admin-tokenomics.js +2 -76
- package/dist/pwa/routes/admin-users-lifecycle.js +1 -14
- package/dist/pwa/routes/admin-users-query.js +35 -2
- package/dist/pwa/routes/admin-wallet-ops.js +26 -3
- package/dist/pwa/routes/auction.js +4 -2
- package/dist/pwa/routes/auth-read.js +11 -6
- package/dist/pwa/routes/auth-register.js +84 -24
- package/dist/pwa/routes/build-task-quota.js +113 -0
- package/dist/pwa/routes/claim-verify.js +15 -11
- package/dist/pwa/routes/contribution-facts.js +18 -0
- package/dist/pwa/routes/contribution-identity.js +17 -0
- package/dist/pwa/routes/dispute-cases.js +5 -4
- package/dist/pwa/routes/growth.js +4 -4
- package/dist/pwa/routes/orders-action.js +46 -23
- package/dist/pwa/routes/orders-create.js +1 -1
- package/dist/pwa/routes/products-meta.js +19 -6
- package/dist/pwa/routes/profile-credentials.js +7 -4
- package/dist/pwa/routes/profile-placement.js +8 -9
- package/dist/pwa/routes/promoter.js +11 -44
- package/dist/pwa/routes/public-build-tasks.js +5 -1
- package/dist/pwa/routes/public-utils.js +9 -12
- package/dist/pwa/routes/ratings.js +64 -4
- package/dist/pwa/routes/recover-key.js +58 -19
- package/dist/pwa/routes/referral.js +9 -50
- package/dist/pwa/routes/rewards-apply.js +3 -2
- package/dist/pwa/routes/share-redirects.js +5 -4
- package/dist/pwa/routes/shareables-interactions.js +2 -1
- package/dist/pwa/routes/shop-referral.js +6 -5
- package/dist/pwa/routes/shops.js +5 -2
- package/dist/pwa/routes/task-proposals.js +159 -7
- package/dist/pwa/routes/trial.js +4 -2
- package/dist/pwa/routes/users-public.js +1 -14
- package/dist/pwa/routes/wallet-read.js +3 -15
- package/dist/pwa/routes/webauthn.js +1 -1
- package/dist/pwa/server.js +223 -478
- package/dist/settlement-math.js +3 -3
- package/dist/version.js +6 -4
- package/package.json +62 -8
- package/dist/index.js +0 -182
- package/dist/pwa/public/docs/ECONOMIC-MODEL.md +0 -287
- package/dist/pwa/public/docs/INTEGRATOR.md +0 -67
- package/dist/pwa/public/docs/META-RULES-FULL.md +0 -543
- package/dist/test-dispute.js +0 -153
- package/dist/test-manifest.js +0 -61
- package/dist/test-mcp-tools.js +0 -135
- package/dist/test-reputation.js +0 -116
- package/dist/test-skill-market.js +0 -101
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** 默认值 === 抽取前的硬编码字面量。修改这里 = 修改全协议默认行为。 */
|
|
2
|
+
export const DEFAULT_ANTI_ABUSE_THRESHOLDS = {
|
|
3
|
+
trustDisputePenalty: 10,
|
|
4
|
+
trustSybilFreeThreshold: 3,
|
|
5
|
+
trustSybilPenalty: 5,
|
|
6
|
+
trustCrossPenalty: 3,
|
|
7
|
+
trustRatelimitPenalty: 2,
|
|
8
|
+
trustLevelTrusted: 20,
|
|
9
|
+
trustLevelQuality: 50,
|
|
10
|
+
trustLevelLegend: 80,
|
|
11
|
+
strikeWarnWindowDays: 7,
|
|
12
|
+
strikeWarnEscalateCount: 1,
|
|
13
|
+
strikeSuspendWindowDays: 30,
|
|
14
|
+
strikeSuspendEscalateCount: 2,
|
|
15
|
+
strikeWarnExpiryHours: 24,
|
|
16
|
+
strikeSuspendExpiryDays: 7,
|
|
17
|
+
outlierWindowDays: 180,
|
|
18
|
+
outlierSuspendCount: 3,
|
|
19
|
+
outlierRevokeCount: 5,
|
|
20
|
+
outlierSuspendDays: 30,
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* protocol_params 注册定义(spread 进 server.ts 的 DEFAULT_PARAMS)。
|
|
24
|
+
* value 必须与 DEFAULT_ANTI_ABUSE_THRESHOLDS 完全一致(测试强制校验)。
|
|
25
|
+
*/
|
|
26
|
+
export const ANTI_ABUSE_PARAMS = [
|
|
27
|
+
// P1-2 agent 信任公式(公开框架 + 治理可调系数)。等级阈值 cap=1000 给治理收紧空间。
|
|
28
|
+
{ key: 'agent_trust_dispute_penalty', value: '10', type: 'number', description: 'agent 信任分:每次败诉 dispute(refund/partial)扣分(#420 P1-2)', category: 'security', min: 0, max: 100 },
|
|
29
|
+
{ key: 'agent_trust_sybil_free_threshold', value: '3', type: 'number', description: 'agent 信任分:同 IP 注册账户数 ≤ 此值不计 sybil 罚分(含本账户)(#420 P1-2)', category: 'security', min: 0, max: 100 },
|
|
30
|
+
{ key: 'agent_trust_sybil_penalty', value: '5', type: 'number', description: 'agent 信任分:超出 free 阈值后每多 1 个同 IP 账户扣分(#420 P1-2)', category: 'security', min: 0, max: 100 },
|
|
31
|
+
{ key: 'agent_trust_cross_penalty', value: '3', type: 'number', description: 'agent 信任分:每次放置同支审计(commission cross)命中扣分(#420 P1-2)', category: 'security', min: 0, max: 100 },
|
|
32
|
+
{ key: 'agent_trust_ratelimit_penalty', value: '2', type: 'number', description: 'agent 信任分:30d 内每次 429 限速命中扣分(#420 P1-2)', category: 'security', min: 0, max: 100 },
|
|
33
|
+
{ key: 'agent_trust_level_trusted', value: '20', type: 'number', description: 'agent 信任分 ≥ 此值 → trusted 级(#420 P1-2;等级 gate 速率上限)', category: 'security', min: 0, max: 1000 },
|
|
34
|
+
{ key: 'agent_trust_level_quality', value: '50', type: 'number', description: 'agent 信任分 ≥ 此值 → quality 级(#420 P1-2)', category: 'security', min: 0, max: 1000 },
|
|
35
|
+
{ key: 'agent_trust_level_legend', value: '80', type: 'number', description: 'agent 信任分 ≥ 此值 → legend 级(#420 P1-2)', category: 'security', min: 0, max: 1000 },
|
|
36
|
+
// P1-4 agent strike 升级阶梯(公开 consequence-transparency,见 negative-space.ts;数值治理可调)
|
|
37
|
+
{ key: 'agent_strike_warn_window_days', value: '7', type: 'number', description: 'agent strike:统计 warning 的回看窗口(天)(#420 P1-4)', category: 'security', min: 1, max: 365 },
|
|
38
|
+
{ key: 'agent_strike_warn_escalate_count', value: '1', type: 'number', description: 'agent strike:窗口内已有 ≥N 次 warning 时本次 warning 升级为 suspend_7d(默认 1=累计第 2 次升级)(#420 P1-4)', category: 'security', min: 1, max: 100 },
|
|
39
|
+
{ key: 'agent_strike_suspend_window_days', value: '30', type: 'number', description: 'agent strike:统计 suspend_7d 的回看窗口(天)(#420 P1-4)', category: 'security', min: 1, max: 365 },
|
|
40
|
+
{ key: 'agent_strike_suspend_escalate_count', value: '2', type: 'number', description: 'agent strike:窗口内已有 ≥N 次 suspend_7d 时升级为 permanent(默认 2=累计第 3 次升级)(#420 P1-4)', category: 'security', min: 1, max: 100 },
|
|
41
|
+
{ key: 'agent_strike_warn_expiry_hours', value: '24', type: 'number', description: 'agent strike:warning 自动过期小时数(#420 P1-4)', category: 'security', min: 1, max: 720 },
|
|
42
|
+
{ key: 'agent_strike_suspend_expiry_days', value: '7', type: 'number', description: 'agent strike:suspend_7d 自动过期天数(#420 P1-4)', category: 'security', min: 1, max: 365 },
|
|
43
|
+
// P1-3 verifier outlier 处罚阈值(少数票 was_majority=0 即时处罚;非 playbook §6.2 confirmed_wrong cron)
|
|
44
|
+
{ key: 'verifier_outlier_window_days', value: '180', type: 'number', description: 'verifier outlier:统计少数票(was_majority=0)的回看窗口(天)(#420 P1-3)', category: 'governance', min: 1, max: 730 },
|
|
45
|
+
{ key: 'verifier_outlier_suspend_count', value: '3', type: 'number', description: 'verifier outlier:窗口内 ≥N 次 → 暂停资格(#420 P1-3)', category: 'governance', min: 1, max: 100 },
|
|
46
|
+
{ key: 'verifier_outlier_revoke_count', value: '5', type: 'number', description: 'verifier outlier:窗口内 ≥N 次 → 永久撤销资格(#420 P1-3)', category: 'governance', min: 1, max: 100 },
|
|
47
|
+
{ key: 'verifier_outlier_suspend_days', value: '30', type: 'number', description: 'verifier outlier:暂停时长(天)(#420 P1-3)', category: 'governance', min: 1, max: 365 },
|
|
48
|
+
];
|
|
49
|
+
// ── 同步读取器(与生产热路径同步语义:better-sqlite3 prepared get)──
|
|
50
|
+
function num(db, key, fallback) {
|
|
51
|
+
try {
|
|
52
|
+
const r = db.prepare('SELECT value FROM protocol_params WHERE key = ?').get(key);
|
|
53
|
+
if (!r)
|
|
54
|
+
return fallback;
|
|
55
|
+
const n = Number(r.value);
|
|
56
|
+
return Number.isFinite(n) ? n : fallback;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** 用于会被插入 SQL 字符串的窗口天数:强制非负整数(injection-safe + 语义正确)。 */
|
|
63
|
+
function intNum(db, key, fallback) {
|
|
64
|
+
const n = Math.round(num(db, key, fallback));
|
|
65
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
66
|
+
}
|
|
67
|
+
/** 从 protocol_params 读取全部反滥用阈值;缺行/坏值回落默认(= 当前生产行为)。 */
|
|
68
|
+
export function readAntiAbuseThresholds(db) {
|
|
69
|
+
const d = DEFAULT_ANTI_ABUSE_THRESHOLDS;
|
|
70
|
+
return {
|
|
71
|
+
trustDisputePenalty: num(db, 'agent_trust_dispute_penalty', d.trustDisputePenalty),
|
|
72
|
+
trustSybilFreeThreshold: num(db, 'agent_trust_sybil_free_threshold', d.trustSybilFreeThreshold),
|
|
73
|
+
trustSybilPenalty: num(db, 'agent_trust_sybil_penalty', d.trustSybilPenalty),
|
|
74
|
+
trustCrossPenalty: num(db, 'agent_trust_cross_penalty', d.trustCrossPenalty),
|
|
75
|
+
trustRatelimitPenalty: num(db, 'agent_trust_ratelimit_penalty', d.trustRatelimitPenalty),
|
|
76
|
+
trustLevelTrusted: num(db, 'agent_trust_level_trusted', d.trustLevelTrusted),
|
|
77
|
+
trustLevelQuality: num(db, 'agent_trust_level_quality', d.trustLevelQuality),
|
|
78
|
+
trustLevelLegend: num(db, 'agent_trust_level_legend', d.trustLevelLegend),
|
|
79
|
+
strikeWarnWindowDays: intNum(db, 'agent_strike_warn_window_days', d.strikeWarnWindowDays),
|
|
80
|
+
strikeWarnEscalateCount: num(db, 'agent_strike_warn_escalate_count', d.strikeWarnEscalateCount),
|
|
81
|
+
strikeSuspendWindowDays: intNum(db, 'agent_strike_suspend_window_days', d.strikeSuspendWindowDays),
|
|
82
|
+
strikeSuspendEscalateCount: num(db, 'agent_strike_suspend_escalate_count', d.strikeSuspendEscalateCount),
|
|
83
|
+
strikeWarnExpiryHours: num(db, 'agent_strike_warn_expiry_hours', d.strikeWarnExpiryHours),
|
|
84
|
+
strikeSuspendExpiryDays: num(db, 'agent_strike_suspend_expiry_days', d.strikeSuspendExpiryDays),
|
|
85
|
+
outlierWindowDays: intNum(db, 'verifier_outlier_window_days', d.outlierWindowDays),
|
|
86
|
+
outlierSuspendCount: num(db, 'verifier_outlier_suspend_count', d.outlierSuspendCount),
|
|
87
|
+
outlierRevokeCount: num(db, 'verifier_outlier_revoke_count', d.outlierRevokeCount),
|
|
88
|
+
outlierSuspendDays: num(db, 'verifier_outlier_suspend_days', d.outlierSuspendDays),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// ── 纯决策函数(生产 + 测试共用;无副作用,不读 db)──
|
|
92
|
+
/** P1-2:trust 分 → 等级。镜像原 server.ts:3818-3821 的 ≥ 级联。 */
|
|
93
|
+
export function agentTrustLevel(score, t) {
|
|
94
|
+
if (score >= t.trustLevelLegend)
|
|
95
|
+
return 'legend';
|
|
96
|
+
if (score >= t.trustLevelQuality)
|
|
97
|
+
return 'quality';
|
|
98
|
+
if (score >= t.trustLevelTrusted)
|
|
99
|
+
return 'trusted';
|
|
100
|
+
return 'new';
|
|
101
|
+
}
|
|
102
|
+
/** P1-2:sybil 罚分。镜像原 `sybilSize > free ? -(sybilSize-free)*pen : 0`。返回非正数。 */
|
|
103
|
+
export function agentSybilPenalty(sybilSize, t) {
|
|
104
|
+
return sybilSize > t.trustSybilFreeThreshold ? -(sybilSize - t.trustSybilFreeThreshold) * t.trustSybilPenalty : 0;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* P1-4:strike 升级判定。镜像原 server.ts:4493-4502。
|
|
108
|
+
* priorWarnings = 窗口内已有未过期 warning 数;priorSuspends = 窗口内已有 suspend_7d 数。
|
|
109
|
+
*/
|
|
110
|
+
export function agentStrikeSeverity(initial, priorWarnings, priorSuspends, t) {
|
|
111
|
+
let severity = initial;
|
|
112
|
+
let escalated = false;
|
|
113
|
+
if (initial === 'warning' && priorWarnings >= t.strikeWarnEscalateCount) {
|
|
114
|
+
severity = 'suspend_7d';
|
|
115
|
+
escalated = true;
|
|
116
|
+
}
|
|
117
|
+
if (initial === 'suspend_7d' || severity === 'suspend_7d') {
|
|
118
|
+
if (priorSuspends >= t.strikeSuspendEscalateCount) {
|
|
119
|
+
severity = 'permanent';
|
|
120
|
+
escalated = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { severity, escalated };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* P1-3:verifier outlier 累计数 → 处罚档位(不含 existing/dup 守卫,由调用方各自施加)。
|
|
127
|
+
* 镜像原 revoke-优先、suspend-其次 的阈值比较。
|
|
128
|
+
*/
|
|
129
|
+
export function verifierOutlierBand(count, t) {
|
|
130
|
+
if (count >= t.outlierRevokeCount)
|
|
131
|
+
return 'revoke';
|
|
132
|
+
if (count >= t.outlierSuspendCount)
|
|
133
|
+
return 'suspend';
|
|
134
|
+
return 'none';
|
|
135
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
+
export const CF_ORIGIN_HEADER = 'x-cf-origin-secret';
|
|
3
|
+
function safeEqual(a, b) {
|
|
4
|
+
const ab = Buffer.from(a);
|
|
5
|
+
const bb = Buffer.from(b);
|
|
6
|
+
return ab.length === bb.length && timingSafeEqual(ab, bb);
|
|
7
|
+
}
|
|
8
|
+
export function createCfOriginGuard(env = process.env) {
|
|
9
|
+
const mode = (env.CF_ORIGIN_GUARD_MODE || 'off').toLowerCase();
|
|
10
|
+
const secret = env.CF_ORIGIN_SHARED_SECRET || '';
|
|
11
|
+
const exempt = new Set((env.CF_ORIGIN_GUARD_EXEMPT || '/api/health,/healthz')
|
|
12
|
+
.split(',').map(s => s.trim()).filter(Boolean));
|
|
13
|
+
return function cfOriginGuard(req, res, next) {
|
|
14
|
+
if (mode === 'off')
|
|
15
|
+
return next();
|
|
16
|
+
if (exempt.has(req.path))
|
|
17
|
+
return next();
|
|
18
|
+
if (!secret) {
|
|
19
|
+
if (mode === 'enforce') {
|
|
20
|
+
console.error('[cf-origin-guard] enforce set but CF_ORIGIN_SHARED_SECRET is empty — failing OPEN (no block)');
|
|
21
|
+
}
|
|
22
|
+
return next();
|
|
23
|
+
}
|
|
24
|
+
const got = req.get(CF_ORIGIN_HEADER) || '';
|
|
25
|
+
if (got && safeEqual(got, secret))
|
|
26
|
+
return next(); // arrived via Cloudflare
|
|
27
|
+
if (mode === 'observe') {
|
|
28
|
+
console.warn(`[cf-origin-guard] observe: would block direct-origin ${req.method} ${req.path} ip=${req.ip}`);
|
|
29
|
+
return next();
|
|
30
|
+
}
|
|
31
|
+
res.status(403).json({ error: 'direct origin access not allowed; use the public endpoint', error_code: 'CF_ORIGIN_ONLY' });
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -35,6 +35,7 @@ export const CONTRACT_CHANGES = [
|
|
|
35
35
|
{ contract_version: 2, date: '2026-06-06', surface: 'entity', kind: 'added', summary: '§① entity dictionary gains product + dispute entities (conservative public fields; dispute = redacted dispute_cases) + goal_index pointer. Additive — existing order entity unchanged; agents may ignore.' },
|
|
36
36
|
{ contract_version: 3, date: '2026-06-09', surface: 'entity', kind: 'changed', summary: '§① order lifecycle: corrected declined_nofault state meaning text — it is NOT terminal (transitions declined_nofault→completed on settlement). Dropped the contradictory "(terminal)" label that conflicted with the auto-derived terminal:false. Semantics/state-machine unchanged; text-only clarification for agents reading the entity dictionary.' },
|
|
37
37
|
{ contract_version: 4, date: '2026-06-09', surface: 'capability', kind: 'changed', summary: '§② capability matrix: POST /api/reviews/:type/:id/claim now requires the new "review_claim" action scope instead of being SAFE (unscoped). The review-claim path locks a 5 WAZ stake (escrow), so it belongs under default-deny accountability like other value writes. GET reviews endpoints stay open.', migration: 'A declared agent that calls review claim must add the "review_claim" scope to its api_key (or hold a Passkey, or declare "*"). Passkey-bound humans and "*" agents are unaffected; GET reviews unchanged.' },
|
|
38
|
+
{ contract_version: 5, date: '2026-06-24', surface: 'integration', kind: 'added', summary: '§④ integration-contract entry (/.well-known/webaz-integration.json) gains an agent_quickstart block: a 60-second stranger-agent cold-start with discrete, machine-parseable fields (canonical_start_url, public_readonly_entrypoints, anonymous_allowed_actions, authenticated_required_actions, safe_next_actions, proposal_flow, contribution_boundary). Additive — no existing field changed; agents may ignore. NB: this surface is NOT covered by the §②/§① fingerprint, so it is registered here by hand.' },
|
|
38
39
|
];
|
|
39
40
|
export function buildChangeFeed() {
|
|
40
41
|
return {
|
|
@@ -77,14 +77,14 @@ export const ONBOARDING_CASES = [
|
|
|
77
77
|
],
|
|
78
78
|
decision_options: [
|
|
79
79
|
{ key: 'release_seller', text_zh: 'release_seller', text_en: 'release_seller' },
|
|
80
|
-
{ key: 'refund_buyer', text_zh: 'refund_buyer + 物流 stake 优先赔买家 +
|
|
80
|
+
{ key: 'refund_buyer', text_zh: 'refund_buyer + 物流 stake 优先赔买家 + protocol_reserve_pool 兜底', text_en: 'refund_buyer + logistics stake to buyer first + protocol_reserve_pool covers shortfall' },
|
|
81
81
|
{ key: 'partial_refund', text_zh: 'partial_refund 50/50', text_en: 'partial_refund 50/50' },
|
|
82
82
|
{ key: 'liability_split', text_zh: 'liability_split(卖家一半 / 物流方一半)', text_en: 'liability_split (half seller / half logistics)' },
|
|
83
83
|
],
|
|
84
84
|
expected_verdict: 'refund_buyer',
|
|
85
85
|
key_principles: [
|
|
86
86
|
'ARBITRATION-PLAYBOOK Case 2 物流卡顿 — 卖家无过错应保护',
|
|
87
|
-
'
|
|
87
|
+
'protocol_reserve_pool 来源 = ECONOMIC §3 ④a + 失效活动罚没',
|
|
88
88
|
'资金流向:物流 stake 优先赔买家(不入 pool),pool 仅兜底差额',
|
|
89
89
|
'物流方 debt_to_protocol 累计 → > 1000 WAZ 角色暂停',
|
|
90
90
|
],
|
|
@@ -128,7 +128,7 @@ export const ONBOARDING_QUIZ = [
|
|
|
128
128
|
question_en: 'Case 2 logistics stuck: seller has shipping doc, buyer never received, logistics gone 45 days. Arbitrator should:',
|
|
129
129
|
options: [
|
|
130
130
|
{ key: 'A', text_zh: 'release_seller(卖家无过错)', text_en: 'release_seller (seller has no fault)' },
|
|
131
|
-
{ key: 'B', text_zh: 'refund_buyer + 物流 stake **优先赔买家**(不足部分由
|
|
131
|
+
{ key: 'B', text_zh: 'refund_buyer + 物流 stake **优先赔买家**(不足部分由 protocol_reserve_pool 兜底差额)', text_en: 'refund_buyer + logistics stake **goes to buyer first** (shortfall covered by protocol_reserve_pool)' },
|
|
132
132
|
{ key: 'C', text_zh: 'partial_refund 50/50', text_en: 'partial_refund 50/50' },
|
|
133
133
|
{ key: 'D', text_zh: 'liability_split,卖家一半物流方一半', text_en: 'liability_split, half seller half logistics' },
|
|
134
134
|
],
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* - 守恒是硬不变量:所有罚没【再分配,绝不增发】(settleFault)。
|
|
10
10
|
* - 诚实 status:已上线角色标 live;通用第三方承保方(无真实需求)标 scaffolded → 自有 RFC + enters-core 门控,不过早造接口。
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* 公平三原则锚:公开透明 / 谁责任谁承担 / 无责方零成本。
|
|
13
13
|
*/
|
|
14
14
|
import { SOFTWARE_VERSION, CONTRACT_VERSION } from '../version.js';
|
|
15
|
-
const GH = 'https://github.com/
|
|
15
|
+
const GH = 'https://github.com/webaz-protocol/webaz/blob/main';
|
|
16
16
|
const BASE = 'https://webaz.xyz';
|
|
17
17
|
export function buildEconomicParticipation(getParam) {
|
|
18
18
|
const num = (k, f) => getParam(k, f);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const DEFAULT_FROM = 'WebAZ <noreply@webaz.xyz>';
|
|
2
|
+
const DEFAULT_BASE_URL = 'https://webaz.xyz';
|
|
3
|
+
export function emailDeliveryNotConfigured() {
|
|
4
|
+
return {
|
|
5
|
+
ok: false,
|
|
6
|
+
status: 503,
|
|
7
|
+
error_code: 'EMAIL_DELIVERY_NOT_CONFIGURED',
|
|
8
|
+
error: '邮箱发送服务未配置,请稍后再试',
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function emailDeliveryFailed() {
|
|
12
|
+
return {
|
|
13
|
+
ok: false,
|
|
14
|
+
status: 502,
|
|
15
|
+
error_code: 'EMAIL_DELIVERY_FAILED',
|
|
16
|
+
error: '验证码邮件发送失败,请稍后再试',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function isProtectedEmailEnv(env) {
|
|
20
|
+
return env.NODE_ENV === 'production'
|
|
21
|
+
|| !!env.RAILWAY_ENVIRONMENT
|
|
22
|
+
|| !!env.RAILWAY_PROJECT_ID
|
|
23
|
+
|| !!env.RAILWAY_SERVICE_ID;
|
|
24
|
+
}
|
|
25
|
+
export function isVerificationEmailReady(env = process.env) {
|
|
26
|
+
if (!isProtectedEmailEnv(env))
|
|
27
|
+
return true;
|
|
28
|
+
return !!env.RESEND_API_KEY?.trim();
|
|
29
|
+
}
|
|
30
|
+
function purposeText(purpose) {
|
|
31
|
+
if (purpose === 'register')
|
|
32
|
+
return { zh: '注册账户(验证邮箱)', en: 'register your account (verify email)' };
|
|
33
|
+
if (purpose === 'bind_email')
|
|
34
|
+
return { zh: '绑定邮箱', en: 'bind your email address' };
|
|
35
|
+
if (purpose === 'recover_key')
|
|
36
|
+
return { zh: '找回密钥', en: 'recover your account key' };
|
|
37
|
+
if (purpose.startsWith('withdraw_confirm'))
|
|
38
|
+
return { zh: '确认提现', en: 'confirm a withdrawal' };
|
|
39
|
+
return { zh: '验证身份', en: 'verify your identity' };
|
|
40
|
+
}
|
|
41
|
+
function escapeHtml(s) {
|
|
42
|
+
return s.replace(/[&<>"']/g, c => ({
|
|
43
|
+
'&': '&',
|
|
44
|
+
'<': '<',
|
|
45
|
+
'>': '>',
|
|
46
|
+
'"': '"',
|
|
47
|
+
"'": ''',
|
|
48
|
+
}[c] || c));
|
|
49
|
+
}
|
|
50
|
+
export function buildVerificationEmail(input) {
|
|
51
|
+
const purpose = purposeText(input.purpose);
|
|
52
|
+
const baseUrl = input.baseUrl?.trim() || DEFAULT_BASE_URL;
|
|
53
|
+
const subject = 'WebAZ 验证码 / Verification code';
|
|
54
|
+
const text = [
|
|
55
|
+
`WebAZ 验证码: ${input.code}`,
|
|
56
|
+
'',
|
|
57
|
+
`用途: ${purpose.zh}`,
|
|
58
|
+
`有效期: ${input.ttlMin} 分钟`,
|
|
59
|
+
'',
|
|
60
|
+
`Your WebAZ verification code is ${input.code}.`,
|
|
61
|
+
`Use it to ${purpose.en}. It expires in ${input.ttlMin} minutes.`,
|
|
62
|
+
'',
|
|
63
|
+
'If you did not request this code, you can ignore this email.',
|
|
64
|
+
baseUrl,
|
|
65
|
+
].join('\n');
|
|
66
|
+
const html = [
|
|
67
|
+
'<div style="font-family:system-ui,-apple-system,Segoe UI,sans-serif;line-height:1.5;color:#111827">',
|
|
68
|
+
'<h2 style="margin:0 0 12px">WebAZ verification code</h2>',
|
|
69
|
+
`<p style="margin:0 0 8px">用途: ${escapeHtml(purpose.zh)} / ${escapeHtml(purpose.en)}</p>`,
|
|
70
|
+
`<p style="font-size:28px;font-weight:700;letter-spacing:4px;margin:16px 0">${escapeHtml(input.code)}</p>`,
|
|
71
|
+
`<p style="margin:0 0 8px">有效期 ${input.ttlMin} 分钟 / Expires in ${input.ttlMin} minutes.</p>`,
|
|
72
|
+
'<p style="margin:16px 0 0;color:#6b7280">If you did not request this code, you can ignore this email.</p>',
|
|
73
|
+
`<p style="margin:16px 0 0"><a href="${escapeHtml(baseUrl)}">${escapeHtml(baseUrl)}</a></p>`,
|
|
74
|
+
'</div>',
|
|
75
|
+
].join('');
|
|
76
|
+
return { subject, text, html };
|
|
77
|
+
}
|
|
78
|
+
export async function deliverVerificationCode(input) {
|
|
79
|
+
const env = input.env || process.env;
|
|
80
|
+
const logger = input.logger || console;
|
|
81
|
+
if (!isProtectedEmailEnv(env)) {
|
|
82
|
+
logger.log(`[verify] ${input.purpose} -> ${input.target} code=${input.code} (expires ${input.ttlMin}min)`);
|
|
83
|
+
return { ok: true, provider: 'dev_console' };
|
|
84
|
+
}
|
|
85
|
+
const apiKey = env.RESEND_API_KEY?.trim();
|
|
86
|
+
if (!apiKey)
|
|
87
|
+
return emailDeliveryNotConfigured();
|
|
88
|
+
const fetchImpl = input.fetchImpl || globalThis.fetch;
|
|
89
|
+
if (!fetchImpl)
|
|
90
|
+
return emailDeliveryNotConfigured();
|
|
91
|
+
const baseUrl = env.WEBAZ_PUBLIC_URL?.trim() || env.PUBLIC_BASE_URL?.trim() || DEFAULT_BASE_URL;
|
|
92
|
+
const email = buildVerificationEmail({
|
|
93
|
+
code: input.code,
|
|
94
|
+
purpose: input.purpose,
|
|
95
|
+
ttlMin: input.ttlMin,
|
|
96
|
+
baseUrl,
|
|
97
|
+
});
|
|
98
|
+
const body = {
|
|
99
|
+
from: env.EMAIL_FROM?.trim() || DEFAULT_FROM,
|
|
100
|
+
to: [input.target],
|
|
101
|
+
subject: email.subject,
|
|
102
|
+
text: email.text,
|
|
103
|
+
html: email.html,
|
|
104
|
+
};
|
|
105
|
+
const replyTo = env.EMAIL_REPLY_TO?.trim();
|
|
106
|
+
if (replyTo)
|
|
107
|
+
body.reply_to = replyTo;
|
|
108
|
+
try {
|
|
109
|
+
const response = await fetchImpl('https://api.resend.com/emails', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
Authorization: `Bearer ${apiKey}`,
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify(body),
|
|
116
|
+
});
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
logger.warn(`[verify] resend delivery failed: status=${response.status} purpose=${input.purpose}`);
|
|
119
|
+
return emailDeliveryFailed();
|
|
120
|
+
}
|
|
121
|
+
return { ok: true, provider: 'resend' };
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
logger.warn(`[verify] resend delivery failed: network purpose=${input.purpose}`);
|
|
125
|
+
return emailDeliveryFailed();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { SOFTWARE_VERSION, CONTRACT_VERSION } from '../version.js';
|
|
7
7
|
const BASE = 'https://webaz.xyz';
|
|
8
|
-
const GH = 'https://github.com/
|
|
8
|
+
const GH = 'https://github.com/webaz-protocol/webaz/blob/main';
|
|
9
9
|
// 集成必需文档(规则 + onboarding)由协议自身 serve —— 外部 agent 必须能读到它被约束的规则,
|
|
10
10
|
// 不能指向私有 repo 的 GitHub 链接(对外 404)。RFC/审计是 provenance,留 GH(随 repo 公开解锁)。
|
|
11
11
|
const DOCS = `${BASE}/docs`;
|
|
@@ -15,8 +15,49 @@ export function buildIntegrationContract() {
|
|
|
15
15
|
contract_version: CONTRACT_VERSION,
|
|
16
16
|
software_version: SOFTWARE_VERSION,
|
|
17
17
|
thesis: 'WebAZ is agent-native: you integrate by your agent reading this machine-readable contract and self-integrating — we do NOT build a bespoke API/auth/webhook layer per integrator. The protocol provides rules + semantics + boundaries + accountability + eventing + verifiability + settlement. See docs/RFC-011.',
|
|
18
|
-
//
|
|
19
|
-
source_status: 'The source repo (github.com/
|
|
18
|
+
// 源码仓库已公开(github.com/webaz-protocol/webaz);机器可读 spec 也在 /.well-known/*。
|
|
19
|
+
source_status: 'The source repo (github.com/webaz-protocol/webaz) is public (open source). The full machine-readable spec is also available via these /.well-known/* surfaces; an agent never needs the repo to integrate or verify.',
|
|
20
|
+
// 60 秒冷启动 —— 一个从没见过 WebAZ、没有登录态、没有内部上下文的陌生 agent,一次 fetch 就能自我定位:
|
|
21
|
+
// 这是什么 / 从哪开始 / 匿名能做什么 / 鉴权才能做什么 / 安全的第一步 / 怎么提建议参与共建。离散字段(非散文)便于解析。
|
|
22
|
+
// 贡献边界(RFC-017)前置声明:建议 ≠ 贡献事实 ≠ 奖励,避免任何经济/兑现承诺暗示。
|
|
23
|
+
agent_quickstart: {
|
|
24
|
+
what_is_webaz: 'An agent-native, open commerce protocol: humans and AI agents transact on the same state-machine-governed protocol — and can also help build the protocol itself. Pre-launch: simulated test currency, no real money settles yet.',
|
|
25
|
+
canonical_start_url: `${BASE}/.well-known/webaz-integration.json`,
|
|
26
|
+
read_this_first: [`${BASE}/.well-known/webaz-integration.json (this document)`, `${DOCS}/INTEGRATOR.md`],
|
|
27
|
+
public_readonly_entrypoints: [
|
|
28
|
+
`${BASE}/.well-known/webaz-protocol.json`,
|
|
29
|
+
`${BASE}/.well-known/webaz-capabilities.json`,
|
|
30
|
+
`${BASE}/.well-known/webaz-entities.json`,
|
|
31
|
+
`${BASE}/.well-known/webaz-goals.json`,
|
|
32
|
+
`${BASE}/api/protocol-status`,
|
|
33
|
+
`${BASE}/api/agent/changes`,
|
|
34
|
+
`${BASE}/api/public/build-tasks`,
|
|
35
|
+
],
|
|
36
|
+
anonymous_allowed_actions: [
|
|
37
|
+
'read every /.well-known/* surface + public GET endpoints (no credential)',
|
|
38
|
+
'browse the live catalog at /#discover',
|
|
39
|
+
'discover open build tasks — GET /api/public/build-tasks (MCP: webaz_contribute action=list_open)',
|
|
40
|
+
'submit a build/improvement suggestion — POST /api/public/task-proposals (MCP: webaz_contribute action=suggest); no key required',
|
|
41
|
+
],
|
|
42
|
+
authenticated_required_actions: [
|
|
43
|
+
'any write / transact (register, order, list, fulfil, dispute)',
|
|
44
|
+
'claim or submit a build task (MCP: webaz_contribute action=claim / submit)',
|
|
45
|
+
'track your own suggestions (MCP: webaz_contribute action=my_suggestions)',
|
|
46
|
+
],
|
|
47
|
+
how_to_authenticate: `An api_key requires a REAL HUMAN to register (invite + Passkey) at ${BASE}/#welcome — agents CANNOT self-register; this is the accountability root. Browsing and reading need no key.`,
|
|
48
|
+
safe_next_actions: [
|
|
49
|
+
'1. Read this contract + INTEGRATOR.md to learn the boundaries (anonymous vs. authenticated).',
|
|
50
|
+
'2. Browse anonymously: the catalog (/#discover) and the well-known surfaces above.',
|
|
51
|
+
'3. To improve the protocol: discover open tasks or submit a suggestion — both keyless (see proposal_flow).',
|
|
52
|
+
'4. To transact: have your accountable human get an invite + api_key, then declare scope (§③).',
|
|
53
|
+
],
|
|
54
|
+
proposal_flow: {
|
|
55
|
+
discover: 'GET /api/public/build-tasks (or MCP webaz_contribute action=list_open) — open, public, no key.',
|
|
56
|
+
suggest: 'POST /api/public/task-proposals (or MCP webaz_contribute action=suggest) — anonymous OK; lands in the maintainer review inbox.',
|
|
57
|
+
after_submit: 'A human maintainer reviews. A suggestion never auto-publishes to the task board and is never auto-accepted; conversion to a formal task is manual.',
|
|
58
|
+
},
|
|
59
|
+
contribution_boundary: 'A suggestion is a proposal in the maintainer review inbox — NOT a contribution fact, NOT formal participation, and NOT any economic or redemption right. Recorded contribution is facts / evidence / attribution only (RFC-017); it confers no payment and no entitlement.',
|
|
60
|
+
},
|
|
20
61
|
// 外部 agent 的第一道问题:"我怎么从匿名读升到能写?" —— 入口必须自答(不依赖 GitHub)。
|
|
21
62
|
access: {
|
|
22
63
|
browse_first: 'No account needed to START: browse the live catalog at https://webaz.xyz/#discover and read every well-known surface below anonymously. Try before you commit.',
|
|
@@ -60,8 +101,9 @@ export function buildIntegrationContract() {
|
|
|
60
101
|
enters_core_test: 'A capability enters the protocol (vs integrator self-solving) iff ALL: ≥N independent integrators need it × needs cross-party trust/verification × cannot be reconstructed from already-exposed data.',
|
|
61
102
|
iron_rule: 'arbitrate / vote / agent_revoke / delete_passkey / large withdraw require a live WebAuthn ceremony regardless of declared scope.',
|
|
62
103
|
references: {
|
|
104
|
+
// RFC-011 is the public formalization of the agent-native integration audit; the audit doc itself is
|
|
105
|
+
// an internal artifact (not in the public tree), so it is not advertised as a reference here.
|
|
63
106
|
rfc_011: `${GH}/docs/rfcs/RFC-011-agent-native-integration-contract.md`,
|
|
64
|
-
audit: `${GH}/docs/AGENT-NATIVE-INTEGRATION-AUDIT.md`,
|
|
65
107
|
manifest: `${BASE}/.well-known/webaz-protocol.json`,
|
|
66
108
|
},
|
|
67
109
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excised stub factory. Accepts the same deps as the original engine (so the call site is unchanged) but
|
|
3
|
+
* returns no-op functions. Deps are intentionally unused.
|
|
4
|
+
*/
|
|
5
|
+
export function createPvSettlementEngine(_deps) {
|
|
6
|
+
return {
|
|
7
|
+
// 匹配结算已切除:不匹配、不产生 Score、不动 PV 腿。永远返回 0。
|
|
8
|
+
runBinarySettlement: () => 0,
|
|
9
|
+
// 兑付已切除:永远 disabled,从不发放。
|
|
10
|
+
executeSafeSettlementCron: () => ({ periodId: '', status: 'disabled' }),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { privateKeyToAccount, privateKeyToAddress } from 'viem/accounts';
|
|
2
|
+
import { createHmac } from 'node:crypto';
|
|
3
|
+
/** Seed string for the hot-wallet role (also the issuer role today — see Phase 0.5). */
|
|
4
|
+
export const HOT_WALLET_SEED = 'platform-hot-wallet';
|
|
5
|
+
/**
|
|
6
|
+
* In-process signer derived from a single master seed (current production behavior; dev/testnet).
|
|
7
|
+
* `privKey(role) = 0x<HMAC-SHA256(masterSeed, role)>` — byte-for-byte identical to the legacy
|
|
8
|
+
* `derivePrivKey` in server.ts, so addresses / signatures do not change.
|
|
9
|
+
*
|
|
10
|
+
* Phase 1+ will provide `createKmsSigner(...)` / `createSafeSigner(...)` implementing the same
|
|
11
|
+
* interface, selected via the `HOT_WALLET_SIGNER` env var.
|
|
12
|
+
*/
|
|
13
|
+
export function createLocalSeedSigner(masterSeed) {
|
|
14
|
+
const privKey = (role) => `0x${createHmac('sha256', masterSeed).update(role).digest('hex')}`;
|
|
15
|
+
// Issuer currently shares the hot-wallet key (unchanged address). Phase 0.5 points it at a
|
|
16
|
+
// dedicated key + dual-key credential verification; do NOT change this seed before that.
|
|
17
|
+
const ISSUER_SEED = HOT_WALLET_SEED;
|
|
18
|
+
return {
|
|
19
|
+
hotAccount: () => privateKeyToAccount(privKey(HOT_WALLET_SEED)),
|
|
20
|
+
hotAddress: () => privateKeyToAddress(privKey(HOT_WALLET_SEED)),
|
|
21
|
+
depositAccount: (userId) => privateKeyToAccount(privKey(userId)),
|
|
22
|
+
depositAddress: (userId) => privateKeyToAddress(privKey(userId)),
|
|
23
|
+
issuerSignMessage: (message) => privateKeyToAccount(privKey(ISSUER_SEED)).signMessage({ message }),
|
|
24
|
+
issuerAddress: () => privateKeyToAddress(privKey(ISSUER_SEED)),
|
|
25
|
+
};
|
|
26
|
+
}
|