@seasonkoh/webaz 0.1.19 → 0.1.21

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.
@@ -0,0 +1,64 @@
1
+ /**
2
+ * RFC-011 §② 负空间策略 —— agent【不能做什么】+ 真实 enforced 限额 + 后果阶梯,统一成机读契约面。
3
+ *
4
+ * 正空间(写能力矩阵)已 #126 发布在 /.well-known/webaz-capabilities.json;本面是其【负空间】补全。
5
+ *
6
+ * doc=code:限额表从 ./limits.ts 读(与 server.ts enforcer 同源,零漂移);per-agent 速率从 protocol_params
7
+ * 实时读(治理可调)。禁区对照 META-RULES-FULL.md #3。后果阶梯对应 server.ts issueAgentStrike 状态机。
8
+ */
9
+ import { SOFTWARE_VERSION, CONTRACT_VERSION } from '../version.js';
10
+ import { capabilityMatrix } from './endpoint-actions.js';
11
+ import { AGENT_RATE_PER_MIN_DEFAULTS, CROSS_USER_READ_DAILY_CAP, MASS_ACTION_DAILY_CAPS, IP_RATE_DEFAULT } from './limits.js';
12
+ const BASE = 'https://webaz.xyz';
13
+ export function buildNegativeSpace(getParam) {
14
+ // per-agent 每分钟速率:实时读 param(治理可调),回落 limits.ts 默认。
15
+ const perAgentRatePerMin = {};
16
+ for (const level of Object.keys(AGENT_RATE_PER_MIN_DEFAULTS)) {
17
+ perAgentRatePerMin[level] = getParam(`agent_rate_${level}_per_min`, AGENT_RATE_PER_MIN_DEFAULTS[level]);
18
+ }
19
+ const cap = capabilityMatrix();
20
+ return {
21
+ contract_version: CONTRACT_VERSION,
22
+ software_version: SOFTWARE_VERSION,
23
+ note: 'RFC-011 §② negative space — what an agent must NOT do + the ENFORCED limits + the consequence ladder. The positive write boundary is the capability matrix (/.well-known/webaz-capabilities.json). Numeric limits are doc=code (shared with the runtime enforcer via src/limits.ts); per-agent rate caps are read live from protocol_params. Crossing a limit returns 429; repeated abuse escalates strikes (see consequence_ladder).',
24
+ // 禁区(质性)—— 元规则 #3,机制 enforce
25
+ forbidden: [
26
+ 'rebuild/aggregate a cross-user graph or dataset (user profiling, content farming, scraping) — meta-rule #3',
27
+ 'resell or redistribute user data obtained via the protocol — meta-rule #3',
28
+ 'impersonate another user or the protocol itself',
29
+ 'exceed your declared scope (capability matrix write_actions you did not declare)',
30
+ 'self-register accounts to bypass invite/captcha/real-person accountability (NETWORK mode blocks agent self-register)',
31
+ ],
32
+ forbidden_enforced_by: [
33
+ 'default-deny write boundary (undeclared agent → AGENT_SCOPE_UNDECLARED)',
34
+ 'cross-user read daily cap (distinct other-user reads; humans capped too)',
35
+ 'sensitive cross-user read scopes (search / profile) constrain declared agents',
36
+ 'accountability strikes → 3-strike block; api-key revocation',
37
+ ],
38
+ // enforced 限额
39
+ rate_limits: {
40
+ per_agent_per_min: { by_trust_level: perAgentRatePerMin, param: 'agent_rate_<level>_per_min (live)', on_exceed: '429; sustained abuse → strike (reason rate_limit_abuse)' },
41
+ cross_user_read_daily: { unit: 'distinct other-user reads per day (e.g. /api/users/:id/*)', by_trust_level: CROSS_USER_READ_DAILY_CAP, note: 'passkey_human is capped too (a scraper using a real account does not bypass)', on_exceed: '403 CROSS_USER_READ_DAILY_CAP' },
42
+ mass_action_daily: { unit: 'social-write actions per day', by_action_and_level: MASS_ACTION_DAILY_CAPS, on_exceed: '429 AGENT_DAILY_CAP; ≥3 overruns/24h → strike (warning)' },
43
+ anonymous_ip: { max: IP_RATE_DEFAULT.max, window_ms: IP_RATE_DEFAULT.window_ms, note: 'per-IP default for public/unauthenticated endpoints' },
44
+ },
45
+ read_scopes: cap.read_scopes, // 敏感跨用户读门(search / profile),与 ② 同源
46
+ // 后果阶梯(对应 issueAgentStrike 状态机)
47
+ consequence_ladder: {
48
+ model: '3-strike state machine on the agent api_key (→ passport, → custodian).',
49
+ steps: [
50
+ { level: 'warning', effect: 'recorded; expires ~24h', escalates: 'a 2nd warning within 7 days → suspend_7d' },
51
+ { level: 'suspend_7d', effect: '7-day suspension; active skills auto-disabled', escalates: 'a 3rd suspension within 30 days → permanent' },
52
+ { level: 'permanent', effect: 'permanent block of the api_key' },
53
+ ],
54
+ appeal: `POST ${BASE}/api/me/agents/strikes/:id/appeal (reason ≥10 chars)`,
55
+ enforced_by: 'src/pwa/server.ts issueAgentStrike + agent_strikes table',
56
+ },
57
+ iron_rule: 'arbitrate / vote / agent_revoke / delete_passkey / large withdraw require a live WebAuthn ceremony regardless of declared scope (CHARTER §4 iron-rule) — no scope or rate budget overrides it.',
58
+ references: {
59
+ meta_rules: `${BASE}/docs/META-RULES-FULL.md`, // 协议自服务 —— agent 必须能读到约束它的规则
60
+ capability_matrix: `${BASE}/.well-known/webaz-capabilities.json`,
61
+ integrator_guide: `${BASE}/docs/INTEGRATOR.md`,
62
+ },
63
+ };
64
+ }
@@ -3256,7 +3256,7 @@ async function renderAdminDashboard(app) {
3256
3256
  ])
