@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.
@@ -1,5 +1,15 @@
1
1
  export function registerOrdersActionRoutes(app, deps) {
2
- const { db, auth, isTrustedRole, generateId, transition, notifyTransition, settleOrder, detectFraud, createDispute, checkTimeouts, recordViolationReputation, broadcastSystemEvent } = deps;
2
+ const { db, auth, isTrustedRole, generateId, transition, notifyTransition, settleOrder, settleFault, detectFraud, createDispute, checkTimeouts, recordViolationReputation, broadcastSystemEvent } = deps;
3
+ // RFC-007 stage 2:卖家主动拒单 reason_code 白名单。
4
+ // classification(客观无责 vs 主观有责)是 stage 3 auto-verify 的事;stage 2 仅捕获 + 一律走违约结算。
5
+ // objective-claimed(stage 3 将尝试确定性核验):stock_consumed_concurrent / stale_price_snapshot / force_majeure
6
+ // subjective(stage 3 直接判 fault):price_regret / cherry_pick / other
7
+ const DECLINE_REASON_CODES = new Set([
8
+ 'stock_consumed_concurrent', 'stale_price_snapshot', 'force_majeure',
9
+ 'price_regret', 'cherry_pick', 'other',
10
+ ]);
11
+ // 客观-声称理由:链下事实(外部已售/损毁),协议无确定性信号可自动核验 → 临时判责 + 举证窗口(stage 5 仲裁)。
12
+ const OBJECTIVE_DECLINE_REASONS = new Set(['stock_consumed_concurrent', 'stale_price_snapshot', 'force_majeure']);
3
13
  // C-4: 卖家批量发货
4
14
  app.post('/api/orders/batch-ship', (req, res) => {
5
15
  const user = auth(req, res);
@@ -142,6 +152,88 @@ export function registerOrdersActionRoutes(app, deps) {
142
152
  if (action === 'pickup' && !order.logistics_id && user.role === 'logistics') {
143
153
  db.prepare('UPDATE orders SET logistics_id = ? WHERE id = ?').run(user.id, req.params.id);
144
154
  }
155
+ // ── RFC-007 stage 2/3:卖家【主动拒单】decline ───────────────────────────────
156
+ // 仅卖家、仅 paid(待接单) 状态可拒;记 reason_code + declined_at。
157
+ // stage 3 按理由分流:
158
+ // · 主观理由(price_regret/cherry_pick/other)→ 立即违约结算(paid→fault_seller→completed)。
159
+ // · 客观-声称理由(并发耗尽/陈旧快照/不可抗力)→ 【临时判责】:转 fault_seller 但【不结算】,
160
+ // 置 decline_objective_pending=1 + 举证窗口 deadline。窗口内卖家可开仲裁(stage 5)举证翻案;
161
+ // 到期无人仲裁 → checkTimeouts 自动终结为违约。客观场景本质是链下事实(外部已售/损毁),
162
+ // 协议无确定性信号可自动核验,必须人工仲裁 —— 故不自动免责,只给举证窗口。
163
+ if (action === 'decline') {
164
+ if (uid !== sellerId)
165
+ return void res.status(403).json({ error: '你不是本订单的卖家', error_code: 'NOT_ORDER_SELLER' });
166
+ if (order.status !== 'paid')
167
+ return void res.status(400).json({ error: `仅可在「待接单(paid)」状态拒单,当前 ${order.status}`, error_code: 'DECLINE_WRONG_STATUS' });
168
+ const reasonCode = String((req.body?.decline_reason_code ?? '') || '').trim();
169
+ if (!DECLINE_REASON_CODES.has(reasonCode)) {
170
+ return void res.status(400).json({ error: `decline_reason_code 无效,需为: ${[...DECLINE_REASON_CODES].join(' / ')}`, error_code: 'DECLINE_REASON_INVALID' });
171
+ }
172
+ const isObjectiveClaim = OBJECTIVE_DECLINE_REASONS.has(reasonCode);
173
+ // 客观-声称 + 举证窗口 > 0 → 临时判责(不结算);否则(主观 或 窗口=0)→ 立即违约结算
174
+ const windowHours = Number(db.prepare("SELECT value FROM protocol_params WHERE key = 'decline_contest_window_hours'").get()?.value ?? 24);
175
+ const provisional = isObjectiveClaim && windowHours > 0;
176
+ db.prepare("UPDATE orders SET decline_reason_code = ?, declined_at = datetime('now') WHERE id = ?").run(reasonCode, req.params.id);
177
+ const r1 = transition(db, req.params.id, 'fault_seller', uid, [], `卖家主动拒单 reason=${reasonCode}${provisional ? '(临时判责·待举证)' : ''}${notes ? ':' + notes : ''}`);
178
+ if (!r1.success) {
179
+ db.prepare("UPDATE orders SET decline_reason_code = NULL, declined_at = NULL WHERE id = ?").run(req.params.id);
180
+ return void res.json({ error: r1.error });
181
+ }
182
+ notifyTransition(db, req.params.id, 'paid', 'fault_seller');
183
+ if (provisional) {
184
+ // 临时判责:置 pending + deadline,【不结算】(escrow/stake 暂挂,随终结或翻案一次性结算)
185
+ db.prepare(`UPDATE orders SET decline_objective_pending = 1, decline_contest_deadline = datetime('now', '+' || ? || ' hours') WHERE id = ?`).run(windowHours, req.params.id);
186
+ const deadline = db.prepare("SELECT decline_contest_deadline AS d FROM orders WHERE id = ?").get(req.params.id).d;
187
+ return void res.json({
188
+ success: true, outcome: 'fault_seller_provisional', decline_reason_code: reasonCode, contest_deadline: deadline,
189
+ note: `客观无责拒单为【临时判责】:你声称的客观理由(外部已售/损毁等)是链下事实,协议无法自动核验,需人工仲裁。请在 ${deadline} 前用 webaz_dispute 开仲裁举证;维持则免责全退,逾期未仲裁则自动终结为违约。买家退款随终结/翻案一次性处理。`,
190
+ });
191
+ }
192
+ // 主观(或窗口=0):立即违约结算(退款买家 + 按 stake_backing 罚没,守恒,绝不印钱)
193
+ const sysUser = db.prepare("SELECT id FROM users WHERE id = 'sys_protocol'").get();
194
+ try {
195
+ settleFault(db, req.params.id, 'fault_seller');
196
+ if (sysUser) {
197
+ transition(db, req.params.id, 'completed', sysUser.id, [], '主动拒单:系统执行违约结算');
198
+ notifyTransition(db, req.params.id, 'fault_seller', 'completed');
199
+ }
200
+ }
201
+ catch (e) {
202
+ console.error('[decline settleFault]', e);
203
+ }
204
+ return void res.json({
205
+ success: true, outcome: 'fault_seller', decline_reason_code: reasonCode,
206
+ note: '主观理由拒单 → 立即违约结算,买家已全额退款。',
207
+ });
208
+ }
209
+ // ── RFC-007 stage 5:卖家就【临时判责】发起仲裁举证 ───────────────────────────
210
+ // 仅卖家、仅 provisional(fault_seller + decline_objective_pending=1 未结算未过期)。置 decline_contested=1
211
+ // → checkTimeouts 暂停自动终结,等人工仲裁裁决(维持→declined_nofault 免责 / 驳回→违约)。
212
+ if (action === 'contest_decline') {
213
+ if (uid !== sellerId)
214
+ return void res.status(403).json({ error: '你不是本订单的卖家', error_code: 'NOT_ORDER_SELLER' });
215
+ if (order.status !== 'fault_seller' || Number(order.decline_objective_pending) !== 1 || order.settled_fault_at) {
216
+ return void res.status(400).json({ error: '本订单不是可举证的【临时判责】状态', error_code: 'NOT_PROVISIONAL_DECLINE' });
217
+ }
218
+ if (Number(order.decline_contested) === 1)
219
+ return void res.status(400).json({ error: '已在仲裁中,无需重复发起', error_code: 'ALREADY_CONTESTED' });
220
+ const overdue = db.prepare("SELECT datetime(decline_contest_deadline) < datetime('now') AS od FROM orders WHERE id = ?").get(req.params.id).od;
221
+ if (overdue)
222
+ return void res.status(400).json({ error: '举证窗口已过期,临时判责已可被终结', error_code: 'CONTEST_WINDOW_CLOSED' });
223
+ const evIds = [];
224
+ if (evidence_description) {
225
+ const eid = generateId('evt');
226
+ const evReasons = detectFraud(String(evidence_description));
227
+ db.prepare(`INSERT INTO evidence (id, order_id, uploader_id, type, description, file_hash, flag_reasons) VALUES (?,?,?,'description',?,?,?)`)
228
+ .run(eid, req.params.id, uid, evidence_description, `hash_${Date.now()}`, evReasons.length ? JSON.stringify(evReasons) : null);
229
+ evIds.push(eid);
230
+ }
231
+ db.prepare("UPDATE orders SET decline_contested = 1 WHERE id = ?").run(req.params.id);
232
+ return void res.json({
233
+ success: true, outcome: 'contested', evidence_ids: evIds,
234
+ note: '已就客观无责拒单发起人工仲裁举证。自动终结已暂停,等待仲裁员裁决:维持→免责全退+退质押,驳回→违约结算。',
235
+ });
236
+ }
145
237
  const actionMap = {
146
238
  accept: 'accepted', ship: 'shipped', pickup: 'picked_up',
147
239
  transit: 'in_transit', deliver: 'delivered', confirm: 'confirmed', dispute: 'disputed'
@@ -1,3 +1,5 @@
1
+ // RFC-011 §⑥ 事件游标流(纯 db 函数,party-gated)
2
+ import { listOrderEventsSince } from '../../layer0-foundation/L0-2-state-machine/order-chain.js';
1
3
  export function registerOrdersReadRoutes(app, deps) {
2
4
  const { db, auth, getOrderStatus, getOrderChain, verifyOrderChain, getOrderDispute } = deps;
3
5
  app.get('/api/orders', (req, res) => {
@@ -112,6 +114,22 @@ export function registerOrdersReadRoutes(app, deps) {
112
114
  const verification = verifyOrderChain(db, req.params.id);
113
115
  res.json({ chain, verification });
114
116
  });
117
+ // RFC-011 §⑥:事件游标流 —— 集成方 agent 拉"自 cursor 以来与我相关的订单变化"(agent 拉,非 webhook)。
118
+ // party-gated(只见自己当事的订单事件,= /chain 同口径,不变量 2:活性 ≤ 读边界);
119
+ // 结构性事件 + 哈希链字段(验链防篡改),完整 payload 仍走 party-gated /chain。
120
+ app.get('/api/agent/events', (req, res) => {
121
+ const user = auth(req, res);
122
+ if (!user)
123
+ return;
124
+ const since = typeof req.query.since === 'string' ? req.query.since : undefined;
125
+ const limit = Number(req.query.limit) || 50;
126
+ const r = listOrderEventsSince(db, user.id, since, limit);
127
+ res.setHeader('Cache-Control', 'no-store'); // 事件流不缓存
128
+ res.json({
129
+ ...r,
130
+ note: 'Cursor stream of order events you are party to. Pass ?since=<next_cursor> to page. Pull, not push. event_hash+prev_event_hash verify chain integrity; full payload via GET /api/orders/:id/chain.',
131
+ });
132
+ });
115
133
  app.get('/api/orders/:id', (req, res) => {
116
134
  const user = auth(req, res);
117
135
  if (!user)
@@ -1,3 +1,15 @@
1
+ import path from 'node:path';
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { SOFTWARE_VERSION, CONTRACT_VERSION } from '../../version.js';
5
+ import { capabilityMatrix } from '../endpoint-actions.js';
6
+ import { buildEntityDictionary } from '../entity-dictionary.js';
7
+ import { buildGoalIndex } from '../goal-index.js';
8
+ import { buildChangeFeed } from '../contract-fingerprint.js';
9
+ import { buildIntegrationContract } from '../integration-contract.js';
10
+ import { buildVerifiabilityIndex } from '../verifiability-index.js';
11
+ import { buildEconomicParticipation } from '../economic-participation.js';
12
+ import { buildNegativeSpace } from '../negative-space.js';
1
13
  export function registerPublicUtilsRoutes(app, deps) {
2
14
  const { db, MASTER_SEED, NODE_ENV, SERVICE_START_MS, rateLimitOk, generateManifest, getUser, logError, issuerAddress } = deps;
3
15
  app.get('/api/health', (_req, res) => {
@@ -73,7 +85,11 @@ export function registerPublicUtilsRoutes(app, deps) {
73
85
  const issuerActiveSince = db.prepare("SELECT value FROM system_state WHERE key='issuer_active_since'").get()?.value || '2026-05-30';
74
86
  return {
75
87
  name: 'WebAZ Protocol',
76
- schema_version: 1,
88
+ // RFC-011 §④ 两轴版本(单一来源 src/version.ts):
89
+ // schema_version = 集成契约版本(整数,仅 breaking 契约变更才 bump;集成方 agent 按此判兼容)
90
+ // software_version = 本代码 npm/release semver(= package.json,自动同步,永不漂移)
91
+ schema_version: CONTRACT_VERSION,
92
+ software_version: SOFTWARE_VERSION,
77
93
  network_state: {
78
94
  phase,
79
95
  real_users_on_canonical: realUsers,
@@ -106,6 +122,22 @@ export function registerPublicUtilsRoutes(app, deps) {
106
122
  agent_governance: 'https://github.com/seasonsagents-art/webaz/blob/main/docs/AGENT-GOVERNANCE.md',
107
123
  protocol_compatibility: 'https://github.com/seasonsagents-art/webaz/blob/main/docs/PROTOCOL-COMPATIBILITY-AUDIT-2026-05-30.md',
108
124
  changelog: 'https://github.com/seasonsagents-art/webaz/blob/main/CHANGELOG.md',
125
+ // RFC-011 §②:agent 可读能力矩阵(写边界 action-scope + 敏感读 scope),live doc=code
126
+ capability_matrix: 'https://webaz.xyz/.well-known/webaz-capabilities.json',
127
+ },
128
+ // RFC-011 agent 接入 live 端点;总入口 integration.json 把它们按旅程串起来
129
+ agent_endpoints: {
130
+ integration_contract: 'https://webaz.xyz/.well-known/webaz-integration.json', // RFC-011 总入口(按旅程导航全维度)
131
+ capability_matrix: 'https://webaz.xyz/.well-known/webaz-capabilities.json', // ② 边界(#126)
132
+ entity_dictionary: 'https://webaz.xyz/.well-known/webaz-entities.json', // ① 语义(order/product/dispute 状态机 + 字段)
133
+ goal_index: 'https://webaz.xyz/.well-known/webaz-goals.json', // ① 目标索引(intent → action + endpoint + 工具)
134
+ change_feed: 'https://webaz.xyz/api/agent/changes', // ④ 契约变更 + 指纹 + 弃用
135
+ verifiability_index: 'https://webaz.xyz/.well-known/webaz-verifiability.json', // ⑤ 什么可验+怎么验
136
+ economic_participation: 'https://webaz.xyz/.well-known/webaz-economic.json', // ⑧ value-participant 角色经济条款(费率实时)
137
+ negative_space: 'https://webaz.xyz/.well-known/webaz-negative-space.json', // ② 负空间(禁区 + 限额 + 后果阶梯)
138
+ event_stream: 'https://webaz.xyz/api/agent/events?since=<cursor>', // ⑥ 事件游标流(party-gated,需 auth)
139
+ passport: 'https://webaz.xyz/api/me/agents/:apiKeyPrefix/passport', // ⑤ 可验护照
140
+ did: 'https://webaz.xyz/.well-known/did.json',
109
141
  },
110
142
  // 路线图 — 回应"知道还有哪些没做"的诚实化第三层。哲学:公开当前到达点 + 已知未做项,不承诺时间表。
111
143
  roadmap: {
@@ -140,6 +172,104 @@ export function registerPublicUtilsRoutes(app, deps) {
140
172
  res.setHeader('Cache-Control', 'public, max-age=300');
141
173
  res.json(buildProtocolManifest());
142
174
  });
175
+ // RFC-011 §② — agent 可读能力矩阵(写边界 action-scope + 敏感读 scope)。
176
+ // live = 直接序列化 enforce 用的规则表(src/pwa/endpoint-actions.ts),doc=code 零漂移。
177
+ // 集成方 agent fetch 此端点即知"我要做的写需要声明哪个 scope / 哪些写无需 scope / 哪些读受约束"。
178
+ app.get('/.well-known/webaz-capabilities.json', (_req, res) => {
179
+ res.setHeader('Cache-Control', 'public, max-age=300');
180
+ res.json(capabilityMatrix());
181
+ });
182
+ app.get('/api/agent/capabilities', (_req, res) => {
183
+ res.setHeader('Cache-Control', 'public, max-age=300');
184
+ res.json(capabilityMatrix());
185
+ });
186
+ // RFC-011 §① — agent 可读实体字典(订单状态机 doc=code + 保守公开字段 + 可验证标注)。
187
+ app.get('/.well-known/webaz-entities.json', (_req, res) => {
188
+ res.setHeader('Cache-Control', 'public, max-age=300');
189
+ res.json(buildEntityDictionary());
190
+ });
191
+ app.get('/api/agent/entities', (_req, res) => {
192
+ res.setHeader('Cache-Control', 'public, max-age=300');
193
+ res.json(buildEntityDictionary());
194
+ });
195
+ // RFC-011 §① 目标索引 —— intent → action(②)+ endpoint + MCP 工具 + PWA 页(agent 自路由)。
196
+ app.get('/.well-known/webaz-goals.json', (_req, res) => {
197
+ res.setHeader('Cache-Control', 'public, max-age=300');
198
+ res.json(buildGoalIndex());
199
+ });
200
+ // 集成必需文档(规则 + onboarding)由协议自身 serve —— 外部 agent 必须能读到约束它的规则,
201
+ // 不能指向私有 repo 的 GitHub(对外 404)。显式白名单:只暴露这 3 份"本就该公开"的文档,
202
+ // 非白名单 → 404(不泄漏 RFC/审计等内部设计文档;它们是 provenance,随 repo 公开解锁)。
203
+ const PUBLIC_DOCS = new Set(['INTEGRATOR.md', 'META-RULES-FULL.md', 'ECONOMIC-MODEL.md']);
204
+ const DOCS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../docs'); // repo-root/docs(dev+prod 都解析到此)
205
+ app.get('/docs/:name', (req, res) => {
206
+ const name = String(req.params.name);
207
+ if (!PUBLIC_DOCS.has(name))
208
+ return void res.status(404).json({ error: 'not a public doc', public_docs: [...PUBLIC_DOCS] });
209
+ try {
210
+ const md = readFileSync(path.join(DOCS_DIR, name), 'utf8');
211
+ res.setHeader('Content-Type', 'text/markdown; charset=utf-8');
212
+ res.setHeader('Cache-Control', 'public, max-age=300');
213
+ res.send(md);
214
+ }
215
+ catch {
216
+ res.status(404).json({ error: 'doc unavailable' });
217
+ }
218
+ });
219
+ app.get('/api/agent/goals', (_req, res) => {
220
+ res.setHeader('Cache-Control', 'public, max-age=300');
221
+ res.json(buildGoalIndex());
222
+ });
223
+ // RFC-011 §④ — 契约变更 feed:current_contract_version + 契约面指纹 + 变更注册表 + 弃用策略。
224
+ // agent 用上次见过的 contract_version poll;指纹让它不必 diff 整份契约就知道有没有漂移。
225
+ // 指纹由 tests/test-contract-fingerprint.ts + docs/CONTRACT-LOCK.json 守住(静默改契约不可 merge)。
226
+ app.get('/api/agent/changes', (_req, res) => {
227
+ res.setHeader('Cache-Control', 'public, max-age=300');
228
+ res.json(buildChangeFeed());
229
+ });
230
+ // RFC-011 总入口 —— 集成方 agent 一次 fetch 拿到整份契约导航(按旅程组织,指向各维度 live 端点)。
231
+ app.get('/.well-known/webaz-integration.json', (_req, res) => {
232
+ res.setHeader('Cache-Control', 'public, max-age=300');
233
+ res.json(buildIntegrationContract());
234
+ });
235
+ app.get('/api/agent/integration', (_req, res) => {
236
+ res.setHeader('Cache-Control', 'public, max-age=300');
237
+ res.json(buildIntegrationContract());
238
+ });
239
+ // RFC-011 §⑤ 可验证索引 —— "什么可验 + 怎么验"统一表(护照/锚/AP2/订单链),诚实分级。
240
+ app.get('/.well-known/webaz-verifiability.json', (_req, res) => {
241
+ res.setHeader('Cache-Control', 'public, max-age=300');
242
+ res.json(buildVerifiabilityIndex());
243
+ });
244
+ app.get('/api/agent/verifiability', (_req, res) => {
245
+ res.setHeader('Cache-Control', 'public, max-age=300');
246
+ res.json(buildVerifiabilityIndex());
247
+ });
248
+ // RFC-011 §⑧ 经济参与索引 —— value-participant 角色 × 赚什么/押什么/担什么责,
249
+ // 费率【实时】从 protocol_params 读(doc=code,永不和 enforced 经济漂移)。
250
+ const liveParam = (key, fallback) => {
251
+ const row = db.prepare('SELECT value, type FROM protocol_params WHERE key = ?').get(key);
252
+ if (!row)
253
+ return fallback;
254
+ if (row.type === 'number')
255
+ return Number(row.value);
256
+ if (row.type === 'boolean')
257
+ return (row.value === 'true' || row.value === '1');
258
+ return row.value;
259
+ };
260
+ const economic = (_req, res) => {
261
+ res.setHeader('Cache-Control', 'public, max-age=300');
262
+ res.json(buildEconomicParticipation(liveParam));
263
+ };
264
+ app.get('/.well-known/webaz-economic.json', economic);
265
+ app.get('/api/agent/economic-participation', economic);
266
+ // RFC-011 §② 负空间 —— 禁区 + enforced 限额 + 后果阶梯(per-agent 速率实时读)。
267
+ const negativeSpace = (_req, res) => {
268
+ res.setHeader('Cache-Control', 'public, max-age=300');
269
+ res.json(buildNegativeSpace(liveParam));
270
+ };
271
+ app.get('/.well-known/webaz-negative-space.json', negativeSpace);
272
+ app.get('/api/agent/negative-space', negativeSpace);
143
273
  // W3C DID Document(B.6 b DID 短期 mapping,2026-05-30):
144
274
  // did:web:webaz.xyz 通过 HTTPS 解析到这里(W3C did:web spec §3.2)
145
275
  // verificationMethod 用 EcdsaSecp256k1RecoveryMethod2020 + CAIP-10 blockchainAccountId
@@ -1,5 +1,7 @@
1
1
  import { generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse, } from '@simplewebauthn/server';
2
2
  import { randomBytes } from 'node:crypto';
3
+ // RFC-004 体验补:绑定 Passkey 后,追溯补发此前"已受理但无锚点跳过"的建设信誉。
4
+ import { grantPendingAnchorCredits } from '../../layer2-business/L2-8-feedback/build-feedback-engine.js';
3
5
  export function registerWebauthnRoutes(app, deps) {
4
6
  const { db, auth, generateId, rpId, rpName, origin, challengeTtlMs, gateTtlMs, invalidateAgentRiskCacheForUser, requireHumanPresence } = deps;
5
7
  // 1. 注册:start — 生成 challenge + 选项
@@ -53,7 +55,13 @@ export function registerWebauthnRoutes(app, deps) {
53
55
  .run(credential.id, user.id, Buffer.from(credential.publicKey), credential.counter || 0, JSON.stringify(credential.transports || []), (device_label || '').slice(0, 60) || null);
54
56
  db.prepare("UPDATE webauthn_challenges SET consumed_at = datetime('now') WHERE id = ?").run(challenge_id);
55
57
  invalidateAgentRiskCacheForUser(user.id); // 让 D2b 中间件立刻看到刚绑的 Passkey,否则 5min 缓存窗内仍被拦
56
- res.json({ success: true, credential_id: credential.id });
58
+ // RFC-004:绑定 Passkey = 成为可问责真人 → 追溯补发此前因无锚点跳过的建设信誉(advisory,永不阻塞绑定)
59
+ let backfilled;
60
+ try {
61
+ backfilled = grantPendingAnchorCredits(db, user.id);
62
+ }
63
+ catch { /* 不影响绑定主流程 */ }
64
+ res.json({ success: true, credential_id: credential.id, ...(backfilled && backfilled.granted > 0 ? { build_credit_backfilled: backfilled } : {}) });
57
65
  }
58
66
  catch (e) {
59
67
  res.status(400).json({ error: e.message });
@@ -20,7 +20,9 @@ import path from 'path';
20
20
  import { fileURLToPath } from 'url';
21
21
  import crypto from 'crypto';
22
22
  import { initDatabase, generateId } from '../layer0-foundation/L0-1-database/schema.js';
23
- import { initSystemUser, transition, getOrderStatus, checkTimeouts } from '../layer0-foundation/L0-2-state-machine/engine.js';
23
+ import { initSystemUser, transition, getOrderStatus, checkTimeouts, settleFault } from '../layer0-foundation/L0-2-state-machine/engine.js';
24
+ import { endpointToAction, endpointToReadAction } from './endpoint-actions.js';
25
+ import { AGENT_RATE_PER_MIN_DEFAULTS, CROSS_USER_READ_DAILY_CAP, MASS_ACTION_TYPES, MASS_ACTION_DAILY_CAPS } from './limits.js';
24
26
  import { initOrderChainSchema, appendOrderEvent, getOrderChain, verifyOrderChain } from '../layer0-foundation/L0-2-state-machine/order-chain.js';
25
27
  import { initSnfSchema, snfSend, snfCleanup } from '../layer2-business/L2-7-snf/snf-engine.js';
26
28
  import { initExternalAnchorSchema } from '../layer1-agent/L1-2-external-anchor/anchor-engine.js';
@@ -534,6 +536,15 @@ for (const stmt of [
534
536
  // RFC-008 stage 1:每单【赔付背书】快照 = 该单实际背书的卖家质押额。
535
537
  // 起步免赔付阶段(require_seller_stake=0)= 0;违约结算只按此数没收,绝不扣未背书的钱 → 根治印钱 bug。
536
538
  'ALTER TABLE orders ADD COLUMN stake_backing REAL DEFAULT 0',
539
+ // RFC-007 stage 2:卖家【主动拒单】记录(vs 沉默超时)。reason_code 供 stage 3/5 判定客观/主观。
540
+ 'ALTER TABLE orders ADD COLUMN decline_reason_code TEXT',
541
+ 'ALTER TABLE orders ADD COLUMN declined_at TEXT',
542
+ // RFC-007 stage 3:客观理由拒单 → 【临时判责】(provisional)。先不结算,给卖家举证窗口(stage 5 仲裁翻案)。
543
+ // pending=1 + deadline 到期仍无人仲裁 → checkTimeouts 终结为违约(settleFault)。stage 5 仲裁维持则翻 declined_nofault。
544
+ 'ALTER TABLE orders ADD COLUMN decline_objective_pending INTEGER DEFAULT 0',
545
+ 'ALTER TABLE orders ADD COLUMN decline_contest_deadline TEXT',
546
+ // RFC-007 stage 5:卖家已就临时判责发起仲裁举证 → 暂停 checkTimeouts 自动终结,等人工仲裁裁决。
547
+ 'ALTER TABLE orders ADD COLUMN decline_contested INTEGER DEFAULT 0',
537
548
  ]) {
538
549
  try {
539
550
  db.exec(stmt);
@@ -778,6 +789,13 @@ const DEFAULT_PARAMS = [
778
789
  { key: 'fund_base_rate', value: '0', type: 'number', description: '协议基金池基础费率(RFC-008 硬帽 1%;pre-launch 减免=0,有真实 GMV 再由治理开启 ≤1%)', category: 'fee', min: 0, max: 0.01 },
779
790
  // RFC-008:起步免赔付门槛。0 = bootstrap(新商家零质押、违约免赔付只退款+掉信誉,降进入门槛);1 = 要求卖家质押(下单锁 stake、违约真没收)。上轨道后由治理开启。
780
791
  { key: 'require_seller_stake', value: '0', type: 'number', description: 'RFC-008 是否要求卖家质押(0=起步免赔付/零门槛,1=要求质押/真没收)', category: 'fee', min: 0, max: 1 },
792
+ // RFC-008 stage 2:违约罚没率,【与质押率解耦】(低质押=低摩擦 + 高罚没=强威慑,单一费率做不到)。
793
+ // 背书订单:penalty = fault_penalty_rate × total,先扣 staked(封顶背书)再扣自由 balance(责任自负,真可执行)。
794
+ // 起步免赔付(stake_backing=0):仍 0 没收,绝不碰新商家自由余额。settleFault 按订单 stake_backing 判定。
795
+ { key: 'fault_penalty_rate', value: '0.30', type: 'number', description: 'RFC-008 违约罚没率(与质押率解耦;背书订单 staked 不足扣自由 balance;起步免赔付订单不适用)', category: 'fee', min: 0, max: 0.50 },
796
+ // RFC-007 stage 3:客观理由拒单的【举证窗口】小时数。卖家声称客观无责拒单 → 临时判责,此窗口内可开仲裁(stage 5)举证;
797
+ // 到期无人仲裁 → 自动终结为违约。窗口内买家 escrow 暂不退(随终结/翻案一次性结算),0=不给窗口(直接违约)。
798
+ { key: 'decline_contest_window_hours', value: '24', type: 'number', description: 'RFC-007 客观拒单举证窗口(小时);到期未仲裁则终结为违约', category: 'limit', min: 0, max: 168 },
781
799
  { key: 'checkin_base_reward', value: '0.5', type: 'number', description: '每日签到基础奖励 WAZ', category: 'reward', min: 0, max: 10 },
782
800
  { key: 'streak_bonus_7', value: '5', type: 'number', description: '7 天里程碑额外奖励', category: 'reward', min: 0, max: 100 },
783
801
  { key: 'streak_bonus_30', value: '20', type: 'number', description: '30 天里程碑额外奖励', category: 'reward', min: 0, max: 500 },
@@ -5160,23 +5178,19 @@ function issueAgentStrike(opts) {
5160
5178
  void issueAgentStrike;
5161
5179
  function getAgentRateCap(level) {
5162
5180
  const key = `agent_rate_${level}_per_min`;
5163
- return Number(getProtocolParam(key, level === 'legend' ? 1200 : level === 'quality' ? 600 : level === 'trusted' ? 300 : 120));
5181
+ return Number(getProtocolParam(key, AGENT_RATE_PER_MIN_DEFAULTS[level] ?? AGENT_RATE_PER_MIN_DEFAULTS.new));
5164
5182
  }
5165
5183
  // 2026-05-23 P1 fix 2.4:mass-action 日 cap(按 trust level + action 类别)
5166
5184
  // 拦截 spam / 信息轰炸:chat / comment / share 三类 social-write 操作有独立的日上限
5167
5185
  // 触发:超 cap → 429 AGENT_DAILY_CAP;累计 ≥3 次超限 / 24h → issueAgentStrike(warning)
5168
- const MASS_ACTION_TYPES = new Set(['chat', 'comment', 'share']);
5169
- const massActionDailyCaps = {
5170
- // [action][level] = daily cap
5171
- chat: { new: 30, trusted: 100, quality: 300, legend: 1000 },
5172
- comment: { new: 20, trusted: 60, quality: 150, legend: 500 },
5173
- share: { new: 10, trusted: 30, quality: 100, legend: 300 },
5174
- };
5186
+ // 限额表抽到 ./limits.ts(单一真相源,与 RFC-011 §② negative-space 发布面共享)
5187
+ const MASS_ACTION_TYPES_SET = new Set(MASS_ACTION_TYPES);
5188
+ const massActionDailyCaps = MASS_ACTION_DAILY_CAPS;
5175
5189
  // in-memory 日窗口 counter(不持久化,重启清零;持久化需求未来用 agent_call_log 算)
5176
5190
  const massActionDailyBuckets = new Map();
5177
5191
  function todayKey() { return new Date().toISOString().slice(0, 10); }
5178
5192
  function checkMassActionCap(apiKey, action, level) {
5179
- if (!MASS_ACTION_TYPES.has(action))
5193
+ if (!MASS_ACTION_TYPES_SET.has(action))
5180
5194
  return { ok: true };
5181
5195
  const cap = (massActionDailyCaps[action] || {})[level] ?? massActionDailyCaps[action]?.new ?? 999999;
5182
5196
  const today = todayKey();
@@ -5207,13 +5221,7 @@ function checkMassActionCap(apiKey, action, level) {
5207
5221
  // · 防"枚举/扒数据"型读取(剽窃 / 内容农场 / 用户画像批量化)
5208
5222
  // · 真人监护人(绑 Passkey) 也罩:audit "补 A" — 之前 Passkey 真人无任何 read cap,scraper 拿真人账号即绕过
5209
5223
  // · 不打 /api/nearby /api/search(地理聚合 / 关键词查),那些已被 B1 read-scope 约束
5210
- const CROSS_USER_READ_DAILY_CAP = {
5211
- passkey_human: 300, // 真人监护人 — 高但有上限(每天看 300 个不同用户已属异常使用)
5212
- legend: 200, // 高信誉 agent
5213
- quality: 100,
5214
- trusted: 60,
5215
- new: 30, // 新 agent — 30 个就该有声誉再说
5216
- };
5224
+ // CROSS_USER_READ_DAILY_CAP 抽到 ./limits.ts(单一真相源)。
5217
5225
  const crossUserReadBuckets = new Map();
5218
5226
  function extractCrossUserTarget(path, currentUserId) {
5219
5227
  // /api/users/:id 或 /api/users/:id/anything → :id;同 user 不算跨;sys_* 协议账号不算
@@ -5248,107 +5256,10 @@ function checkCrossUserReadCap(userId, targetId, level) {
5248
5256
  }
5249
5257
  // 2026-05-23 P0 audit fix 2.2:endpoint → action 映射(用于 declared_scope enforcement)
5250
5258
  // 只对写操作 enforce;读操作不限制(任何 agent 都能读自己范围内的数据)
5251
- function endpointToAction(method, path) {
5252
- if (method === 'GET')
5253
- return null;
5254
- if (method === 'POST' && path === '/api/orders')
5255
- return 'place_order';
5256
- if ((method === 'POST' || method === 'PUT') && /^\/api\/products(\/[^/]+)?$/.test(path))
5257
- return 'list_product';
5258
- if (method === 'POST' && /^\/api\/orders\/[^/]+\/(accept|ship|deliver|pickup|transit)/.test(path))
5259
- return 'fulfill';
5260
- if (method === 'POST' && /^\/api\/orders\/[^/]+\/confirm/.test(path))
5261
- return 'confirm_order';
5262
- if (method === 'POST' && /^\/api\/claim-tasks\/[^/]+\/vote/.test(path))
5263
- return 'vote';
5264
- if (method === 'POST' && /^\/api\/disputes\/[^/]+\/arbitrate/.test(path))
5265
- return 'arbitrate';
5266
- if (method === 'POST' && /^\/api\/disputes\/[^/]+\/respond/.test(path))
5267
- return 'dispute_respond';
5268
- if (method === 'POST' && /^\/api\/charity\/fund\/donate/.test(path))
5269
- return 'donate';
5270
- if (method === 'POST' && /^\/api\/(wishes|charity)/.test(path))
5271
- return 'charity';
5272
- if (method === 'POST' && /^\/api\/shareables/.test(path))
5273
- return 'share';
5274
- if (method === 'POST' && /^\/api\/conversations/.test(path))
5275
- return 'chat';
5276
- if (method === 'POST' && path === '/api/skills')
5277
- return 'list_skill';
5278
- if (method === 'POST' && /^\/api\/rfqs/.test(path))
5279
- return 'rfq';
5280
- if (method === 'POST' && /^\/api\/auctions\/[^/]+\/bid/.test(path))
5281
- return 'bid';
5282
- // 权限审计 #1115 P0:花钱/价值写纳入问责门(与 place_order 同档:无 Passkey 须声明 scope)。
5283
- // 之前这些漏在 default-allow 之外,无声明无 Passkey 的 agent 可花掉账户余额 —— 补齐一致性。
5284
- if (method === 'POST' && /^\/api\/skill-market\/[^/]+\/purchase/.test(path))
5285
- return 'purchase';
5286
- if (method === 'POST' && /^\/api\/secondhand\/[^/]+\/order/.test(path))
5287
- return 'buy_secondhand';
5288
- if (method === 'POST' && /^\/api\/group-buys\/[^/]+\/join/.test(path))
5289
- return 'group_buy_join';
5290
- // #1115 P1:写 PII(收货地址)也需问责。含 addresses 增删改 + profile 默认地址。
5291
- if (method !== 'GET' && (/^\/api\/addresses(\/|$)/.test(path) || path === '/api/profile/default-address'))
5292
- return 'set_address';
5293
- // #1115 P2:钱包写(转出/充值/白名单/连接)统一纳入 'wallet' 问责门。
5294
- // 之前 wallet/* 整段在 SAFE 放行 → api_key agent 可直达 withdraw,小额零真人门分批盗刷;
5295
- // 现要求声明 'wallet' scope(或 '*' / Passkey)。withdraw 另在 handler 内强制 Passkey 真人门(铁律,见 wallet-write.ts)。
5296
- if (method !== 'GET' && /^\/api\/wallet\//.test(path))
5297
- return 'wallet';
5298
- // #1115 P2:profile **PII/身份/接管向量**写纳入 'set_profile' —— 仅这一子集需问责。
5299
- // 改恢复邮箱(bind/confirm-email)=账户接管;改 handle/name=身份;set/clear-location=地理 PII。
5300
- // ⚠️ 不一刀切整段 profile/*:role/region/placement/password/verify 是"无 Passkey 也要能用"的
5301
- // 自助操作(见原则:没身份验证也要解决的问题不要求身份),它们留在下方 SAFE。
5302
- // default-address 已在上面归 set_address(更具体,优先)。
5303
- if (method !== 'GET' && /^\/api\/profile\/(bind-email|confirm-email|change-handle|change-name|set-location|clear-location)$/.test(path))
5304
- return 'set_profile';
5305
- // ── #1115 B4 结构性:default-deny ──────────────────────────────
5306
- // 上面是细粒度命名 token;下面 safe-list 放行(返回 null),其余一切写 → 'write'(需 Passkey 或声明 scope)。
5307
- // 目的:新增的敏感写默认就被门控,不会因"忘了加映射"而裸奔(把 default-allow 翻成 default-deny)。
5308
- // safe-list 三类必须放行(否则误伤 / 死锁):
5309
- // (a) 脱困入口:绑 Passkey(webauthn)/ 声明 scope(me/agents)/ 登录注册/找回 —— 若门控则永远无法获得通行资格
5310
- // (b) 自带门:build-feedback(分级门)/ admin(requireAdmin)。
5311
- // 注意:wallet / profile 不再 SAFE —— #1115 P2 移到上方命名 token(wallet / set_profile / set_address)。
5312
- // (c) 公开 + 低值自我状态写(管理自己的 cart/wishlist/通知/关注 等,登录即可)
5313
- const SAFE = [
5314
- // (a) 脱困 / 鉴权 / 身份(bootstrap 通行资格所必需 —— wallet 与 profile 的 PII 子集不在此列,见上方命名 token)
5315
- /^\/api\/(login|register)$/, /^\/api\/recover-key/, /^\/api\/webauthn\//,
5316
- /^\/api\/me\/agents\//,
5317
- // profile 自助子集(无 Passkey 真人也要能用:切角色/选地区/PV 配置/改密码/二次确认)。
5318
- // PII/身份子集(bind-email/change-handle/set-location 等)已在上方归 set_profile,不在此放行。
5319
- /^\/api\/profile\/(switch-role|add-role|region|placement-pref|bind-placement|feed-visible|verify-password|set-password|remove-password)$/,
5320
- // (b) 自带门(build-feedback=分级门;build-tasks=协调层登录即可;admin=requireAdmin。wallet/profile 已移到上方命名 token #1115 P2)
5321
- /^\/api\/build-feedback/, /^\/api\/build-tasks/, /^\/api\/admin\//,
5322
- // (c) 公开 / 无需登录
5323
- /^\/api\/(public-ideas|error-report|mcp-telemetry|email-subscriptions|search-by-link|feedback)(\/|$)/,
5324
- // (c) 低值自我状态
5325
- /^\/api\/cart$/, /^\/api\/cart\/(?!checkout)[^/]+$/,
5326
- /^\/api\/wishlist/, /^\/api\/products\/[^/]+\/waitlist$/,
5327
- /^\/api\/notifications\/read$/, /^\/api\/announcements\/[^/]+\/read$/,
5328
- /^\/api\/follows\//, /^\/api\/blocklist\//,
5329
- /^\/api\/checkin$/, /^\/api\/growth\/tasks\//, /^\/api\/tasks\/[^/]+\/claim$/,
5330
- /^\/api\/push\//, /^\/api\/auth\//,
5331
- /^\/api\/me\/(delete-cancel|notify-claim-tasks)/,
5332
- /^\/api\/peers\//, /^\/api\/signaling\//,
5333
- /^\/api\/product-share\/touch$/, /^\/api\/anchor\/[^/]+\/touch$/,
5334
- /^\/api\/reviews\//,
5335
- ];
5336
- if (SAFE.some(r => r.test(path)))
5337
- return null;
5338
- return 'write'; // 默认拒绝:其余写需问责
5339
- }
5340
- // Phase 3b(B1):敏感读 → read-scope token 映射。只挑「跨用户聚合 / 批量扫描」这类剽窃向读取,
5341
- // 普通读(自己的 profile / products 列表 / 订单等)一律 null 不约束,避免误伤声明 agent 的日常读。
5342
- // 仅对「有声明」的 agent 生效(见中间件);真人 / 无声明 / 通配 '*' 都不受此约束。
5343
- function endpointToReadAction(path) {
5344
- if (/^\/api\/nearby/.test(path))
5345
- return 'search'; // 雷达扫描(地理聚合)
5346
- if (/^\/api\/search/.test(path))
5347
- return 'search'; // 模糊搜索深翻页
5348
- if (/^\/api\/users\/[^/]+\//.test(path))
5349
- return 'profile'; // 他人主页 / 信誉 / 内容流(枚举剽窃向)
5350
- return null;
5351
- }
5259
+ // RFC-011 §②:写边界分类器 + 敏感读 scope 已抽到 src/pwa/endpoint-actions.ts(声明式规则表,
5260
+ // 同一份规则既 enforce(此处中间件迭代)又 publish(capabilityMatrix 端点),doc=code。
5261
+ // 行为零变化由 tests/test-endpoint-actions.ts 锁定(420 组 path×method diff legacy)。
5262
+ // endpointToAction / endpointToReadAction 现从该模块 import(见文件顶部)
5352
5263
  function getDeclaredActions(apiKey) {
5353
5264
  const row = db.prepare(`SELECT declared_scope FROM agent_declarations WHERE api_key = ? AND revoked_at IS NULL`).get(apiKey);
5354
5265
  if (!row)
@@ -7605,7 +7516,7 @@ registerOrdersReadRoutes(app, { db, auth, getOrderStatus, getOrderChain, verifyO
7605
7516
  // 注意:settleOrder 是函数声明(hoisted)但绑了 db 闭包,无需注入 db
7606
7517
  registerOrdersActionRoutes(app, {
7607
7518
  db, auth, isTrustedRole, generateId, transition, notifyTransition,
7608
- settleOrder, detectFraud, createDispute, checkTimeouts, recordViolationReputation,
7519
+ settleOrder, settleFault, detectFraud, createDispute, checkTimeouts, recordViolationReputation,
7609
7520
  broadcastSystemEvent,
7610
7521
  });
7611
7522
  // #1013 Phase 85: POST /api/orders 巨型事务已迁出
@@ -0,0 +1,63 @@
1
+ /**
2
+ * RFC-011 §⑤ 可验证索引 —— 一份"什么可验 + 怎么验"的总表(护照/锚/AP2/订单链散在四处,这里统一)。
3
+ * 诚实分级(不可过度声明):
4
+ * - 护照 / 外部锚:公开可验(任何第三方离线 ecrecover / 验签),强。
5
+ * - AP2 Mandate:签名输出,可验。
6
+ * - 订单事件链:HMAC 是 actor 私钥 → 第三方【无法】验签;可验的是【哈希链连续性】(防篡改),且 party-gated。
7
+ * 只【链接 + 说明】how-to,不嵌密钥(密钥 live 发布在 did.json / protocol-status issuers),doc=code 不漂移。
8
+ */
9
+ import { SOFTWARE_VERSION, CONTRACT_VERSION } from '../version.js';
10
+ const BASE = 'https://webaz.xyz';
11
+ export function buildVerifiabilityIndex() {
12
+ return {
13
+ contract_version: CONTRACT_VERSION,
14
+ software_version: SOFTWARE_VERSION,
15
+ note: 'RFC-011 §⑤. Each artifact lists what it proves, the scheme, how to verify, and its verifiability LEVEL — do not over-trust beyond the stated level. Issuer keys are published live (not embedded here): /.well-known/did.json + /.well-known/webaz-protocol.json#issuers.',
16
+ levels: {
17
+ public_signature: 'any third party verifies offline (ecrecover / sig-check) without calling WebAZ',
18
+ public_endpoint: 'verifiable via a public WebAZ endpoint (no auth)',
19
+ integrity_chain: 'tamper-evidence via a hash-chain (verify continuity); NOT a third-party-verifiable signature',
20
+ party_gated: 'full data only to order parties; others get integrity, not contents',
21
+ },
22
+ artifacts: [
23
+ {
24
+ artifact: 'agent_passport',
25
+ proves: 'an agent\'s custodian fingerprint + risk/engagement/behavior, signed by the WebAZ issuer key',
26
+ scheme: 'eip191 (EIP-191 personal_sign)',
27
+ level: 'public_signature',
28
+ offline: true,
29
+ how_to_verify: 'GET /api/me/agents/:apiKeyPrefix/passport → ecrecover(passport.canonical, passport.signature) == issuer address from /.well-known/did.json (CAIP-10) / /.well-known/webaz-protocol.json#issuers; check active_since/revoked_at window.',
30
+ endpoint: `${BASE}/api/me/agents/:apiKeyPrefix/passport`,
31
+ keys: `${BASE}/.well-known/did.json`,
32
+ },
33
+ {
34
+ artifact: 'external_anchor',
35
+ proves: 'a real-world item\'s ownership/authenticity anchor + independent verifier attestations',
36
+ scheme: 'signature (server-verifiable)',
37
+ level: 'public_endpoint',
38
+ offline: false,
39
+ how_to_verify: `GET ${BASE}/api/external-anchors/:id/verify-sig (public); cross-check verifier attestations via /api/external-anchors/:id.`,
40
+ endpoint: `${BASE}/api/external-anchors/:id/verify-sig`,
41
+ },
42
+ {
43
+ artifact: 'ap2_mandate',
44
+ proves: 'a buyer\'s signed Intent/Cart/Payment Mandate (AP2) emitted alongside the webaz price/order format',
45
+ scheme: 'AP2 signed mandate',
46
+ level: 'public_signature',
47
+ offline: true,
48
+ how_to_verify: 'verify the AP2 mandate signature per the AP2 spec; emitted by webaz_verify_price + webaz_place_order (dual-output).',
49
+ endpoint: 'returned inline by verify_price / place_order',
50
+ },
51
+ {
52
+ artifact: 'order_event_chain',
53
+ proves: 'the order/dispute transition history is append-only + tamper-evident (each event hash chains to the previous)',
54
+ scheme: 'sha256 hash-chain (event_hash / prev_event_hash). NOTE: the per-event `signature` is an HMAC with the actor\'s api_key — NOT third-party verifiable; the verifiable property is the HASH-CHAIN continuity.',
55
+ level: 'integrity_chain',
56
+ offline: true,
57
+ party_gated: true,
58
+ how_to_verify: `For an order you are party to: GET ${BASE}/api/orders/:id/chain (returns chain + verification). Or stream events via ${BASE}/api/agent/events (§⑥): check each event\'s prev_event_hash == the previous event\'s event_hash. Continuity proves no insert/delete/reorder.`,
59
+ endpoint: `${BASE}/api/orders/:id/chain`,
60
+ },
61
+ ],
62
+ };
63
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Single source of truth for WebAZ version axes — RFC-011 §④ (version hygiene).
3
+ *
4
+ * TWO DISTINCT AXES. Do not conflate them — conflating is exactly the dirt this file kills
5
+ * (the old hardcoded MCP `SERVER_VERSION='0.1.8'` + Server handshake `version:'0.1.0'` drifted
6
+ * away from the real package version 0.1.19; this makes that structurally impossible).
7
+ *
8
+ * 1. SOFTWARE_VERSION — npm / release semver of THIS codebase (MCP client+server + PWA).
9
+ * Read from package.json at runtime so it can NEVER drift. Bumps on every release.
10
+ * Resolves from both dev (tsx src/version.ts → ../package.json) and prod/published
11
+ * (node dist/version.js → ../package.json), since package.json sits at the package root
12
+ * and is always included in the npm tarball.
13
+ *
14
+ * 2. CONTRACT_VERSION — the agent-native INTEGRATION CONTRACT version (manifest `schema_version`).
15
+ * A deliberate integer that integrators' agents key off. Bump ONLY on a *breaking* change to
16
+ * the data contract they read (entity shape / boundary / verifiable-field semantics).
17
+ * INDEPENDENT of software releases — a patch/feature release does NOT bump it; a breaking
18
+ * contract change DOES, even within the same software version. Governs RFC-011's contract.
19
+ *
20
+ * Both are surfaced to integrators in /.well-known/webaz-protocol.json so an agent can read
21
+ * "contract vN running software x.y.z" and decide compatibility.
22
+ */
23
+ import { createRequire } from 'node:module';
24
+ const require = createRequire(import.meta.url);
25
+ const pkg = require('../package.json');
26
+ /** npm/release semver — single source = package.json. Never hardcode a copy of this. */
27
+ export const SOFTWARE_VERSION = pkg.version;
28
+ /** Integration-contract version. Bump on ANY integrator-observable contract-surface change
29
+ * (capability matrix §② / entity dictionary §①); the CONTRACT_CHANGES `kind` classifies whether
30
+ * it is breaking. Additive changes (kind:'added') are safe for agents to ignore. Guarded by
31
+ * tests/test-contract-fingerprint.ts + docs/CONTRACT-LOCK.json. */
32
+ export const CONTRACT_VERSION = 2;