@seasonkoh/webaz 0.1.18 → 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.
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +171 -56
- package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +23 -0
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +65 -2
- package/dist/layer1-agent/L1-1-mcp-server/server.js +46 -22
- package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +64 -5
- package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +4 -0
- package/dist/pwa/contract-fingerprint.js +46 -0
- package/dist/pwa/economic-participation.js +122 -0
- package/dist/pwa/endpoint-actions.js +112 -0
- package/dist/pwa/entity-dictionary.js +125 -0
- package/dist/pwa/goal-index.js +60 -0
- package/dist/pwa/integration-contract.js +64 -0
- package/dist/pwa/limits.js +30 -0
- package/dist/pwa/negative-space.js +64 -0
- package/dist/pwa/public/app.js +5 -0
- package/dist/pwa/public/docs/ECONOMIC-MODEL.md +287 -0
- package/dist/pwa/public/docs/INTEGRATOR.md +67 -0
- package/dist/pwa/public/docs/META-RULES-FULL.md +543 -0
- package/dist/pwa/public/i18n.js +4 -0
- package/dist/pwa/routes/build-feedback.js +3 -2
- package/dist/pwa/routes/disputes-write.js +68 -0
- package/dist/pwa/routes/orders-action.js +93 -1
- package/dist/pwa/routes/orders-create.js +7 -2
- package/dist/pwa/routes/orders-read.js +18 -0
- package/dist/pwa/routes/public-utils.js +131 -1
- package/dist/pwa/routes/webauthn.js +9 -1
- package/dist/pwa/server.js +69 -123
- package/dist/pwa/verifiability-index.js +63 -0
- package/dist/version.js +32 -0
- package/package.json +2 -1
|
@@ -2,6 +2,8 @@ import { generateId } from '../../layer0-foundation/L0-1-database/schema.js';
|
|
|
2
2
|
// RFC-006 不变量 1:建设贡献记入【独立】build_reputation 池,不再写交易 reputation_scores
|
|
3
3
|
//(旧:recordRepEvent('feedback_accepted') 会污染 verifier/arbitrator 准入,已隔离)。
|
|
4
4
|
import { creditBuildReputation, BUILD_POINTS } from '../L2-9-contribution/build-reputation-engine.js';
|
|
5
|
+
// RFC-006 桥(use→build 漏斗补全):采纳的 proposal → 自动建 build_task + 邀请提案人来认领。
|
|
6
|
+
import { createBuildTask } from '../L2-9-contribution/build-tasks-engine.js';
|
|
5
7
|
export const FB_TYPES = new Set(['ux_issue', 'bug', 'proposal']);
|
|
6
8
|
export const FB_SEVERITY = new Set(['low', 'annoying', 'blocking']);
|
|
7
9
|
export const FB_STATUS = new Set(['received', 'triaged', 'in_progress', 'resolved', 'declined', 'duplicate']);
|
|
@@ -38,6 +40,11 @@ export function initBuildFeedbackSchema(db) {
|
|
|
38
40
|
'ALTER TABLE build_feedback ADD COLUMN ai_summary TEXT', // 一句话摘要(给 maintainer 扫)
|
|
39
41
|
'ALTER TABLE build_feedback ADD COLUMN ai_models TEXT', // 参与的模型 + 是否一致
|
|
40
42
|
'ALTER TABLE build_feedback ADD COLUMN ai_triaged_at TEXT',
|
|
43
|
+
// RFC-006 桥:采纳的 proposal 被 promote 成 build_task 时,记其 task id(use→build 漏斗:反馈→协调)
|
|
44
|
+
'ALTER TABLE build_feedback ADD COLUMN promoted_task_id TEXT',
|
|
45
|
+
// RFC-004 体验补:受理时本可记功、但提交者【无 Passkey 锚点】而跳过 → 标记为待补发,
|
|
46
|
+
// 绑定 Passkey 后由 grantPendingAnchorCredits 追溯发放(把"静默不记分"变成"绑 Passkey 领取")。
|
|
47
|
+
'ALTER TABLE build_feedback ADD COLUMN credit_pending_anchor INTEGER DEFAULT 0',
|
|
41
48
|
]) {
|
|
42
49
|
try {
|
|
43
50
|
db.exec(stmt);
|
|
@@ -133,10 +140,33 @@ function parse(row) {
|
|
|
133
140
|
return { ...rest, scene };
|
|
134
141
|
}
|
|
135
142
|
export function listMyBuildFeedback(db, userId) {
|
|
136
|
-
const rows = db.prepare(`SELECT id, type, area, severity, subject, body, status, dedup_of, resolution, credited_points, created_at, updated_at
|
|
143
|
+
const rows = db.prepare(`SELECT id, type, area, severity, subject, body, status, dedup_of, resolution, credited_points, credit_pending_anchor, promoted_task_id, created_at, updated_at
|
|
137
144
|
FROM build_feedback WHERE user_id = ? ORDER BY created_at DESC LIMIT 100`).all(userId);
|
|
138
145
|
return rows;
|
|
139
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* RFC-004 体验补:提交者【事后】绑定 Passkey 时,追溯补发此前"已受理但因无锚点跳过记功"的贡献信誉。
|
|
149
|
+
* 原则自洽:受理已由 maintainer 把关(分是挣得的),Passkey 只是解锁"奖励锚真人"——故补发无 gaming 风险。
|
|
150
|
+
* 幂等:creditBuildReputation 按 (source, ref_id) 去重;且只扫 credit_pending_anchor=1 的行。绑定流程调用,advisory 永不阻塞。
|
|
151
|
+
*/
|
|
152
|
+
export function grantPendingAnchorCredits(db, userId) {
|
|
153
|
+
const hasAnchor = ((db.prepare('SELECT COUNT(*) AS n FROM webauthn_credentials WHERE user_id = ?')
|
|
154
|
+
.get(userId)?.n) || 0) > 0;
|
|
155
|
+
if (!hasAnchor)
|
|
156
|
+
return { granted: 0, total_points: 0 };
|
|
157
|
+
const rows = db.prepare(`SELECT id FROM build_feedback WHERE user_id = ? AND credit_pending_anchor = 1`).all(userId);
|
|
158
|
+
let granted = 0, total = 0;
|
|
159
|
+
for (const r of rows) {
|
|
160
|
+
const res = creditBuildReputation(db, userId, 'feedback_accepted', BUILD_POINTS.feedback_accepted, r.id, `feedback ${r.id} accepted (anchor backfill)`);
|
|
161
|
+
db.prepare(`UPDATE build_feedback SET credited_points = ?, credit_pending_anchor = 0, updated_at = datetime('now') WHERE id = ?`)
|
|
162
|
+
.run(BUILD_POINTS.feedback_accepted, r.id);
|
|
163
|
+
if (!res.already) {
|
|
164
|
+
granted++;
|
|
165
|
+
total += BUILD_POINTS.feedback_accepted;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { granted, total_points: total };
|
|
169
|
+
}
|
|
140
170
|
export function getBuildFeedback(db, id, userId, isAdmin) {
|
|
141
171
|
const row = db.prepare('SELECT * FROM build_feedback WHERE id = ?').get(id);
|
|
142
172
|
if (!row)
|
|
@@ -163,23 +193,52 @@ export function adminUpdateBuildFeedback(db, u) {
|
|
|
163
193
|
// 无 Passkey 的报告者(报问题=用)可受理致谢,但无锚点不记分。
|
|
164
194
|
let credited = Number(row.credited_points) || 0;
|
|
165
195
|
let credit_skipped_no_anchor = false;
|
|
196
|
+
let pendingFlag = null; // null = 不改 credit_pending_anchor;1 = 待补发;0 = 已发/清除
|
|
166
197
|
if (u.credit && newStatus === 'resolved' && credited === 0) {
|
|
167
198
|
const hasAnchor = ((db.prepare('SELECT COUNT(*) AS n FROM webauthn_credentials WHERE user_id = ?')
|
|
168
199
|
.get(row.user_id)?.n) || 0) > 0;
|
|
169
200
|
if (hasAnchor) {
|
|
170
201
|
creditBuildReputation(db, row.user_id, 'feedback_accepted', BUILD_POINTS.feedback_accepted, u.id, `feedback ${u.id} accepted`);
|
|
171
202
|
credited = BUILD_POINTS.feedback_accepted;
|
|
203
|
+
pendingFlag = 0;
|
|
172
204
|
}
|
|
173
205
|
else {
|
|
174
|
-
credit_skipped_no_anchor = true; // 受理但不记分(提交者无 Passkey 锚点)
|
|
206
|
+
credit_skipped_no_anchor = true; // 受理但不记分(提交者无 Passkey 锚点)→ 标记待补发,绑 Passkey 后发放
|
|
207
|
+
pendingFlag = 1;
|
|
175
208
|
}
|
|
176
209
|
}
|
|
177
210
|
db.prepare(`UPDATE build_feedback SET status = ?, resolution = COALESCE(?, resolution),
|
|
178
|
-
rfc_draft = COALESCE(?, rfc_draft), credited_points = ?,
|
|
179
|
-
|
|
211
|
+
rfc_draft = COALESCE(?, rfc_draft), credited_points = ?, credit_pending_anchor = COALESCE(?, credit_pending_anchor),
|
|
212
|
+
handled_by = ?, updated_at = datetime('now')
|
|
213
|
+
WHERE id = ?`).run(newStatus, u.resolution ?? null, u.rfcDraft ?? null, credited, pendingFlag, u.adminId, u.id);
|
|
180
214
|
if (newStatus !== fromStatus)
|
|
181
215
|
logEvent(db, u.id, u.adminId, fromStatus, newStatus, u.resolution ?? null);
|
|
182
|
-
|
|
216
|
+
// RFC-006 桥(use→build 漏斗补全):maintainer 采纳提案时可 promote 成可认领的 build_task,
|
|
217
|
+
// 并【邀请提案人】来认领——把"反馈被采纳"接到"来一起建设",补上漏斗最大断点。
|
|
218
|
+
// 幂等:已 promote 过(promoted_task_id 非空)不重复建。
|
|
219
|
+
let promoted_task_id;
|
|
220
|
+
const already = row.promoted_task_id || null;
|
|
221
|
+
if (u.promoteToTask && !already && row.type === 'proposal') {
|
|
222
|
+
const title = (row.subject || row.body || 'contributor proposal').slice(0, 200);
|
|
223
|
+
const created = createBuildTask(db, {
|
|
224
|
+
creatorId: u.adminId,
|
|
225
|
+
title,
|
|
226
|
+
area: row.area || undefined,
|
|
227
|
+
description: `From accepted proposal ${u.id} (by ${row.user_id}).\n\n${row.body || ''}`.slice(0, 4000),
|
|
228
|
+
rfcRef: row.rfc_draft || undefined,
|
|
229
|
+
});
|
|
230
|
+
if ('id' in created) {
|
|
231
|
+
promoted_task_id = created.id;
|
|
232
|
+
db.prepare('UPDATE build_feedback SET promoted_task_id = ? WHERE id = ?').run(promoted_task_id, u.id);
|
|
233
|
+
// 邀请提案人:通知 + 反馈闭环里会显示 promoted_task_id
|
|
234
|
+
try {
|
|
235
|
+
db.prepare(`INSERT INTO notifications (id, user_id, type, title, body) VALUES (?,?,?,?,?)`).run(generateId('ntf'), row.user_id, 'build_invite', '你的提案被采纳了 — 来一起建设?', `提案「${title}」已被采纳并建成可认领任务 ${promoted_task_id}。在「我的共建」用 webaz_contribute 认领即可参与实现。`);
|
|
236
|
+
}
|
|
237
|
+
catch { /* notifications 可选,不阻断 */ }
|
|
238
|
+
logEvent(db, u.id, u.adminId, newStatus, newStatus, `promoted → task ${promoted_task_id}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return { ok: true, credited, ...(credit_skipped_no_anchor ? { credit_skipped_no_anchor: true } : {}), ...(promoted_task_id ? { promoted_task_id } : {}) };
|
|
183
242
|
}
|
|
184
243
|
// ─── RFC-005 Phase 2:AI triage(advisory)─────────────────────────
|
|
185
244
|
// 给"内部反馈"打标,不碰代码、不 resolve、不记功(那是人类的)。无 key 也能跑(只做确定性去重 + 置 triaged)。
|
|
@@ -57,6 +57,10 @@ export function creditBuildReputation(db, userId, source, points, refId, note) {
|
|
|
57
57
|
}
|
|
58
58
|
return { credited: points };
|
|
59
59
|
}
|
|
60
|
+
// RFC-006 stage 4(恶意管理)= 复用现有问责中间件,**无需新代码**:
|
|
61
|
+
// build_tasks 是 api_key 写端点,被 strike 至 suspend_7d/permanent 的贡献者已被 isApiKeyBlocked
|
|
62
|
+
// (server.ts)挡在所有写之外,包括建设。strike/blocklist/outlier 对建设贡献自动生效。
|
|
63
|
+
// 看板这里只【展示】当事人的活跃 strike + 申诉入口(透明先于强制);真人申诉走现成 strikes/:id/appeal。
|
|
60
64
|
// 贡献者【自查】档案 —— KPI + 等级 + 来源拆分 + provenance + 限制/惩罚 + 申诉入口。
|
|
61
65
|
// 不变量 3:仅本人可调(路由层 auth);不做公开榜。
|
|
62
66
|
export function getBuildProfile(db, userId) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-011 §④ 契约变更体系 —— 让 feed 诚实(防 CHANGELOG-rot 那个失败模式)。
|
|
3
|
+
*
|
|
4
|
+
* 思路:给每个【契约面】(②能力矩阵 / ①实体字典)算确定性指纹(canonical + sha256),
|
|
5
|
+
* 排除 software_version 等易变位 —— 只盖集成方依赖的契约内容。committed 基线 docs/CONTRACT-LOCK.json
|
|
6
|
+
* 按 contract_version 锁;tests/test-contract-fingerprint.ts 守卫:指纹变了但 CONTRACT_VERSION 没 bump
|
|
7
|
+
* → FAIL,逼"bump + 写 CONTRACT_CHANGES 条目 + 更基线"。静默改契约不可 merge(= schema:verify 模式)。
|
|
8
|
+
*
|
|
9
|
+
* 版本模型:CONTRACT_VERSION = 契约内容修订号,任何 integrator-observable 变更都 bump;
|
|
10
|
+
* 变更条目的 kind(added|changed|deprecated|removed)由人分类是否破坏性(additive agent 可忽略)。
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
import { capabilityMatrix } from './endpoint-actions.js';
|
|
14
|
+
import { buildEntityDictionary } from './entity-dictionary.js';
|
|
15
|
+
import { canonicalSerialize } from '../layer0-foundation/L0-2-state-machine/order-chain.js';
|
|
16
|
+
import { CONTRACT_VERSION } from '../version.js';
|
|
17
|
+
const sha256 = (s) => createHash('sha256').update(s).digest('hex');
|
|
18
|
+
// 契约投影 —— 只取集成方依赖的契约内容,【排除】software_version / 易变 roadmap(todo)。
|
|
19
|
+
function contractProjection() {
|
|
20
|
+
const cap = capabilityMatrix();
|
|
21
|
+
const ent = buildEntityDictionary();
|
|
22
|
+
return {
|
|
23
|
+
capability: { model: cap.model, write_actions: cap.write_actions, safe_write_unscoped: cap.safe_write_unscoped, read_scopes: cap.read_scopes, notes: cap.notes },
|
|
24
|
+
entity: { note: ent.note, entities: ent.entities }, // 注:不含 ent.software_version / ent.todo
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function contractFingerprints() {
|
|
28
|
+
const p = contractProjection();
|
|
29
|
+
const capability = sha256(canonicalSerialize(p.capability));
|
|
30
|
+
const entity = sha256(canonicalSerialize(p.entity));
|
|
31
|
+
return { contract_version: CONTRACT_VERSION, capability, entity, combined: sha256(`${capability}|${entity}`) };
|
|
32
|
+
}
|
|
33
|
+
export const CONTRACT_CHANGES = [
|
|
34
|
+
{ contract_version: 1, date: '2026-06-06', surface: 'all', kind: 'genesis', summary: 'Contract v1 baseline: capability matrix (§②), entity dictionary + order lifecycle (§①), event cursor stream (§⑥), two version axes (§④).' },
|
|
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
|
+
];
|
|
37
|
+
export function buildChangeFeed() {
|
|
38
|
+
return {
|
|
39
|
+
current_contract_version: CONTRACT_VERSION,
|
|
40
|
+
fingerprints: contractFingerprints(),
|
|
41
|
+
deprecation_policy: 'Sunset-bound surfaces carry RFC 8594 Deprecation + Sunset headers; window ≥ 1 contract_version. Any integrator-observable contract change bumps contract_version and appends a change entry (kind: added|changed|deprecated|removed). A fingerprint CI guard makes a silent change un-mergeable.',
|
|
42
|
+
deprecations: [],
|
|
43
|
+
changes: CONTRACT_CHANGES,
|
|
44
|
+
note: 'Poll with your last-seen contract_version and apply entries with a higher contract_version. The fingerprints let you detect drift without diffing the whole contract: if combined != your cached value, re-read /.well-known/webaz-{capabilities,entities}.json.',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -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
|
+
}
|