3257
3257
  const kpiTokenomics2 = kpiGrid([
3258
3258
  { label: t('累计分享分润'),value: Number(tk.commission_total || 0).toFixed(2), unit: 'WAZ' },
3259
- { label: t('累计对碰发放'),value: Number(tk.binary_waz_total || 0).toFixed(2), unit: 'WAZ' },
3259
+ { label: t('累计匹配发放'),value: Number(tk.binary_waz_total || 0).toFixed(2), unit: 'WAZ' },
3260
3260
  { label: t('PV 待处理'), value: tk.ledger_pending ?? 0 },
3261
3261
  ])
3262
3262
  // 异常告警 banner — 多条件聚合
@@ -3298,7 +3298,7 @@ async function renderAdminDashboard(app) {
3298
3298
  </div>
3299
3299
  <div style="font-size:13px;color:#6b7280;margin:16px 0 8px">⚛ ${t('Tokenomics')}</div>
3300
3300
  <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:16px">
3301
- ${quickAction('#admin/tokenomics', '⚛', t('双轨基金 / Tier 配置 / 高额榜'))}
3301
+ ${quickAction('#admin/tokenomics', '⚛', t('积分基金 / Tier 配置 / 高额榜'))}
3302
3302
  </div>`
3303
3303
 
3304
3304
  // A5 重设:渐变标题 + 分区标题 + 颜色块分组
@@ -4352,7 +4352,7 @@ async function renderApplyRewards(app) {
4352
4352
  <div class="card" style="margin-bottom:16px;background:#fef2f2;border-color:#fca5a5">
4353
4353
  <div style="font-weight:600;color:#991b1b;margin-bottom:6px;font-size:14px">⚠️ ${t('本流程与购物无关')}</div>
4354
4354
  <div style="font-size:12px;color:#7f1d1d;line-height:1.6">
4355
- ${t('你可以随时退出,不影响任何已下单或未来订单。本流程涉及经济关系登记(三级佣金 + 双轨配对),请仔细阅读全部条款。')}<br>
4355
+ ${t('你可以随时退出,不影响任何已下单或未来订单。本流程涉及经济关系登记(三级佣金 + 积分配对),请仔细阅读全部条款。')}<br>
4356
4356
  <span style="opacity:0.85">This flow is not part of shopping. You can leave anytime without affecting any orders. This is an economic-relationship registration — please read all terms.</span>
4357
4357
  </div>
4358
4358
  </div>
@@ -5536,7 +5536,7 @@ async function renderAdminTokenomics(app) {
5536
5536
  <h2 style="font-size:15px;font-weight:600;margin:16px 0 8px">🎁 ${t('管理津贴池')}</h2>
5537
5537
  <div class="card" style="margin-bottom:12px">
5538
5538
  <div style="font-size:18px;font-weight:700">${Number(mb.balance ?? 0).toFixed(2)} WAZ</div>
5539
- <div style="font-size:11px;color:#9ca3af;margin-top:4px">${t('来自协议费 50%,用于大博主对碰团队 10/5/2% 补贴')}</div>
5539
+ <div style="font-size:11px;color:#9ca3af;margin-top:4px">${t('来自协议费 50%,用于大博主匹配团队 10/5/2% 补贴')}</div>
5540
5540
  <div id="mgmt-bonus-control" style="margin-top:12px;padding:10px;background:#fef9c3;border:1px solid #fde047;border-radius:6px">
5541
5541
  <div style="font-size:12px;font-weight:600;margin-bottom:6px">⚠️ ${t('津贴门控')}</div>
5542
5542
  <div id="mgmt-bonus-status" style="font-size:11px;color:#92400e;margin-bottom:8px">${t('加载中...')}</div>
@@ -5560,7 +5560,7 @@ async function renderAdminTokenomics(app) {
5560
5560
  <div class="card" style="margin-bottom:12px">
5561
5561
  <div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px">
5562
5562
  <button class="btn btn-outline btn-sm" style="font-size:11px" onclick="doTkProcess()">${t('处理 PV 流水')}</button>
5563
- <button class="btn btn-outline btn-sm" style="font-size:11px" onclick="doTkSettle()">${t('触发对碰结算')}</button>
5563
+ <button class="btn btn-outline btn-sm" style="font-size:11px" onclick="doTkSettle()">${t('触发匹配结算')}</button>
5564
5564
  <button class="btn btn-outline btn-sm" style="font-size:11px;color:#dc2626;border-color:#dc2626" onclick="if(confirm('${t('确认分发 WAZ?这将清空池子按 N 比例分配')}'))doTkDistribute()">${t('分发 WAZ(清池)')}</button>
5565
5565
  </div>
5566
5566
  <div style="font-size:12px;color:#6b7280">${t('PV 待处理流水')}: ${data.pv_ledger?.pending ?? 0} ${t('条')} · ${t('待累积 PV')}: ${Number(data.pv_ledger?.pending_pv ?? 0).toLocaleString()}</div>
@@ -5580,10 +5580,10 @@ async function renderAdminTokenomics(app) {
5580
5580
  <div style="padding:8px 12px;font-size:11px;color:#6b7280">${t('调整:POST /api/admin/tokenomics/tier with body {tier, pv_threshold, score_per_hit, active}')}</div>
5581
5581
  </div>
5582
5582
 
5583
- <h2 style="font-size:15px;font-weight:600;margin:16px 0 8px">🏆 ${t('Top 推土机佣金')} (Top 10)</h2>
5583
+ <h2 style="font-size:15px;font-weight:600;margin:16px 0 8px">🏆 ${t('Top 三级佣金')} (Top 10)</h2>
5584
5584
  <div class="card" style="padding:0;margin-bottom:12px">${commRows}</div>
5585
5585
 
5586
- <h2 style="font-size:15px;font-weight:600;margin:16px 0 8px">🏆 ${t('Top 对碰收益')} (Top 10)</h2>
5586
+ <h2 style="font-size:15px;font-weight:600;margin:16px 0 8px">🏆 ${t('Top 匹配收益')} (Top 10)</h2>
5587
5587
  <div class="card" style="padding:0;margin-bottom:12px">${binaryRows}</div>
5588
5588
  `, 'admin')
