@seasonkoh/webaz 0.1.19 → 0.1.20

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,122 @@
1
+ /**
2
+ * RFC-011 §⑧ 经济参与索引 —— 外部 actor 进入协议【价值流】的统一契约面。
3
+ *
4
+ * 价值参与 = liability_tiers 里最高的 `value_participant` 层:不只是读(①)/写(②),
5
+ * 而是【赚费 + 押抵押 + 承担守恒的连带责任】。本表把【已存在 + 已 enforce】的角色串起来。
6
+ *
7
+ * doc=code 纪律(同 ④⑤⑥):
8
+ * - 费率/门槛【请求时实时从 protocol_params 读】(getParam 注入),永不和 enforced 经济漂移 —— 反 #1094 装饰化。
9
+ * - 守恒是硬不变量:所有罚没【再分配,绝不增发】(settleFault)。
10
+ * - 诚实 status:已上线角色标 live;通用第三方承保方(无真实需求)标 scaffolded → 自有 RFC + enters-core 门控,不过早造接口。
11
+ *
12
+ * 公平三原则锚([[project_fairness_principle]]):公开透明 / 谁责任谁承担 / 无责方零成本。
13
+ */
14
+ import { SOFTWARE_VERSION, CONTRACT_VERSION } from '../version.js';
15
+ const GH = 'https://github.com/seasonsagents-art/webaz/blob/main';
16
+ const BASE = 'https://webaz.xyz';
17
+ export function buildEconomicParticipation(getParam) {
18
+ const num = (k, f) => getParam(k, f);
19
+ return {
20
+ contract_version: CONTRACT_VERSION,
21
+ software_version: SOFTWARE_VERSION,
22
+ note: 'RFC-011 §⑧. The roles by which an external actor enters the protocol VALUE flow (earns fees / posts collateral / bears conserved liability) — the highest liability tier (value_participant). Rates & thresholds are read LIVE from protocol_params at request time (doc=code: this index can never drift from the enforced economics). Honesty: roles marked status=live are enforced today; status=scaffolded means the hook exists but generic third-party onboarding awaits its own RFC + real demand (enters-core test).',
23
+ principles: {
24
+ fairness: ['public & transparent', 'liability follows the responsible party', 'zero cost to the faultless party'],
25
+ conservation: 'Every settlement conserves value: a forfeit F is REDISTRIBUTED (protocol ≤ its fee, fund_base excluded / buyer up to 50% / promoters capped at original commission / residual → commission_reserve) — never minted. See engine.settleFault.',
26
+ bootstrap_no_forfeit: 'RFC-008: an order with stake_backing=0 (bootstrap / require_seller_stake=0) incurs ZERO forfeit and never touches the participant\'s free balance. Real forfeit applies only to staked orders.',
27
+ },
28
+ enter_value_flow: 'A value participant is in the accountability net via api_key→passport AND collateral/reputation-bound. Highest liability tier. See /.well-known/webaz-integration.json#liability_tiers.value_participant.',
29
+ roles: [
30
+ {
31
+ role: 'seller_shop',
32
+ enters_as: 'lists products + fulfills orders',
33
+ earns: { source: 'sale proceeds minus protocol fee', protocol_fee_rate: num('protocol_fee_rate_shop', 0.02), fee_hard_cap: 0.02, fee_note: 'RFC-008 hard cap 2%, can only decrease; pre-launch may be waived lower.' },
34
+ collateral: { required: num('require_seller_stake', 0) === 1, param: 'require_seller_stake', model: 'RFC-008 stake_backing per order; bootstrap (=0) → zero forfeit.' },
35
+ liability: { fault_states: ['fault_seller'], penalty_rate: num('fault_penalty_rate', 0.30), penalty_note: 'decoupled from stake rate; staked orders forfeit from stake then free balance; bootstrap orders exempt.', settlement: 'engine.settleFault (conserved)' },
36
+ gate: 'api_key (authenticated_write)',
37
+ status: 'live',
38
+ enforced_by: 'routes/orders-create.ts + layer0 engine.settleFault',
39
+ },
40
+ {
41
+ role: 'seller_secondhand',
42
+ enters_as: 'lists used items + fulfills',
43
+ earns: { source: 'sale proceeds minus protocol fee', protocol_fee_rate: num('protocol_fee_rate_secondhand', 0.01), fee_hard_cap: 0.02, fee_note: 'RFC-008 hard cap 2%, can only decrease.' },
44
+ collateral: { required: num('require_seller_stake', 0) === 1, param: 'require_seller_stake', model: 'same RFC-008 stake model as seller_shop.' },
45
+ liability: { fault_states: ['fault_seller'], penalty_rate: num('fault_penalty_rate', 0.30), settlement: 'engine.settleFault (conserved)' },
46
+ gate: 'api_key (authenticated_write)',
47
+ status: 'live',
48
+ enforced_by: 'routes/orders-create.ts + engine.settleFault',
49
+ },
50
+ {
51
+ role: 'promoter',
52
+ enters_as: 'shares a product link; earns commission on attributed sales',
53
+ earns: { source: 'commission on attributed order', default_commission_rate: num('default_commission_rate', 0.05), rate_note: 'per-product, seller-set; default shown here.' },
54
+ collateral: { required: false, model: 'none; promoter takes no fulfillment liability.' },
55
+ liability: { fault_states: [], note: 'on a fault settlement a promoter\'s payout is clawed back but capped at the original commission (conservation) — never negative.' },
56
+ gate: 'api_key (authenticated_write)',
57
+ status: 'live',
58
+ enforced_by: 'commission attribution + engine.settleFault forfeit distribution',
59
+ },
60
+ {
61
+ role: 'logistics',
62
+ enters_as: 'carries the order; reports pickup/transit/delivery evidence',
63
+ earns: { source: 'order-specific logistics fee (negotiated off-protocol / per-order, NOT a global protocol param)', protocol_param: null },
64
+ collateral: { required: false, optional_hook: 'insurance_cap', model: 'a carrier may set insurance_cap on an order; loss above the cap is covered by the protocol fund (buyer still fully compensated).' },
65
+ liability: { fault_states: ['fault_logistics'], settlement: 'engine.settleFault (conserved); carrier bears loss up to insurance_cap.' },
66
+ gate: 'api_key (authenticated_write) + evidence (gps/photo) on transitions',
67
+ status: 'live',
68
+ enforced_by: 'L0-2 state machine transitions + L3-1 dispute-engine (insurance_cap)',
69
+ },
70
+ {
71
+ role: 'anchor_verifier',
72
+ enters_as: 'independently verifies a seller\'s external-anchor (real-world ownership/authenticity) claim by voting',
73
+ earns: { source: 'verification_fee the seller attached to the anchor, split evenly among correct (content_matches=1) voters on community upgrade', recommended_fee: 2.0, fee_note: 'seller-set per anchor (may be 0 = community verification off); not a global param.' },
74
+ collateral: { required: true, field: 'verifier_whitelist.stake_amount', model: 'staked to join the verifier whitelist.' },
75
+ liability: { fault_states: ['verifier_error'], penalty: 'on an error, 50% of stake_amount forfeited from staked balance.', settlement: 'anchor-engine + verifier_whitelist' },
76
+ gate: `reputation ≥ ${num('governance_onboarding.verifier_min_reputation', 90)} (param) + live WebAuthn per vote (iron-rule)`,
77
+ status: 'live',
78
+ enforced_by: 'L1-2 anchor-engine (fee split) + verifier_whitelist (stake/forfeit)',
79
+ },
80
+ {
81
+ role: 'arbitrator',
82
+ enters_as: 'adjudicates disputes (objective-claimed non-acceptance, fault contests)',
83
+ earns: { source: 'a per-dispute arbitration fee (today: 50% of orderAmount×1%, paid by the loser). Compensated, NOT fee-maximizing — pay must stay independent of the ruling; see RFC-013 (decouples pay from ruling direction + fixes the latent "rule against who can pay" bias).', rfc: `${GH}/docs/rfcs/RFC-013-arbitrator-compensation-independence.md` },
84
+ collateral: { required: false, model: 'reputation-bound rather than stake-bound; mis-adjudication damages reputation.' },
85
+ liability: { note: 'accountable via reputation + audit log; iron-rule human presence required.' },
86
+ gate: `reputation ≥ ${num('governance_onboarding.arbitrator_min_reputation', 95)} (param) + live WebAuthn per ruling (iron-rule)`,
87
+ status: 'live',
88
+ enforced_by: 'L3-1 dispute-engine + governance onboarding gates',
89
+ },
90
+ {
91
+ role: 'skill_author',
92
+ enters_as: 'publishes a knowledge skill to the skill market; earns on sales',
93
+ earns: { source: 'sale price minus protocol fee', skill_fee_rate: num('skill_fee_rate', 0.05), payout_note: 'author nets price × (1 − skill_fee_rate). Independent revenue stream — NOT routed into commission/PV.' },
94
+ collateral: { required: false },
95
+ liability: { note: 'subject to skill-market review + meta-rules; refunds per market policy.' },
96
+ gate: 'api_key (authenticated_write) + skill-market review',
97
+ status: 'live',
98
+ enforced_by: 'skill-market engine + admin review',
99
+ },
100
+ {
101
+ role: 'insurer',
102
+ enters_as: '(generic third-party underwriter) prices & carries order risk for a premium',
103
+ earns: { source: 'order insurance premium', order_insurance_rate: num('order_insurance_rate', 0.01), today: 'buyer opt-in premium accrues at the protocol rate; there is NOT yet a generic external-underwriter market.' },
104
+ collateral: { required: true, model: 'an external underwriter would post collateral backing its book — to be defined.' },
105
+ liability: { note: 'would pay out covered losses; bound by collateral.' },
106
+ gate: 'TBD (own RFC)',
107
+ status: 'scaffolded',
108
+ spec: `${GH}/docs/rfcs/RFC-012-external-risk-underwriter.md`,
109
+ why_not_live: 'No real underwriters yet. Per the enters-core test (≥N independent integrators × cross-party trust × not reconstructable), generic underwriter onboarding is specified in RFC-012 (collateralized risk-cover bound to RFC-008; NOT licensed insurance) and gated on real demand — we do not pre-build the interface.',
110
+ enforced_by: 'order_insurance_rate premium (live) + insurance_cap fund backstop (live); generic underwriter onboarding: to-build',
111
+ },
112
+ ],
113
+ human_gates: 'arbitrate / verifier-vote / large withdraw require a live WebAuthn ceremony regardless of scope (iron-rule).',
114
+ references: {
115
+ economic_model: `${BASE}/docs/ECONOMIC-MODEL.md`, // 协议自服务(公开经济模型)
116
+ rfc_008: `${GH}/docs/rfcs/RFC-008-merchant-cost-collateral.md`,
117
+ rfc_011: `${GH}/docs/rfcs/RFC-011-agent-native-integration-contract.md`,
118
+ rfc_012_underwriter: `${GH}/docs/rfcs/RFC-012-external-risk-underwriter.md`,
119
+ liability_tiers: 'https://webaz.xyz/.well-known/webaz-integration.json',
120
+ },
121
+ };
122
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Endpoint → action-scope classifier — the WRITE-boundary spine (元规则 #3 + #1115 default-deny).
3
+ * RFC-011 §② (capability matrix): the SAME declarative rules that ENFORCE the boundary at runtime
4
+ * are also SERIALIZED (capabilityMatrix) and published live, so an integrator's agent reads exactly
5
+ * what the protocol enforces — doc=code, zero drift. Extracted from server.ts unchanged in behaviour
6
+ * (locked by tests/test-endpoint-actions.ts, which diffs this against the legacy if-chain).
7
+ *
8
+ * Model: GET reads are open (except the sensitive cross-user read scopes below). Every WRITE either
9
+ * maps to a named action-scope token (agent must declare that scope, or hold a Passkey, or declare '*'),
10
+ * or is on the SAFE list (write allowed without a declared scope — bootstrap/auth/low-value self-state),
11
+ * or falls through to the generic 'write' token (default-deny: new sensitive writes are gated by default).
12
+ */
13
+ import { SOFTWARE_VERSION, CONTRACT_VERSION } from '../version.js';
14
+ /** Ordered write-action rules. Order matters (first match wins). Mirrors the legacy if-chain exactly. */
15
+ export const WRITE_RULES = [
16
+ { method: 'POST', exact: '/api/orders', action: 'place_order' },
17
+ { method: 'POST_PUT', re: /^\/api\/products(\/[^/]+)?$/, action: 'list_product' },
18
+ { method: 'POST', re: /^\/api\/orders\/[^/]+\/(accept|ship|deliver|pickup|transit)/, action: 'fulfill' },
19
+ { method: 'POST', re: /^\/api\/orders\/[^/]+\/confirm/, action: 'confirm_order' },
20
+ { method: 'POST', re: /^\/api\/claim-tasks\/[^/]+\/vote/, action: 'vote' },
21
+ { method: 'POST', re: /^\/api\/disputes\/[^/]+\/arbitrate/, action: 'arbitrate' },
22
+ { method: 'POST', re: /^\/api\/disputes\/[^/]+\/respond/, action: 'dispute_respond' },
23
+ { method: 'POST', re: /^\/api\/charity\/fund\/donate/, action: 'donate' },
24
+ { method: 'POST', re: /^\/api\/(wishes|charity)/, action: 'charity' },
25
+ { method: 'POST', re: /^\/api\/shareables/, action: 'share' },
26
+ { method: 'POST', re: /^\/api\/conversations/, action: 'chat' },
27
+ { method: 'POST', exact: '/api/skills', action: 'list_skill' },
28
+ { method: 'POST', re: /^\/api\/rfqs/, action: 'rfq' },
29
+ { method: 'POST', re: /^\/api\/auctions\/[^/]+\/bid/, action: 'bid' },
30
+ // #1115 P0:花钱/价值写纳入问责门(与 place_order 同档)
31
+ { method: 'POST', re: /^\/api\/skill-market\/[^/]+\/purchase/, action: 'purchase' },
32
+ { method: 'POST', re: /^\/api\/secondhand\/[^/]+\/order/, action: 'buy_secondhand' },
33
+ { method: 'POST', re: /^\/api\/group-buys\/[^/]+\/join/, action: 'group_buy_join' },
34
+ // #1115 P1:写 PII(收货地址)。原为 (addresses OR profile/default-address);拆两条等价规则,顺序保持。
35
+ { method: 'WRITE', re: /^\/api\/addresses(\/|$)/, action: 'set_address' },
36
+ { method: 'WRITE', exact: '/api/profile/default-address', action: 'set_address' },
37
+ // #1115 P2:钱包写统一 'wallet'(withdraw 另在 handler 强制 Passkey 铁律)
38
+ { method: 'WRITE', re: /^\/api\/wallet\//, action: 'wallet' },
39
+ // #1115 P2:profile PII/身份/接管向量子集 'set_profile'(其余 profile 自助写在 SAFE)
40
+ { method: 'WRITE', re: /^\/api\/profile\/(bind-email|confirm-email|change-handle|change-name|set-location|clear-location)$/, action: 'set_profile' },
41
+ ];
42
+ /** SAFE writes — allowed without a declared scope (bootstrap/auth/self-bound-gate/low-value self-state). */
43
+ export const SAFE_WRITE = [
44
+ /^\/api\/(login|register)$/, /^\/api\/recover-key/, /^\/api\/webauthn\//,
45
+ /^\/api\/me\/agents\//,
46
+ /^\/api\/profile\/(switch-role|add-role|region|placement-pref|bind-placement|feed-visible|verify-password|set-password|remove-password)$/,
47
+ /^\/api\/build-feedback/, /^\/api\/build-tasks/, /^\/api\/admin\//,
48
+ /^\/api\/(public-ideas|error-report|mcp-telemetry|email-subscriptions|search-by-link|feedback)(\/|$)/,
49
+ /^\/api\/cart$/, /^\/api\/cart\/(?!checkout)[^/]+$/,
50
+ /^\/api\/wishlist/, /^\/api\/products\/[^/]+\/waitlist$/,
51
+ /^\/api\/notifications\/read$/, /^\/api\/announcements\/[^/]+\/read$/,
52
+ /^\/api\/follows\//, /^\/api\/blocklist\//,
53
+ /^\/api\/checkin$/, /^\/api\/growth\/tasks\//, /^\/api\/tasks\/[^/]+\/claim$/,
54
+ /^\/api\/push\//, /^\/api\/auth\//,
55
+ /^\/api\/me\/(delete-cancel|notify-claim-tasks)/,
56
+ /^\/api\/peers\//, /^\/api\/signaling\//,
57
+ /^\/api\/product-share\/touch$/, /^\/api\/anchor\/[^/]+\/touch$/,
58
+ /^\/api\/reviews\//,
59
+ ];
60
+ function methodMatches(m, method) {
61
+ if (m === 'POST')
62
+ return method === 'POST';
63
+ if (m === 'POST_PUT')
64
+ return method === 'POST' || method === 'PUT';
65
+ return method !== 'GET'; // WRITE
66
+ }
67
+ /** Write-boundary classifier. Returns a named action-scope token, or 'write' (generic), or null (open). */
68
+ export function endpointToAction(method, path) {
69
+ if (method === 'GET')
70
+ return null;
71
+ for (const r of WRITE_RULES) {
72
+ if (!methodMatches(r.method, method))
73
+ continue;
74
+ if (r.exact !== undefined ? path === r.exact : r.re.test(path))
75
+ return r.action;
76
+ }
77
+ if (SAFE_WRITE.some(re => re.test(path)))
78
+ return null;
79
+ return 'write'; // 默认拒绝:其余写需问责
80
+ }
81
+ /** Sensitive cross-user READ scopes (Phase 3b B1) — only constrains *declared* agents; humans/'*'/undeclared exempt. */
82
+ export const READ_RULES = [
83
+ { re: /^\/api\/nearby/, scope: 'search' }, // 雷达扫描(地理聚合)
84
+ { re: /^\/api\/search/, scope: 'search' }, // 模糊搜索深翻页
85
+ { re: /^\/api\/users\/[^/]+\//, scope: 'profile' }, // 他人主页/信誉/内容流(枚举剽窃向)
86
+ ];
87
+ export function endpointToReadAction(path) {
88
+ for (const r of READ_RULES)
89
+ if (r.re.test(path))
90
+ return r.scope;
91
+ return null;
92
+ }
93
+ /** Serialize the live boundary as the agent-readable capability matrix (RFC-011 §②). doc=code. */
94
+ export function capabilityMatrix() {
95
+ return {
96
+ contract_version: CONTRACT_VERSION,
97
+ software_version: SOFTWARE_VERSION,
98
+ model: 'default-deny writes. GET reads are open except the sensitive cross-user read scopes. Every write either maps to a named action-scope token (the agent must declare that scope on its api_key, OR hold a Passkey, OR declare "*"), or is SAFE (write allowed unscoped), or falls through to the generic "write" token.',
99
+ write_actions: WRITE_RULES.map(r => ({
100
+ action: r.action,
101
+ method: r.method === 'POST' ? 'POST' : r.method === 'POST_PUT' ? 'POST|PUT' : 'POST|PUT|PATCH|DELETE',
102
+ match: r.exact !== undefined ? `=${r.exact}` : r.re.source,
103
+ })),
104
+ safe_write_unscoped: SAFE_WRITE.map(re => re.source),
105
+ read_scopes: READ_RULES.map(r => ({ scope: r.scope, match: r.re.source })),
106
+ notes: {
107
+ passkey_exempt: 'A Passkey-bound human is exempt from scope-declaration (但仍受铁律真人门约束).',
108
+ iron_rule: 'arbitrate / vote / agent_revoke / delete_passkey / large withdraw require a live WebAuthn ceremony regardless of declared scope (CHARTER §4 iron-rule).',
109
+ undeclared: 'An agent that has NOT declared any actions and has no Passkey is denied any named/ generic write (AGENT_SCOPE_UNDECLARED).',
110
+ },
111
+ };
112
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * RFC-011 §① 实体字典 —— agent 可读的"数据是什么 + 状态机 + 哪些可验证"。
3
+ * - 状态机:从 transitions.ts 生成(doc=code,零漂移)。
4
+ * - 字段含义:authored(schema 只有类型);【保守白名单】—— 只列无争议公开字段,
5
+ * PII(收货地址/recipient_code)与身份/内部字段明确【排除】,全记录走 party-gated /api/orders/:id。
6
+ * 白名单是读边界 + 元规则#3 的安全决策,宁缺勿滥。
7
+ * coverage/lock 由 tests/test-order-lifecycle-contract.ts 守(每状态有含义 + 每转移序列化 + 无 PII 泄漏)。
8
+ */
9
+ import { orderLifecycleContract } from '../layer0-foundation/L0-2-state-machine/transitions.js';
10
+ import { SOFTWARE_VERSION, CONTRACT_VERSION } from '../version.js';
11
+ // order 实体【保守公开字段】+ 含义。刻意不含 PII / 身份 / 内部结算字段(见 pii_excluded)。
12
+ const ORDER_PUBLIC_FIELDS = [
13
+ { field: 'id', type: 'string', meaning: '订单 ID / order id' },
14
+ { field: 'product_id', type: 'string', meaning: '商品/二手物品 ID / product (or secondhand item) id' },
15
+ { field: 'status', type: 'enum', meaning: '当前状态(见 lifecycle.states)/ current lifecycle state' },
16
+ { field: 'source', type: 'enum', meaning: "'shop' | 'secondhand' —— 渠道 / order channel" },
17
+ { field: 'quantity', type: 'integer', meaning: '数量 / quantity' },
18
+ { field: 'unit_price', type: 'number', meaning: '单价(下单快照)/ unit price (order-time snapshot)' },
19
+ { field: 'total_amount', type: 'number', meaning: '买家支付总额 / total paid by buyer' },
20
+ { field: 'fulfillment_mode', type: 'enum', meaning: "'shipping' | 'in_person' —— 履约方式 / fulfilment mode" },
21
+ { field: 'created_at', type: 'datetime', meaning: '下单时间 / created' },
22
+ { field: 'updated_at', type: 'datetime', meaning: '最后更新 / last updated' },
23
+ { field: 'accept_deadline', type: 'datetime', meaning: '卖家接单截止(超时→fault_seller)/ seller-accept deadline' },
24
+ { field: 'ship_deadline', type: 'datetime', meaning: '发货截止 / ship deadline' },
25
+ { field: 'delivery_deadline', type: 'datetime', meaning: '投递截止 / delivery deadline' },
26
+ { field: 'confirm_deadline', type: 'datetime', meaning: '买家确认截止(超时→自动确认)/ buyer-confirm deadline' },
27
+ { field: 'stake_backing', type: 'number', meaning: 'RFC-008 该单赔付背书额(0=起步免赔付)/ per-order stake backing (0 = bootstrap no-payout)' },
28
+ { field: 'decline_reason_code', type: 'enum', meaning: 'RFC-007 卖家拒单原因码 / seller decline reason code' },
29
+ { field: 'declined_at', type: 'datetime', meaning: 'RFC-007 拒单时间 / decline timestamp' },
30
+ ];
31
+ // 明确【不公开】—— 安全/隐私边界声明(让集成方知道这些存在但只对当事人,经 party-gated 端点)
32
+ const ORDER_PII_OR_PRIVATE = [
33
+ 'shipping_address (PII — 元规则#3)', 'recipient_code (PII)', 'buyer_id / seller_id / logistics_id (party identities — 经 /api/orders/:id 对当事方可见)',
34
+ 'escrow_amount / settled_* / commission internals (内部结算)',
35
+ ];
36
+ // product 实体【保守公开字段】—— 买家/agent 选购 + 验证所需,排除内部审核/排序内参。
37
+ const PRODUCT_PUBLIC_FIELDS = [
38
+ { field: 'id', type: 'string', meaning: '商品 ID (prd_xxx) / product id' },
39
+ { field: 'seller_id', type: 'string', meaning: '卖家 ID(公开,店铺主体)/ seller id (public — the shop)' },
40
+ { field: 'title', type: 'string', meaning: '标题(按 buyer 语言回落)/ title (localized w/ fallback)' },
41
+ { field: 'description', type: 'string', meaning: '描述 / description' },
42
+ { field: 'price', type: 'number', meaning: '价格(以 protocol-status 报价币种计)/ price' },
43
+ { field: 'currency', type: 'string', meaning: '币种 / currency' },
44
+ { field: 'stock', type: 'integer', meaning: '库存(下单 expected_price/stock 守卫见 ②)/ stock' },
45
+ { field: 'category', type: 'string', meaning: '类目 / category' },
46
+ { field: 'images', type: 'json', meaning: '图片路径数组 / image paths (JSON array)' },
47
+ { field: 'specs', type: 'json', meaning: '规格键值 / spec key-values' },
48
+ { field: 'estimated_days', type: 'json', meaning: '预计时效 / estimated fulfilment days' },
49
+ { field: 'commission_rate', type: 'number', meaning: '分享佣金率(推广方可得)/ promoter commission rate' },
50
+ { field: 'stake_amount', type: 'number', meaning: 'RFC-008 卖家为该品质押额(买家保护信号)/ seller stake on this product' },
51
+ { field: 'completion_count', type: 'integer', meaning: '累计成交数(社会证明)/ completed sales' },
52
+ { field: 'total_likes', type: 'integer', meaning: '点赞数 / likes' },
53
+ { field: 'content_hash', type: 'string', meaning: '商品详情 canonical JSON 的 sha256(可验,见 ⑤)/ sha256 of canonical detail (verifiable)' },
54
+ { field: 'content_signature', type: 'string', meaning: '卖家对 content_hash 的签名(P2P 模式自证)/ seller signature over content_hash' },
55
+ { field: 'status', type: 'enum', meaning: "'active' | 'warehouse' | 'deleted' —— 上架状态 / listing status" },
56
+ { field: 'created_at', type: 'datetime', meaning: '创建 / created' },
57
+ { field: 'updated_at', type: 'datetime', meaning: '更新 / updated' },
58
+ ];
59
+ const PRODUCT_PRIVATE_OR_INTERNAL = [
60
+ 'claim_loss_count (内部审核:声明不实累计,≥3 自动下架)',
61
+ 'internal ranking inputs (last_sold_at / unique_sharer_count 等用于排序,非契约字段)',
62
+ 'cost / margin (协议不存卖家成本)',
63
+ ];
64
+ // dispute 实体 = 【裁决后公开脱敏版 dispute_cases】(非私域 disputes)。amount 分桶、argument 脱敏、buyer 身份不外露。
65
+ const DISPUTE_PUBLIC_FIELDS = [
66
+ { field: 'id', type: 'string', meaning: '公开判例 ID (dcase_xxx) / public case id' },
67
+ { field: 'order_id', type: 'string', meaning: '关联订单 / order id' },
68
+ { field: 'product_id', type: 'string', meaning: '关联商品(按品查判例)/ product id' },
69
+ { field: 'seller_id', type: 'string', meaning: '卖家 ID(公开,信誉相关)/ seller id (public)' },
70
+ { field: 'category_tag', type: 'enum', meaning: '物流/质量/描述不符/售后/拒收/其他 / dispute category' },
71
+ { field: 'winner', type: 'enum', meaning: "'buyer' | 'seller' | 'split' | 'dismissed' —— 裁决结果 / outcome" },
72
+ { field: 'resolution', type: 'string', meaning: '简短人读判决(如"全额退款")/ short human-readable resolution' },
73
+ { field: 'amount_bucket', type: 'enum', meaning: "'0-100' | '100-500' | '500-2000' | '2000+' —— 金额【分桶】非精确(隐私)/ bucketed amount, not exact" },
74
+ { field: 'buyer_argument', type: 'string', meaning: '买家陈述(脱敏)/ buyer statement (redacted)' },
75
+ { field: 'seller_argument', type: 'string', meaning: '卖家陈述(脱敏)/ seller statement (redacted)' },
76
+ { field: 'ruling_text', type: 'string', meaning: '仲裁员判决书(脱敏)/ arbitrator ruling (redacted)' },
77
+ { field: 'fairness_yes', type: 'integer', meaning: '社区"公正"投票数 / community fairness up-votes' },
78
+ { field: 'fairness_no', type: 'integer', meaning: '社区"不公"投票数 / fairness down-votes' },
79
+ { field: 'published_at', type: 'datetime', meaning: '公开发布时间 / published' },
80
+ ];
81
+ const DISPUTE_PRIVATE_OR_INTERNAL = [
82
+ 'buyer_id (注释明示"仅内部使用,不外露")',
83
+ 'dispute_id (原始 disputes.id,内部追溯)',
84
+ 'live case 全文(证据/PII/未脱敏陈述)走 party + arbitrator-gated GET /api/disputes/:id —— dispute_cases 是【裁决后】脱敏快照',
85
+ ];
86
+ export function buildEntityDictionary() {
87
+ return {
88
+ contract_version: CONTRACT_VERSION,
89
+ software_version: SOFTWARE_VERSION,
90
+ note: 'RFC-011 §① machine-readable entity dictionary (order / product / dispute). Order lifecycle is generated from the protocol state machine (doc=code). Field lists are a conservative PUBLIC subset — PII/identity/internal fields are excluded and only reachable by parties via party-gated endpoints. The public "dispute" entity is the redacted post-ruling dispute_cases; the live case is party+arbitrator-gated. Full read access follows the capability matrix (§② /.well-known/webaz-capabilities.json). Intent→action routing: goal index (§① /.well-known/webaz-goals.json).',
91
+ entities: {
92
+ order: {
93
+ kind: 'trade',
94
+ public_fields: ORDER_PUBLIC_FIELDS,
95
+ pii_excluded: ORDER_PII_OR_PRIVATE,
96
+ full_record: 'GET /api/orders/:id (party-gated)',
97
+ lifecycle: orderLifecycleContract(),
98
+ verifiable: {
99
+ state_changes: 'observable via GET /api/agent/events (§⑥), integrity-verifiable via GET /api/orders/:id/chain (§⑤)',
100
+ },
101
+ },
102
+ product: {
103
+ kind: 'listing',
104
+ public_fields: PRODUCT_PUBLIC_FIELDS,
105
+ pii_excluded: PRODUCT_PRIVATE_OR_INTERNAL,
106
+ full_record: 'GET /api/products/:id (public for active listings)',
107
+ list: 'GET /api/search (strict match — see goal index / §② read scope "search")',
108
+ verifiable: {
109
+ detail: 'content_hash = sha256(canonical detail); content_signature = seller signature (P2P self-attestation). See verifiability index (§⑤) external_anchor for real-world ownership/authenticity anchoring.',
110
+ },
111
+ },
112
+ dispute: {
113
+ kind: 'judicial',
114
+ note: 'PUBLIC entity = dispute_cases — the post-ruling, redacted snapshot. The live case (disputes) with full evidence/PII is party + arbitrator-gated.',
115
+ public_fields: DISPUTE_PUBLIC_FIELDS,
116
+ pii_excluded: DISPUTE_PRIVATE_OR_INTERNAL,
117
+ full_record: 'GET /api/disputes/cases/:case_id (public, redacted)',
118
+ list: 'GET /api/disputes/cases · GET /api/disputes/cases/by-product/:product_id',
119
+ live_case: 'GET /api/disputes/:id (party + arbitrator-gated; full evidence, not redacted)',
120
+ lifecycle: 'dispute transitions are part of the order lifecycle (see entities.order.lifecycle: disputed / fault_* / resolved_* / refunded_* states)',
121
+ },
122
+ },
123
+ goal_index: 'GET /.well-known/webaz-goals.json — intent → capability action (§②) + endpoint + MCP tool + PWA page (self-routing).',
124
+ };
125
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * RFC-011 §① 目标索引 —— intent → 怎么做:能力 action(②)+ REST endpoint + MCP 工具 + PWA 页。
3
+ *
4
+ * 泛化自 MCP `webaz_info.search_routing`(#1072,zh/搜索/MCP-only)→ 协议级、非 MCP 集成方也能自路由。
5
+ *
6
+ * doc=code 防漂移锁(关键):每条目标的 `action` 要么是 `open`(开放读),要么必须是
7
+ * capability matrix(②)里真实存在的 token —— write_action 或 read_scope。
8
+ * `assertGoalActionsValid()` + tests/test-goal-index.ts 守门:引用不存在的能力 = 测试红。
9
+ * 这样 goal-index 永远和 enforced 边界一致,不会指向幽灵能力。
10
+ */
11
+ import { SOFTWARE_VERSION, CONTRACT_VERSION } from '../version.js';
12
+ import { capabilityMatrix } from './endpoint-actions.js';
13
+ const GOALS = [
14
+ // ── discover / buy ──
15
+ { goal: 'Find a specific product by title/SKU/exact desc', when: 'buyer knows what they want (strict match)', action: 'open', endpoint: 'GET /api/search?query=...', mcp_tool: 'webaz_search', pwa: '#buy', see: '② read scope "search"', notes: 'STRICT — no fuzzy fallback; 0 hits → guide user to #discover (fuzzy is a human action, not agent-automated).' },
16
+ { goal: 'Match a pasted external link (taobao/douyin/xhs/jd/...)', when: 'buyer pastes an off-site product URL', action: 'open', endpoint: 'GET /api/search?external_link=...', mcp_tool: 'webaz_search', pwa: '#buy', notes: 'matches the anchor registry product fingerprint.' },
17
+ { goal: "Browse what's popular near me / same city", when: 'geo discovery, no keyword', action: 'search', endpoint: 'GET /api/nearby', mcp_tool: 'webaz_nearby', pwa: '#nearby', see: '② read scope "search"', notes: 'k-anonymity ≥3.' },
18
+ { goal: 'Find used / pre-owned / secondhand items', when: 'pre-owned, separate space from new catalog', action: 'open', endpoint: 'GET /api/secondhand', mcp_tool: 'webaz_secondhand', pwa: '#secondhand', notes: 'webaz_search does NOT return secondhand.' },
19
+ { goal: 'Verify a price before buying', when: 'BEFORE every purchase', action: 'open', endpoint: 'GET /api/products/:id (+ verify)', mcp_tool: 'webaz_verify_price', pwa: '#buy', notes: 'defeats flash-sale/hidden-fee race; protocol only liable for the verified T0 price.' },
20
+ { goal: 'Place an order (buy a catalog product)', when: 'buyer commits to purchase', action: 'place_order', endpoint: 'POST /api/orders', mcp_tool: 'webaz_place_order', pwa: '#buy', see: '① entity order · ⑧ value flow', notes: 'pass expected_price (T0 guard, 409 on drift).' },
21
+ { goal: 'Buy a secondhand item', when: 'order a pre-owned listing', action: 'buy_secondhand', endpoint: 'POST /api/secondhand/:id/order', mcp_tool: 'webaz_secondhand', pwa: '#secondhand' },
22
+ { goal: 'Buy a knowledge skill', when: 'purchase a prompt/template/checklist', action: 'purchase', endpoint: 'POST /api/skill-market/:id/purchase', mcp_tool: 'webaz_skill_market', pwa: '#skill-market', notes: 'content market — distinct from webaz_skill behavior plugins.' },
23
+ { goal: 'Bid in an auction', when: 'time-windowed price discovery on listed item', action: 'bid', endpoint: 'POST /api/auctions/:id/bid', mcp_tool: 'webaz_bid', pwa: '#auctions', notes: 'anti-snipe time extension.' },
24
+ { goal: 'Post a buy request (RFQ) for sellers to quote', when: 'no good match / bulk / custom / wants competing quotes', action: 'rfq', endpoint: 'POST /api/rfqs', mcp_tool: 'webaz_rfq', pwa: '#rfqs', notes: 'reverse match — buyer posts need + 1% stake.' },
25
+ // ── sell / fulfill ──
26
+ { goal: 'List / update a product', when: 'seller publishes, edits, delists own listing', action: 'list_product', endpoint: 'POST|PUT /api/products', mcp_tool: 'webaz_list_product', pwa: '#me', see: '① entity product', notes: 'system suggests stake ~15% of price (buyer protection).' },
27
+ { goal: 'Fulfill an order (accept / ship / deliver / pickup)', when: 'seller or logistics advances fulfilment', action: 'fulfill', endpoint: 'POST /api/orders/:id/{accept|ship|deliver|pickup|transit}', mcp_tool: 'webaz_update_order', pwa: '#me', see: '① order lifecycle · ⑦ liability', notes: 'missing a deadline → auto fault (see order lifecycle).' },
28
+ { goal: 'Confirm receipt (buyer closes the order)', when: 'buyer received the goods', action: 'confirm_order', endpoint: 'POST /api/orders/:id/confirm', mcp_tool: 'webaz_update_order', pwa: '#me', notes: 'auto-confirm on confirm_deadline timeout.' },
29
+ // ── dispute / verify ──
30
+ { goal: 'Respond to a dispute as a party', when: 'a counterparty opened a dispute on your order', action: 'dispute_respond', endpoint: 'POST /api/disputes/:id/respond', mcp_tool: 'webaz_dispute', pwa: '#me', see: '① entity dispute · ⑦ liability' },
31
+ { goal: 'Look up public dispute precedents', when: 'assess a seller / understand likely ruling', action: 'open', endpoint: 'GET /api/disputes/cases (+ /by-product/:id)', mcp_tool: 'webaz_dispute', pwa: '#disputes', see: '① entity dispute', notes: 'redacted post-ruling cases; amount is bucketed.' },
32
+ { goal: 'Verify an agent passport / external anchor / AP2 mandate', when: 'check a counterparty/data is genuine', action: 'open', endpoint: 'GET /.well-known/webaz-verifiability.json', mcp_tool: null, pwa: '(n/a)', see: '⑤ verifiability index', notes: 'offline-verifiable where signed; order-chain is integrity-only.' },
33
+ // ── participate / social ──
34
+ { goal: 'Become a value participant (earn/pay/stake)', when: 'integrate as seller/logistics/verifier/insurer/etc.', action: 'open', endpoint: 'GET /.well-known/webaz-economic.json', mcp_tool: null, pwa: '(n/a)', see: '⑧ economic participation', notes: 'roles + live rates + collateral + conserved liability.' },
35
+ { goal: 'Communicate with a trade counterparty', when: 'ask seller a question / coordinate an order', action: 'chat', endpoint: 'POST /api/conversations', mcp_tool: 'webaz_chat', pwa: '#messages', notes: 'every message attaches to a trade context — not general LLM chat.' },
36
+ { goal: 'Share / refer a product for commission', when: 'promote a listing; attributed sales pay commission', action: 'share', endpoint: 'POST /api/shareables', mcp_tool: 'webaz_shareables', pwa: '#me', see: '⑧ promoter role' },
37
+ { goal: 'Publish or fund a charity wish / community fund', when: 'community mutual-aid', action: 'charity', endpoint: 'POST /api/wishes · POST /api/charity', mcp_tool: 'webaz_charity', pwa: '#charity', notes: 'distinct from place_order donation_pct.' },
38
+ { goal: 'Donate to the community fund', when: 'contribute to the shared fund', action: 'donate', endpoint: 'POST /api/charity/fund/donate', mcp_tool: 'webaz_charity', pwa: '#charity' },
39
+ // ── self state ──
40
+ { goal: 'Set a shipping address (PII write)', when: 'before a shipped order', action: 'set_address', endpoint: 'POST /api/addresses', mcp_tool: 'webaz_default_address', pwa: '#me', see: '② write_action set_address (元规则#3 PII gate)' },
41
+ ];
42
+ /** doc=code 锁:返回非法引用(action 既非 'open' 也不在 ② capability matrix token 集)的目标,空数组=自洽。 */
43
+ export function invalidGoalActions() {
44
+ const m = capabilityMatrix();
45
+ const valid = new Set(['open']);
46
+ for (const w of m.write_actions)
47
+ valid.add(w.action);
48
+ for (const r of m.read_scopes)
49
+ valid.add(r.scope);
50
+ return GOALS.filter(g => !valid.has(g.action)).map(g => ({ goal: g.goal, action: g.action }));
51
+ }
52
+ export function buildGoalIndex() {
53
+ return {
54
+ contract_version: CONTRACT_VERSION,
55
+ software_version: SOFTWARE_VERSION,
56
+ note: 'RFC-011 §① goal index — maps an integrator agent\'s INTENT to the capability action (§②), the REST endpoint, the MCP tool, and the PWA page, so a (non-MCP) agent can self-route from goal to action. Each goal.action is "open" (public read) or a real token from the capability matrix (§② /.well-known/webaz-capabilities.json) — validated by tests/test-goal-index.ts (doc=code, no phantom capabilities). To exercise a write action, declare its scope per docs/INTEGRATOR.md (§③).',
57
+ capability_matrix: 'https://webaz.xyz/.well-known/webaz-capabilities.json',
58
+ goals: GOALS,
59
+ };
60
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * RFC-011 总入口 /.well-known/webaz-integration.json —— 集成方 agent 一次 fetch 拿到整份契约导航。
3
+ * 按【集成方旅程】组织,每维度指向 live 端点 + 诚实标 status(✅live / 🚧 to-build)。
4
+ * 它只【链接 + 导航】,不复制内容(各维度的真身是各自的 live 端点 / 文档),所以不漂移。
5
+ */
6
+ import { SOFTWARE_VERSION, CONTRACT_VERSION } from '../version.js';
7
+ const BASE = 'https://webaz.xyz';
8
+ const GH = 'https://github.com/seasonsagents-art/webaz/blob/main';
9
+ // 集成必需文档(规则 + onboarding)由协议自身 serve —— 外部 agent 必须能读到它被约束的规则,
10
+ // 不能指向私有 repo 的 GitHub 链接(对外 404)。RFC/审计是 provenance,留 GH(随 repo 公开解锁)。
11
+ const DOCS = `${BASE}/docs`;
12
+ export function buildIntegrationContract() {
13
+ return {
14
+ name: 'WebAZ Agent-Native Integration Contract',
15
+ contract_version: CONTRACT_VERSION,
16
+ software_version: SOFTWARE_VERSION,
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
+ // 外部 agent 的第一道问题:"我怎么从匿名读升到能写?" —— 入口必须自答(不依赖 GitHub)。
19
+ access: {
20
+ anonymous_read: 'no credential needed — public GET endpoints + the well-known surfaces below.',
21
+ get_api_key: 'an api_key requires a REAL HUMAN to register at https://webaz.xyz (invite code + Passkey). Agents CANNOT self-register — this is the accountability root ("every agent has an accountable human behind it"). After the human gets the key, set it as the agent\'s bearer token.',
22
+ then: 'declare your write scope at POST /api/me/agents/declarations (scope tokens from the capability matrix §②); a Passkey-bound human is exempt from scope declaration. See onboarding (③).',
23
+ tiers: 'anonymous_read → authenticated_write (api_key→passport) → value_participant (collateral). See liability_tiers below.',
24
+ },
25
+ // 集成方旅程 —— 每步指向背后的维度
26
+ journey: [
27
+ { step: 1, name: 'discover', uses: ['this document'] },
28
+ { step: 2, name: 'understand', uses: ['semantics ①'] },
29
+ { step: 3, name: 'authorize', uses: ['authz ③', 'liability ⑦'] },
30
+ { step: 4, name: 'know_limits', uses: ['boundary ②'] },
31
+ { step: 5, name: 'act', uses: ['boundary ②', 'authz ③'] },
32
+ { step: 6, name: 'stay_in_sync', uses: ['eventing ⑥'] },
33
+ { step: 7, name: 'verify', uses: ['verifiability ⑤'] },
34
+ { step: 8, name: 'participate', uses: ['economic ⑧'] },
35
+ ],
36
+ dimensions: {
37
+ '①_semantics': { status: 'live', entity_dictionary: `${BASE}/.well-known/webaz-entities.json`, entities: ['order', 'product', 'dispute'], goal_index: `${BASE}/.well-known/webaz-goals.json` },
38
+ '②_boundary': { status: 'live', capability_matrix: `${BASE}/.well-known/webaz-capabilities.json`, negative_space: `${BASE}/.well-known/webaz-negative-space.json` },
39
+ '③_authz': { status: 'live', onboarding: `${DOCS}/INTEGRATOR.md`, scope_declare: `${BASE}/api/me/agents/declarations`, passport: `${BASE}/api/me/agents/:apiKeyPrefix/passport`, scope_tokens: 'from capability_matrix.write_actions' },
40
+ '④_versioning': { status: 'live', manifest: `${BASE}/.well-known/webaz-protocol.json`, change_feed: `${BASE}/api/agent/changes` },
41
+ '⑤_verifiability': { status: 'live', index: `${BASE}/.well-known/webaz-verifiability.json`, passport_did: `${BASE}/.well-known/did.json`, anchor_verify: `${BASE}/api/external-anchors/:id/verify-sig`, order_chain: `${BASE}/api/orders/:id/chain (party-gated, integrity-chain not signature)` },
42
+ '⑥_eventing': { status: 'live', event_stream: `${BASE}/api/agent/events?since=<cursor>`, transport: 'pull (cursor), not push; party-gated; rowid cursor; signed hash-chain' },
43
+ '⑦_liability': { status: 'live', terms: `${DOCS}/INTEGRATOR.md`, accountability: 'api_key → user → passport (5 metrics + custodian); enforced: scope-403 / rate-strike / cross-user-cap / dispute-fault → 3-strike block; appeal: /api/me/agents/strikes/:id/appeal' },
44
+ '⑧_economic': { status: 'live', index: `${BASE}/.well-known/webaz-economic.json`, note: 'value-participant roles × earns/collateral/liability; rates read live from protocol_params (doc=code). Generic third-party insurer onboarding marked scaffolded (own RFC + enters-core gate).' },
45
+ },
46
+ negative_space: {
47
+ forbidden: ['rebuild cross-user graph / aggregate cross-user data (meta-rule #3)', 'resell user data', 'impersonate a user or the protocol', 'exceed declared scope'],
48
+ enforced_by: ['default-deny write boundary (capability_matrix)', 'cross-user read cap', 'accountability strikes + api-key block'],
49
+ meta_rules: `${DOCS}/META-RULES-FULL.md`,
50
+ },
51
+ liability_tiers: [
52
+ { tier: 'anonymous_read', net: 'outside accountability net', caveat: 'public/Schema.org reads; caveat-emptor; no writes' },
53
+ { tier: 'authenticated_write', net: 'in net via api_key→passport', liability: 'responsible party; misuse → strikes/block' },
54
+ { tier: 'value_participant', net: 'in net + collateral-bound', liability: 'highest; conserved + collateral/reputation-backed (⑧/RFC-008)' },
55
+ ],
56
+ 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.',
57
+ iron_rule: 'arbitrate / vote / agent_revoke / delete_passkey / large withdraw require a live WebAuthn ceremony regardless of declared scope.',
58
+ references: {
59
+ rfc_011: `${GH}/docs/rfcs/RFC-011-agent-native-integration-contract.md`,
60
+ audit: `${GH}/docs/AGENT-NATIVE-INTEGRATION-AUDIT.md`,
61
+ manifest: `${BASE}/.well-known/webaz-protocol.json`,
62
+ },
63
+ };
64
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Enforced rate/cap tables —— 单一真相源(doc=code)。
3
+ * server.ts 的 enforcer(getAgentRateCap / checkMassActionCap / checkCrossUserReadCap / rateLimitOk)
4
+ * 与 RFC-011 §② negative-space 发布面【都从这里读】,所以发布的限额永远 == 真正 enforce 的限额,零漂移。
5
+ * 抽取自 server.ts 内联常量(行为不变,值逐一对应),同 endpoint-actions.ts(#126)抽取模式。
6
+ */
7
+ /** Per-agent 每分钟请求上限的【默认值】(治理可经 param `agent_rate_<level>_per_min` 覆盖,运行时实时读)。 */
8
+ export const AGENT_RATE_PER_MIN_DEFAULTS = {
9
+ legend: 1200,
10
+ quality: 600,
11
+ trusted: 300,
12
+ new: 120,
13
+ };
14
+ /** 跨用户读日 cap —— 每天可读的【不同】其他用户数(distinct other_user_id)。防枚举/扒数据;真人监护人也罩(只是更高)。 */
15
+ export const CROSS_USER_READ_DAILY_CAP = {
16
+ passkey_human: 300,
17
+ legend: 200,
18
+ quality: 100,
19
+ trusted: 60,
20
+ new: 30,
21
+ };
22
+ /** Mass-action(social-write)日 cap —— 防 spam / 信息轰炸。 */
23
+ export const MASS_ACTION_TYPES = ['chat', 'comment', 'share'];
24
+ export const MASS_ACTION_DAILY_CAPS = {
25
+ chat: { new: 30, trusted: 100, quality: 300, legend: 1000 },
26
+ comment: { new: 20, trusted: 60, quality: 150, legend: 500 },
27
+ share: { new: 10, trusted: 30, quality: 100, legend: 300 },
28
+ };
29
+ /** 公开/匿名端点的 per-IP 限流默认(rateLimitOk)。 */
30
+ export const IP_RATE_DEFAULT = { max: 200, window_ms: 60_000 };
@@ -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
+ }