5589
5589
  setTimeout(loadRequireRefStatus, 100)
@@ -5643,7 +5643,7 @@ window.doTkProcess = async () => {
5643
5643
  window.doTkSettle = async () => {
5644
5644
  const res = await POST('/admin/atomic/run-settlement', {})
5645
5645
  const msg = document.getElementById('tk-msg')
5646
- if (msg) msg.innerHTML = res.error ? alert$('error', res.error) : alert$('success', `${t('已触发对碰')}: ${res.settled}`)
5646
+ if (msg) msg.innerHTML = res.error ? alert$('error', res.error) : alert$('success', `${t('已触发匹配')}: ${res.settled}`)
5647
5647
  setTimeout(() => renderAdminTokenomics(document.getElementById('app')), 800)
5648
5648
  }
5649
5649
 
@@ -6716,10 +6716,10 @@ async function renderPromoter(app) {
6716
6716
  const _kpiRestricted = _mlmMax <= 1
6717
6717
  // 两个语义独立的 gate(三级奖励 ≠ PV 双轨系统)—— 2026-06-04 已解耦:
6718
6718
  // - _kpiRestricted (max ≤ 1):紧张地区 — KPI 改时间线 / team 隐 WAZ
6719
- // - _pvAllowed:PV 双轨/对碰系统是否开启,读 region_pv_enabled(独立旋钮,不再绑 max≥3)
6719
+ // - _pvAllowed:PV 双轨/匹配系统是否开启,读 region_pv_enabled(独立旋钮,不再绑 max≥3)
6720
6720
  // commission 层级(max_levels) 与 PV 系统(pv_enabled) 分离:可单独开某辖区到 L2/L3 而 PV 仍关。
6721
6721
  const _pvAllowed = Number(state.user?.region_pv_enabled ?? 0) === 1
6722
- // 紧张地区:拼装最近奖励时间线(commission + 对碰 binary 混合,按时间倒序取 5 条)
6722
+ // 紧张地区:拼装最近奖励时间线(commission + 匹配 binary 混合,按时间倒序取 5 条)
6723
6723
  let kpiBar = ''
6724
6724
  if (_kpiRestricted) {
6725
6725
  const _cm = (data.recent || []).slice(0, 10).map(r => ({
@@ -7051,13 +7051,13 @@ async function renderPromoter(app) {
7051
7051
 
7052
7052
  // ─── ⑥ WebAZ 发展奖(默认折叠;紧张 PV 地区精简版)───
7053
7053
  // 合规:max_levels ≤ 1 的地区(GCC/越南/印尼/菲律宾 + 未审计地区)
7054
- // 只显示"累计推广 N 人"客观数字,全部金钱/tier/对碰话术隐藏
7054
+ // 只显示"累计推广 N 人"客观数字,全部金钱/tier/匹配话术隐藏
7055
7055
  // 复用顶部 _kpiRestricted(同一含义,避免重复定义)
7056
7056
  const _totalRecruits = Number(data.team?.l1 || 0) + Number(data.team?.l2 || 0) + Number(data.team?.l3 || 0)
7057
7057
  // 最右侧地区显示(所有模式都加)
7058
7058
  const _userRegion = state.user?.region || 'global'
7059
7059
  const _regionChip = `<span style="font-size:11px;color:#6b7280;white-space:nowrap;font-weight:400">${regionLabel(_userRegion)}</span>`
7060
- // PV 双轨系统:只在 _pvAllowed(region_pv_enabled=1,2026-06-04 解耦,独立于 max_levels)才显示完整 tier/对碰/弱腿;
7060
+ // PV 双轨系统:只在 _pvAllowed(region_pv_enabled=1,2026-06-04 解耦,独立于 max_levels)才显示完整 tier/匹配/弱侧;
7061
7061
  // 未开启 PV 的地区 → 走精简卡(分数仍后台计算累积,待 pv_enabled 开启/迁移后兑现)
7062
7062
  const atomicSection = atomic.left_invite_url
7063
7063
  ? (!_pvAllowed
@@ -7080,7 +7080,7 @@ async function renderPromoter(app) {
7080
7080
  <span>
7081
7081
  🌟 ${t('WebAZ 发展奖')}
7082
7082
  <span style="font-weight:400;font-size:12px;color:#6b7280;margin-left:6px">
7083
- ${t('弱腿')} ${weak.toLocaleString()} PV · ${t('累计')} ${(atomic.score?.settled_waz || 0).toFixed(2)} WAZ
7083
+ ${t('弱侧')} ${weak.toLocaleString()} PV · ${t('累计')} ${(atomic.score?.settled_waz || 0).toFixed(2)} WAZ
7084
7084
  </span>
7085
7085
  </span>
7086
7086
  ${_regionChip}
@@ -7266,7 +7266,7 @@ window.setBoughtKw = (kw) => {
7266
7266
  }, 200)
7267
7267
  }
7268
7268
 
7269
- // 双轨对碰内部内容(折叠展开后渲染,从 renderAtomicSection 简化抽出)
7269
+ // 积分匹配内部内容(折叠展开后渲染,从 renderAtomicSection 简化抽出)
7270
7270
  function renderAtomicInner(a, leftPv, rightPv, weak, nextTier, nextProgress) {
7271
7271
  const tiers = a.tier_config || []
7272
7272
  const tierTable = tiers.map(x => `
@@ -7276,7 +7276,7 @@ function renderAtomicInner(a, leftPv, rightPv, weak, nextTier, nextProgress) {
7276
7276
  <td style="padding:5px 8px;font-size:12px;text-align:right;font-weight:600">${x.score_per_hit}</td>
7277
7277
  </tr>`).join('')
7278
7278
 
7279
- // 本月对碰次数 + WAZ
7279
+ // 本月匹配次数 + WAZ
7280
7280
  const now = new Date()
7281
7281
  const monthStart = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10)
7282
7282
  const monthBinary = (a.recent_binary || []).filter(r => (r.created_at || '').slice(0, 10) >= monthStart)
@@ -7293,7 +7293,7 @@ function renderAtomicInner(a, leftPv, rightPv, weak, nextTier, nextProgress) {
7293
7293
  <div>tier ${r.tier} · Score <strong>${r.score}</strong> · ${r.settled_at ? `<span style="color:#16a34a">${t('已结')} ${Number(r.waz_amount).toFixed(2)}</span>` : `<span style="color:#d97706">${t('待分配')}</span>`}</div>
7294
7294
  <div style="color:#9ca3af">${fmtTime(r.created_at)}</div>
7295
7295
  </div>`).join('')
7296
- : `<div style="text-align:center;color:#9ca3af;padding:10px;font-size:12px">${t('暂无对碰记录')}</div>`
7296
+ : `<div style="text-align:center;color:#9ca3af;padding:10px;font-size:12px">${t('暂无匹配记录')}</div>`
7297
7297
 
7298
7298
  return `
7299
7299
  <!-- 我的位置 -->
@@ -7302,7 +7302,7 @@ function renderAtomicInner(a, leftPv, rightPv, weak, nextTier, nextProgress) {
7302
7302
  📍 ${t('我挂位置')}: <strong>${escHtml(a.my_placement.name)}</strong> 的 ${a.my_placement.side === 'left' ? '🔵 左侧' : '🟢 右侧'}
7303
7303
  </div>` : `
7304
7304
  <div style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;padding:8px 10px;font-size:12px;color:#9ca3af;margin-bottom:10px">
7305
- ${t('你还没加入任何上级的双轨树(独立根节点)')}
7305
+ ${t('你还没加入任何上级的积分树(独立根节点)')}
7306
7306
  </div>`}
7307
7307
 
7308
7308
  <!-- 左/右 PV -->
@@ -7319,25 +7319,25 @@ function renderAtomicInner(a, leftPv, rightPv, weak, nextTier, nextProgress) {
7319
7319
  </div>
7320
7320
  </div>
7321
7321
 
7322
- <!-- 本月对碰统计 + 总对碰 -->
7322
+ <!-- 本月匹配统计 + 总匹配 -->
7323
7323
  <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;margin-bottom:10px">
7324
7324
  <div style="text-align:center;background:#fef3c7;border-radius:6px;padding:8px 4px">
7325
- <div style="font-size:10px;color:#92400e">${t('本月对碰次数')}</div>
7325
+ <div style="font-size:10px;color:#92400e">${t('本月匹配次数')}</div>
7326
7326
  <div style="font-size:14px;font-weight:700;color:#78350f">${monthHits}</div>
7327
7327
  </div>
7328
7328
  <div style="text-align:center;background:#dcfce7;border-radius:6px;padding:8px 4px">
7329
- <div style="font-size:10px;color:#166534">${t('本月对碰 WAZ')}</div>
7329
+ <div style="font-size:10px;color:#166534">${t('本月匹配 WAZ')}</div>
7330
7330
  <div style="font-size:14px;font-weight:700;color:#14532d">${monthWaz.toFixed(2)}</div>
7331
7331
  </div>
7332
7332
  <div style="text-align:center;background:#eef2ff;border-radius:6px;padding:8px 4px">
7333
- <div style="font-size:10px;color:#4338ca">${t('累计对碰次数')}</div>
7333
+ <div style="font-size:10px;color:#4338ca">${t('累计匹配次数')}</div>
7334
7334
  <div style="font-size:14px;font-weight:700;color:#3730a3">${totalHits}</div>
7335
7335
  </div>
7336
7336
  </div>
7337
7337
 
7338
7338
  ${renderBinaryTree(a.binary_tree)}
7339
7339
 
7340
- <div style="font-size:12px;color:#6b7280;margin-bottom:6px">${t('弱腿对碰量')}: <strong>${weak.toLocaleString()}</strong> PV</div>
7340
+ <div style="font-size:12px;color:#6b7280;margin-bottom:6px">${t('弱侧匹配量')}: <strong>${weak.toLocaleString()}</strong> PV</div>
7341
7341
  ${nextTier ? `
7342
7342
  <div style="font-size:11px;color:#6b7280;margin-bottom:4px">${t('距离 tier')} ${nextTier.tier} ${t('门槛')} ${Number(nextTier.pv_threshold).toLocaleString()} PV (+${nextTier.score_per_hit} Score)</div>
7343
7343
  <div style="background:#f3f4f6;height:6px;border-radius:3px;overflow:hidden;margin-bottom:10px">
@@ -7345,7 +7345,7 @@ function renderAtomicInner(a, leftPv, rightPv, weak, nextTier, nextProgress) {
7345
7345
  </div>` : ''}
7346
7346
 
7347
7347
  <details style="margin-bottom:10px">
7348
- <summary style="font-size:12px;color:#6366f1;cursor:pointer">${t('对碰档位表')}</summary>
7348
+ <summary style="font-size:12px;color:#6366f1;cursor:pointer">${t('匹配档位表')}</summary>
7349
7349
  <table style="width:100%;margin-top:6px;border-collapse:collapse">
7350
7350
  <tr style="background:#f9fafb;font-size:11px;color:#6b7280">
7351
7351
  <th style="padding:5px 8px;text-align:left">Tier</th>
@@ -7355,7 +7355,7 @@ function renderAtomicInner(a, leftPv, rightPv, weak, nextTier, nextProgress) {
7355
7355
  ${tierTable}
7356
7356
  </table>
7357
7357
  </details>
7358
- <h4 style="font-size:12px;font-weight:600;margin:6px 0">📊 ${t('最近对碰')}</h4>
7358
+ <h4 style="font-size:12px;font-weight:600;margin:6px 0">📊 ${t('最近匹配')}</h4>
7359
7359
  ${recentRows}`
7360
7360
  }
7361
7361
 
@@ -7510,10 +7510,10 @@ function renderAtomicSection(a) {
7510
7510
  <div>tier ${r.tier} · Score <strong>${r.score}</strong> · ${r.settled_at ? `<span style="color:#16a34a">已结 ${Number(r.waz_amount).toFixed(2)} WAZ</span>` : `<span style="color:#d97706">待分配</span>`}</div>
7511
7511
  <div style="color:#9ca3af">${fmtTime(r.created_at)}</div>
7512
7512
  </div>`).join('')
7513
- : `<div style="text-align:center;color:#9ca3af;padding:16px;font-size:12px">${t('暂无对碰记录')}</div>`
7513
+ : `<div style="text-align:center;color:#9ca3af;padding:16px;font-size:12px">${t('暂无匹配记录')}</div>`
7514
7514
 
7515
7515
  return `
7516
- <h2 style="font-size:15px;font-weight:600;margin:24px 0 8px">⚛ ${t('原子能双轨对碰')}</h2>
7516
+ <h2 style="font-size:15px;font-weight:600;margin:24px 0 8px">⚛ ${t('积分积分匹配')}</h2>
7517
7517
 
7518
7518
  <div class="card" style="margin-bottom:12px;background:linear-gradient(135deg,#dbeafe,#f0fdf4)">
7519
7519
  <div style="font-size:13px;color:#6b7280;margin-bottom:6px">🔗 ${t('左右码(完全对称 / 末端垂直挂靠)')}</div>
@@ -7568,7 +7568,7 @@ function renderAtomicSection(a) {
7568
7568
  ${renderBinaryTree(a.binary_tree)}
7569
7569
 
7570
7570
  <div class="card" style="margin-bottom:12px">
7571
- <div style="font-size:13px;color:#6b7280;margin-bottom:6px">${t('弱腿对碰量')}: <strong>${pair.toLocaleString()}</strong> PV</div>
7571
+ <div style="font-size:13px;color:#6b7280;margin-bottom:6px">${t('弱侧匹配量')}: <strong>${pair.toLocaleString()}</strong> PV</div>
7572
7572
  ${nextTier ? `
7573
7573
  <div style="font-size:11px;color:#6b7280;margin-bottom:4px">${t('距离 tier')} ${nextTier.tier} ${t('门槛')} ${Number(nextTier.pv_threshold).toLocaleString()} PV (${nextTier.score_per_hit} Score)</div>
7574
7574
  <div style="background:#f3f4f6;height:6px;border-radius:3px;overflow:hidden">
@@ -7588,7 +7588,7 @@ function renderAtomicSection(a) {
7588
7588
  </div>
7589
7589
 
7590
7590
  <details style="margin-bottom:12px">
7591
- <summary style="font-size:12px;color:#6366f1;cursor:pointer">${t('对碰档位表')}</summary>
7591
+ <summary style="font-size:12px;color:#6366f1;cursor:pointer">${t('匹配档位表')}</summary>
7592
7592
  <table style="width:100%;margin-top:8px;border-collapse:collapse">
7593
7593
  <tr style="background:#f9fafb;font-size:11px;color:#6b7280">
7594
7594
  <th style="padding:6px 8px;text-align:left">Tier</th>
@@ -7599,13 +7599,13 @@ function renderAtomicSection(a) {
7599
7599
  </table>
7600
7600
  </details>
7601
7601
 
7602
- <h3 style="font-size:13px;font-weight:600;margin:8px 0">📊 ${t('最近对碰')}</h3>
7602
+ <h3 style="font-size:13px;font-weight:600;margin:8px 0">📊 ${t('最近匹配')}</h3>
7603
7603
  <div class="card" style="padding:0">
7604
7604
  ${recentRows}
7605
7605
  </div>`
7606
7606
  }
7607
7607
 
7608
- // P12: 三层双轨树(你 + 左右 + 各自左右)
7608
+ // P12: 三层积分树(你 + 左右 + 各自左右)
7609
7609
  function renderBinaryTree(tree) {
7610
7610
  if (!tree || !tree.me) return ''
7611
7611
  const node = (n, bg, fg, label) => n
@@ -7617,7 +7617,7 @@ function renderBinaryTree(tree) {
7617
7617
 
7618
7618
  return `
7619
7619
  <details style="margin-bottom:12px" open>
7620
- <summary style="font-size:12px;color:#6366f1;cursor:pointer;margin-bottom:6px">🌳 ${t('双轨组织图(3 层)')} <span style="color:#9ca3af;font-size:10px">${t('点击节点查看 PV')}</span></summary>
7620
+ <summary style="font-size:12px;color:#6366f1;cursor:pointer;margin-bottom:6px">🌳 ${t('团队组织图(3 层)')} <span style="color:#9ca3af;font-size:10px">${t('点击节点查看 PV')}</span></summary>
7621
7621
  <div style="padding:8px;background:#fff;border:1px solid #e5e7eb;border-radius:8px">
7622
7622
  <!-- L0: 你 -->
7623
7623
  <div style="margin-bottom:6px">
@@ -7693,8 +7693,8 @@ window.showNodePvModal = async (userId) => {
7693
7693
  </div>
7694
7694
 
7695
7695
  <div style="background:#f9fafb;border-radius:6px;padding:8px 10px;font-size:11px;color:#6b7280;line-height:1.7;margin-bottom:10px">
7696
- ${t('弱腿对碰量')}: <strong style="color:#374151">${weak.toLocaleString()}</strong> PV · ${t('双腿均衡')} ${ratio}%<br>
7697
- ${t('累计对碰')}: <strong style="color:#374151">${data.total_hits || 0}</strong> ${t('次')} · ${t('累计获 WAZ')}: <strong style="color:#16a34a">${Number(data.settled_waz || 0).toFixed(2)}</strong><br>
7696
+ ${t('弱侧匹配量')}: <strong style="color:#374151">${weak.toLocaleString()}</strong> PV · ${t('双腿均衡')} ${ratio}%<br>
7697
+ ${t('累计匹配')}: <strong style="color:#374151">${data.total_hits || 0}</strong> ${t('次')} · ${t('累计获 WAZ')}: <strong style="color:#16a34a">${Number(data.settled_waz || 0).toFixed(2)}</strong><br>
7698
7698
  ${t('待结 Score')}: <strong style="color:#d97706">${Number(data.pending_score || 0).toFixed(1)}</strong>
7699
7699
  </div>
7700
7700
 
@@ -7829,11 +7829,11 @@ async function maybePromptPlacementBind() {
7829
7829
  const st = await GET('/profile/placement-status')
7830
7830
  if (!st.can_bind) return // 已有 placement 或 有下线 → 不弹
7831
7831
  setTimeout(() => {
7832
- const ok = confirm(`📍 ${t('系统检测到邀请链接')}\n\n${t('inviter')}: ${inviter}\n\n${t('是否加入对方的双轨 PV 树?')}\n${t('系统将按推荐人偏好自动选择左/右区。一旦加入永久不变。')}`)
7832
+ const ok = confirm(`📍 ${t('系统检测到邀请链接')}\n\n${t('inviter')}: ${inviter}\n\n${t('是否加入对方的积分配对?')}\n${t('系统将按推荐人偏好自动选择左/右区。一旦加入永久不变。')}`)
7833
7833
  if (ok) {
7834
7834
  POST('/profile/bind-placement', { inviter_id: inviter, side: hint.placement_side || undefined }).then(res => {
7835
7835
  if (res.error) alert(`✗ ${res.error}`)
7836
- else alert(`✓ ${t('已加入双轨树')}\n${t('侧')}: ${res.side}\n${t('深度')}: ${res.depth}`)
7836
+ else alert(`✓ ${t('已加入积分树')}\n${t('侧')}: ${res.side}\n${t('深度')}: ${res.depth}`)
7837
7837
  })
7838
7838
  }
7839
7839
  localStorage.setItem(askedKey, Date.now().toString())
@@ -9748,6 +9748,11 @@ async function renderMyBuildFeedback(app) {
9748
9748
  <div style="font-size:13px;color:#374151">${escHtml(String(f.body || ''))}</div>
9749
9749
  ${f.resolution ? `<div style="font-size:11px;color:#6b7280;margin-top:6px;padding-top:6px;border-top:1px solid #f3f4f6">↳ ${escHtml(String(f.resolution))}</div>` : ''}
9750
9750
  ${Number(f.credited_points) > 0 ? `<div style="font-size:11px;color:#16a34a;margin-top:4px">🏅 +${f.credited_points} ${t('共建信誉')}</div>` : ''}
9751
+ ${(f.credit_pending_anchor && !(Number(f.credited_points) > 0)) ? `
9752
+ <div style="font-size:11px;color:#854d0e;background:#fef3c7;border-radius:6px;padding:8px;margin-top:6px">
9753
+ 🔐 ${t('这条贡献已被采纳 —— 绑定 Passkey 即可领取建设信誉(奖励需锚定可问责真人)')}
9754
+ <button class="btn btn-sm" style="margin-top:6px;font-size:11px;padding:4px 10px;background:#7c3aed;color:#fff;border-color:transparent" onclick="(async()=>{ if(await doRegisterPasskey('')){ alert(t('已绑定,建设信誉已补发')); navigate('#build-feedback') } })()">${t('绑定 Passkey 领取')}</button>
9755
+ </div>` : ''}
9751
9756
  </div>`).join('')
9752
9757
  app.innerHTML = shell(`
9753
9758
  <h1 class="page-title">💬 ${t('我的反馈')}</h1>
@@ -10204,7 +10209,7 @@ async function renderMyAgents(app) {
10204
10209
  <summary style="cursor:pointer;color:#6366f1;padding:4px 0">📊 ${t('为什么是')} ${lvlLabel} ${t('级?')}</summary>
10205
10210
  <div style="background:#f9fafb;border-radius:6px;padding:8px 10px;margin-top:4px;font-family:monospace;line-height:1.6">
10206
10211
  <div>${t('加分')}:${t('账号年龄')} +${rep.signals.age_pts} · ${t('订单数')} +${rep.signals.order_pts} · ${t('分享转化')} +${rep.signals.share_pts} · ${t('调用多样性')} +${rep.signals.diversity_pts}</div>
10207
- ${(rep.signals.dispute_penalty + rep.signals.sybil_penalty + rep.signals.cross_penalty + rep.signals.ratelimit_penalty) < 0 ? `<div style="color:#dc2626">${t('扣分')}:${t('争议败诉')} ${rep.signals.dispute_penalty} · ${t('同 IP 异户')} ${rep.signals.sybil_penalty} · ${t('双轨命中')} ${rep.signals.cross_penalty} · ${t('限速命中')} ${rep.signals.ratelimit_penalty}</div>` : ''}
10212
+ ${(rep.signals.dispute_penalty + rep.signals.sybil_penalty + rep.signals.cross_penalty + rep.signals.ratelimit_penalty) < 0 ? `<div style="color:#dc2626">${t('扣分')}:${t('争议败诉')} ${rep.signals.dispute_penalty} · ${t('同 IP 异户')} ${rep.signals.sybil_penalty} · ${t('匹配命中')} ${rep.signals.cross_penalty} · ${t('限速命中')} ${rep.signals.ratelimit_penalty}</div>` : ''}
10208
10213
  <div style="color:#9ca3af;margin-top:4px">${t('阈值')}:new&lt;20 · trusted≥20 · quality≥50 · legend≥80</div>
10209
10214
  </div>
10210
10215
  </details>
@@ -11683,8 +11688,8 @@ window.doLoginByPassword = async () => {
11683
11688
 
11684
11689
 
11685
11690
  // 从 URL 解析分享 hint(30 天 cookie 持久化)
11686
- // ?ref=usr_xxx → 仅推土机 sponsor
11687
- // ?placement=usr_xxx&side=left|right → 仅原子能挂靠
11691
+ // ?ref=usr_xxx → 仅三级佣金 sponsor
11692
+ // ?placement=usr_xxx&side=left|right → 仅积分挂靠
11688
11693
  // ?ref=xxx&side=left|right → 两轨道(ref 同时作 placement)
11689
11694
  // ?ref=xxx&placement=xxx&side=... → 显式两轨道
11690
11695
  function readShareHint() {
@@ -12054,10 +12059,10 @@ function showRegisterSuccessModal(res) {
12054
12059
  const targetBtn = (intended && intended !== '#' && intended !== '')
12055
12060
  ? `<button class="btn btn-primary" style="width:100%;margin-bottom:10px" onclick="window._closeRegModal(true)">🎯 ${t('回到原页')} ${tgt?.title ? '— ' + escHtml(tgt.title) : ''} →</button>`
12056
12061
  : ''
12057
- // P1 (QA 轮 14.a): 区分"邀请人"(referral sponsor) 与"双轨树挂靠"(binary placement)
12062
+ // P1 (QA 轮 14.a): 区分"邀请人"(referral sponsor) 与"积分树挂靠"(binary placement)
12058
12063
  // 旧版挤一行 "已绑定邀请人 X · 左区 depth N" → 用户误以为 depth 是相对邀请人
12059
12064
  const placementLine = res.placement
12060
- ? `<div style="font-size:11px;color:#9ca3af;margin-bottom:14px">${t('双轨树挂靠')}: ${res.placement.side === 'left' ? '🔵 ' + t('左区') : '🟢 ' + t('右区')} · ${t('深度')} ${res.placement.depth}<br><span style="font-size:10px">${t('(挂靠位置由系统按弱区自动决定,与邀请人不一定相邻)')}</span></div>`
12065
+ ? `<div style="font-size:11px;color:#9ca3af;margin-bottom:14px">${t('积分树挂靠')}: ${res.placement.side === 'left' ? '🔵 ' + t('左区') : '🟢 ' + t('右区')} · ${t('深度')} ${res.placement.depth}<br><span style="font-size:10px">${t('(挂靠位置由系统按弱区自动决定,与邀请人不一定相邻)')}</span></div>`
12061
12066
  : ''
12062
12067
  _openModal(`
12063
12068
  <h2 style="font-size:18px;font-weight:600;margin-bottom:8px;color:#16a34a">🎉 ${t('注册成功!')}</h2>
@@ -13565,7 +13570,7 @@ async function renderUserProfile(app, userId) {
13565
13570
  <input type="checkbox" id="feed-visible-tg" style="width:16px;height:16px" ${data.feed_visible ? 'checked' : ''} onchange="toggleFeedVisible(this.checked)">
13566
13571
  <label for="feed-visible-tg" style="font-size:13px;cursor:pointer">${t('在公开动态流显示我的活动')}</label>
13567
13572
  </div>
13568
- <p style="font-size:11px;color:#9ca3af;margin-top:4px;margin-left:26px">${t('关闭后,你的购买/对碰/分润事件不会出现在 发现好物 > 动态')}</p>
13573
+ <p style="font-size:11px;color:#9ca3af;margin-top:4px;margin-left:26px">${t('关闭后,你的购买/匹配/分润事件不会出现在 发现好物 > 动态')}</p>
13569
13574
 
13570
13575
  <div id="social-msg" style="margin-top:10px"></div>
13571
13576
 
@@ -21731,13 +21736,13 @@ async function renderSeller(app) {
21731
21736
  )}
21732
21737
  ${featureCard(
21733
21738
  '🎯',
21734
- t('买家 Agent 意图对碰'),
21739
+ t('买家 Agent 意图匹配'),
21735
21740
  t('零推广费'),
21736
21741
  t('全网买家 Agent 携精准需求(Alias)全天候巡航,与商品语义刚需匹配,订单全自动闭合成交。')
21737
21742
  )}
21738
21743
  ${featureCard(
21739
21744
  '🌊',
21740
- t('智能体裂变分润'),
21745
+ t('智能体扩散分润'),
21741
21746
  t('秒级自动分账'),
21742
21747
  t('商品由其他节点或达人 Agent 智能分享成交,下游分润通过智能合约去中心化自动清算,即刻到账。')
21743
21748
  )}
@@ -24728,7 +24733,7 @@ function renderTrustCard(trust) {
24728
24733
  const neg = []
24729
24734
  if (s.dispute_penalty < 0) neg.push(`${t('仲裁败诉')} ${s.dispute_penalty}`)
24730
24735
  if (s.sybil_penalty < 0) neg.push(`${t('同 IP 多号')} ${s.sybil_penalty}`)
24731
- if (s.cross_penalty < 0) neg.push(`${t('双轨同支')} ${s.cross_penalty}`)
24736
+ if (s.cross_penalty < 0) neg.push(`${t('同支')} ${s.cross_penalty}`)
24732
24737
  if (s.ratelimit_penalty < 0) neg.push(`${t('限速命中')} ${s.ratelimit_penalty}`)
24733
24738
 
24734
24739
  const nextHint = trust.trust_score < trust.raw_mode_min_trust
@@ -24907,7 +24912,7 @@ async function renderWallet(app) {
24907
24912
  </div>
24908
24913
  </div>
24909
24914
 
24910
- <!-- 卖家收入分类速览(仅 seller 显示)- 2026-05-24 对碰列只在 PV 允许地区显示 -->
24915
+ <!-- 卖家收入分类速览(仅 seller 显示)- 2026-05-24 匹配列只在 PV 允许地区显示 -->
24911
24916
  ${state.user?.role === 'seller' ? (() => {
24912
24917
  const _pvOK = Number(state.user?.region_pv_enabled ?? 0) === 1 // 2026-06-04 解耦:PV 列读 pv_enabled,不再绑 max≥3
24913
24918
  const cols = _pvOK ? 4 : 3
@@ -24926,7 +24931,7 @@ async function renderWallet(app) {
24926
24931
  <div style="font-size:9px;color:#9ca3af">${((inc.commissions?.l1?.count||0)+(inc.commissions?.l2?.count||0)+(inc.commissions?.l3?.count||0))} ${t('单')}</div>
24927
24932
  </div>
24928
24933
  ${_pvOK ? `<div style="background:#fefce8;border-radius:6px;padding:8px">
24929
- <div style="font-size:9px;color:#854d0e">⚛ ${t('对碰已结')}</div>
24934
+ <div style="font-size:9px;color:#854d0e">⚛ ${t('匹配已结')}</div>
24930
24935
  <div style="font-size:14px;font-weight:700;color:#a16207;margin-top:2px">${(inc.binary?.settled_waz||0).toFixed(2)}</div>
24931
24936
  <div style="font-size:9px;color:#9ca3af">${inc.binary?.settled_count||0} ${t('次')}</div>
24932
24937
  </div>` : ''}
@@ -24962,7 +24967,7 @@ async function renderWallet(app) {
24962
24967
  })()}
24963
24968
  ${getMaxLevels() >= 3 ? `
24964
24969
  <div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:10px">
24965
- <div style="background:#fefce8;border-radius:6px;padding:10px"><div style="font-size:10px;color:#854d0e">⚛ ${t('双轨已结算')}</div><div style="font-size:15px;font-weight:700;color:#a16207">${inc.binary.settled_waz.toFixed(2)} WAZ</div><div style="font-size:9px;color:#9ca3af">${inc.binary.settled_count} ${t('次')}</div></div>
24970
+ <div style="background:#fefce8;border-radius:6px;padding:10px"><div style="font-size:10px;color:#854d0e">⚛ ${t('积分已结算')}</div><div style="font-size:15px;font-weight:700;color:#a16207">${inc.binary.settled_waz.toFixed(2)} WAZ</div><div style="font-size:9px;color:#9ca3af">${inc.binary.settled_count} ${t('次')}</div></div>
24966
24971
  <div style="background:#fef3c7;border-radius:6px;padding:10px"><div style="font-size:10px;color:#92400e">⏳ ${t('待结 Score')}</div><div style="font-size:15px;font-weight:700;color:#d97706">${inc.binary.pending_score.toFixed(1)}</div><div style="font-size:9px;color:#9ca3af">${t('每月结算')}</div></div>
24967
24972
  </div>` : ''}
24968
24973
  ${inc.sales.count > 0 ? `