@seasonkoh/webaz 0.1.24 → 0.1.26
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/README.md +5 -1
- package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
- package/dist/layer0-foundation/L0-1-database/db.js +65 -0
- package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
- package/dist/layer1-agent/L1-1-mcp-server/server.js +288 -208
- package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
- package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
- package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
- package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
- package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +182 -0
- package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
- package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
- package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +11 -3
- package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
- package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
- package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
- package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
- package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
- package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
- package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
- package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +191 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
- package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
- package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
- package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
- package/dist/pwa/acp-feed.js +13 -1
- package/dist/pwa/admin-bearer-auth.js +21 -0
- package/dist/pwa/contract-fingerprint.js +2 -0
- package/dist/pwa/email-delivery.js +127 -0
- package/dist/pwa/endpoint-actions.js +5 -1
- package/dist/pwa/goal-index.js +8 -8
- package/dist/pwa/human-presence.js +62 -0
- package/dist/pwa/public/app.js +1485 -283
- package/dist/pwa/public/i18n.js +297 -59
- package/dist/pwa/public/index.html +1 -0
- package/dist/pwa/public/openapi.json +5 -5
- package/dist/pwa/public/whitepaper/en/index.html +153 -0
- package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
- package/dist/pwa/rate-limit.js +22 -0
- package/dist/pwa/routes/account-deletion.js +15 -13
- package/dist/pwa/routes/addresses.js +10 -9
- package/dist/pwa/routes/admin-admins.js +13 -14
- package/dist/pwa/routes/admin-analytics.js +109 -69
- package/dist/pwa/routes/admin-atomic.js +10 -4
- package/dist/pwa/routes/admin-catalog.js +13 -11
- package/dist/pwa/routes/admin-editor-picks.js +15 -10
- package/dist/pwa/routes/admin-events.js +5 -3
- package/dist/pwa/routes/admin-health.js +2 -1
- package/dist/pwa/routes/admin-moderation.js +50 -29
- package/dist/pwa/routes/admin-ops.js +35 -23
- package/dist/pwa/routes/admin-protocol-params.js +16 -19
- package/dist/pwa/routes/admin-reports.js +23 -21
- package/dist/pwa/routes/admin-tokenomics.js +26 -25
- package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
- package/dist/pwa/routes/admin-users-query.js +65 -53
- package/dist/pwa/routes/admin-verifier-flow.js +82 -41
- package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
- package/dist/pwa/routes/admin-wallet-ops.js +32 -7
- package/dist/pwa/routes/agent-buy.js +46 -22
- package/dist/pwa/routes/agent-governance.js +52 -56
- package/dist/pwa/routes/ai.js +7 -5
- package/dist/pwa/routes/analytics.js +43 -41
- package/dist/pwa/routes/anchors.js +19 -20
- package/dist/pwa/routes/announcements.js +13 -13
- package/dist/pwa/routes/arbitrator.js +97 -31
- package/dist/pwa/routes/auction.js +157 -116
- package/dist/pwa/routes/auth-login.js +6 -4
- package/dist/pwa/routes/auth-read.js +21 -10
- package/dist/pwa/routes/auth-register.js +111 -26
- package/dist/pwa/routes/auth-sessions.js +12 -11
- package/dist/pwa/routes/blocklist.js +16 -15
- package/dist/pwa/routes/build-feedback.js +10 -9
- package/dist/pwa/routes/build-reputation.js +6 -2
- package/dist/pwa/routes/build-tasks.js +45 -13
- package/dist/pwa/routes/buyer-feeds.js +27 -25
- package/dist/pwa/routes/cart.js +16 -15
- package/dist/pwa/routes/charity.js +212 -150
- package/dist/pwa/routes/chat.js +42 -43
- package/dist/pwa/routes/checkin-tasks.js +10 -9
- package/dist/pwa/routes/checkout-helpers.js +12 -10
- package/dist/pwa/routes/claim-initiators.js +34 -14
- package/dist/pwa/routes/claim-verify.js +86 -53
- package/dist/pwa/routes/claim-voting.js +43 -18
- package/dist/pwa/routes/contribution-identity.js +164 -0
- package/dist/pwa/routes/contribution-score.js +19 -0
- package/dist/pwa/routes/coupons.js +19 -16
- package/dist/pwa/routes/dashboards.js +18 -16
- package/dist/pwa/routes/dispute-cases.js +25 -24
- package/dist/pwa/routes/disputes-read.js +45 -51
- package/dist/pwa/routes/disputes-write.js +124 -61
- package/dist/pwa/routes/evidence.js +9 -9
- package/dist/pwa/routes/external-anchors.js +13 -12
- package/dist/pwa/routes/feedback.js +29 -33
- package/dist/pwa/routes/flash-sales.js +18 -16
- package/dist/pwa/routes/follows.js +25 -24
- package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
- package/dist/pwa/routes/governance-onboarding.js +70 -59
- package/dist/pwa/routes/group-buys.js +22 -22
- package/dist/pwa/routes/growth.js +34 -31
- package/dist/pwa/routes/import-product.js +12 -10
- package/dist/pwa/routes/kyc.js +9 -8
- package/dist/pwa/routes/leaderboard.js +20 -18
- package/dist/pwa/routes/listings.js +23 -22
- package/dist/pwa/routes/logistics.js +10 -8
- package/dist/pwa/routes/manifests.js +27 -27
- package/dist/pwa/routes/me-data.js +23 -21
- package/dist/pwa/routes/notifications.js +7 -6
- package/dist/pwa/routes/offers.js +30 -12
- package/dist/pwa/routes/orders-action.js +51 -29
- package/dist/pwa/routes/orders-create.js +75 -20
- package/dist/pwa/routes/orders-read.js +21 -20
- package/dist/pwa/routes/p2p-products.js +30 -18
- package/dist/pwa/routes/payments-governance.js +61 -56
- package/dist/pwa/routes/peers.js +9 -8
- package/dist/pwa/routes/pin-receipts.js +13 -13
- package/dist/pwa/routes/products-aliases.js +12 -10
- package/dist/pwa/routes/products-claims.js +36 -17
- package/dist/pwa/routes/products-create.js +53 -38
- package/dist/pwa/routes/products-crud.js +17 -16
- package/dist/pwa/routes/products-links.js +49 -26
- package/dist/pwa/routes/products-list.js +6 -4
- package/dist/pwa/routes/products-meta.js +40 -39
- package/dist/pwa/routes/products-update.js +19 -5
- package/dist/pwa/routes/profile-credentials.js +20 -19
- package/dist/pwa/routes/profile-identity.js +14 -13
- package/dist/pwa/routes/profile-location.js +7 -6
- package/dist/pwa/routes/profile-placement.js +20 -19
- package/dist/pwa/routes/profile-prefs.js +11 -11
- package/dist/pwa/routes/promoter.js +58 -66
- package/dist/pwa/routes/public-build-tasks.js +19 -0
- package/dist/pwa/routes/public-utils.js +108 -46
- package/dist/pwa/routes/push.js +16 -15
- package/dist/pwa/routes/ratings.js +92 -32
- package/dist/pwa/routes/recover-key.js +66 -26
- package/dist/pwa/routes/referral.js +37 -52
- package/dist/pwa/routes/reputation.js +3 -2
- package/dist/pwa/routes/returns.js +76 -73
- package/dist/pwa/routes/reviews.js +41 -18
- package/dist/pwa/routes/rewards-apply.js +16 -15
- package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
- package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
- package/dist/pwa/routes/rfqs.js +163 -85
- package/dist/pwa/routes/search.js +16 -14
- package/dist/pwa/routes/secondhand.js +25 -22
- package/dist/pwa/routes/seller-quota.js +24 -26
- package/dist/pwa/routes/share-redirects.js +60 -55
- package/dist/pwa/routes/shareables-interactions.js +34 -35
- package/dist/pwa/routes/shareables.js +55 -51
- package/dist/pwa/routes/shop-referral.js +58 -0
- package/dist/pwa/routes/shops.js +25 -20
- package/dist/pwa/routes/signaling.js +10 -9
- package/dist/pwa/routes/skill-market.js +16 -16
- package/dist/pwa/routes/skills.js +15 -14
- package/dist/pwa/routes/snf.js +14 -13
- package/dist/pwa/routes/tags.js +10 -9
- package/dist/pwa/routes/task-proposals.js +121 -0
- package/dist/pwa/routes/trial.js +72 -52
- package/dist/pwa/routes/trusted-kpi.js +20 -18
- package/dist/pwa/routes/url-claim.js +67 -28
- package/dist/pwa/routes/users-public.js +62 -70
- package/dist/pwa/routes/variants.js +12 -13
- package/dist/pwa/routes/verifier-user.js +61 -21
- package/dist/pwa/routes/verify-tasks.js +49 -25
- package/dist/pwa/routes/waitlist.js +16 -15
- package/dist/pwa/routes/wallet-read.js +75 -37
- package/dist/pwa/routes/wallet-write.js +12 -9
- package/dist/pwa/routes/webauthn.js +25 -26
- package/dist/pwa/routes/webhooks.js +26 -26
- package/dist/pwa/routes/welcome.js +45 -50
- package/dist/pwa/routes/wishlist-qa.js +29 -32
- package/dist/pwa/server.js +304 -90
- package/dist/version.js +1 -1
- package/package.json +76 -3
|
@@ -1,40 +1,43 @@
|
|
|
1
|
+
import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerAdminVerifierWhitelistRoutes(app, deps) {
|
|
3
|
+
// 单写白名单管理站点走 RFC-016 异步 seam;db 保留:revoke 是没收/退质押 + 白名单重写的
|
|
4
|
+
// 多写资金路径,必须原子(db.transaction + 防重复没收 guard),Phase 3 迁 pg 行锁。
|
|
2
5
|
const { db, requireVerifierMgmtAdmin, adminCanOperateOn, logAdminAction, INTERNAL_AUDITOR_ID, TIER_QUOTAS, REVOKE_COOLDOWN_DAYS } = deps;
|
|
3
|
-
app.get('/api/admin/verifier-whitelist', (req, res) => {
|
|
6
|
+
app.get('/api/admin/verifier-whitelist', async (req, res) => {
|
|
4
7
|
const user = requireVerifierMgmtAdmin(req, res);
|
|
5
8
|
if (!user)
|
|
6
9
|
return;
|
|
7
|
-
const list =
|
|
10
|
+
const list = await dbAll(`
|
|
8
11
|
SELECT vw.user_id, vw.added_at, vw.note, u.name, u.role
|
|
9
12
|
FROM verifier_whitelist vw
|
|
10
13
|
JOIN users u ON u.id = vw.user_id
|
|
11
14
|
ORDER BY vw.added_at ASC
|
|
12
|
-
`)
|
|
15
|
+
`);
|
|
13
16
|
res.json(list);
|
|
14
17
|
});
|
|
15
|
-
app.post('/api/admin/verifier-whitelist', (req, res) => {
|
|
18
|
+
app.post('/api/admin/verifier-whitelist', async (req, res) => {
|
|
16
19
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
17
20
|
if (!admin)
|
|
18
21
|
return;
|
|
19
22
|
const { user_id, name, note } = req.body;
|
|
20
23
|
let targetId = user_id;
|
|
21
24
|
if (!targetId && name) {
|
|
22
|
-
const found =
|
|
25
|
+
const found = await dbOne('SELECT id FROM users WHERE name = ?', [name]);
|
|
23
26
|
if (!found)
|
|
24
27
|
return void res.json({ error: `用户「${name}」不存在` });
|
|
25
28
|
targetId = found.id;
|
|
26
29
|
}
|
|
27
30
|
if (!targetId)
|
|
28
31
|
return void res.json({ error: '请提供 user_id 或 name' });
|
|
29
|
-
const target =
|
|
32
|
+
const target = await dbOne('SELECT id, name FROM users WHERE id = ?', [targetId]);
|
|
30
33
|
if (!target)
|
|
31
34
|
return void res.json({ error: '用户不存在' });
|
|
32
35
|
if (!adminCanOperateOn(admin, targetId, res))
|
|
33
36
|
return;
|
|
34
|
-
|
|
37
|
+
await dbRun('INSERT OR IGNORE INTO verifier_whitelist (user_id, note) VALUES (?, ?)', [targetId, note ?? null]);
|
|
35
38
|
res.json({ success: true, user_id: targetId, name: target.name });
|
|
36
39
|
});
|
|
37
|
-
app.delete('/api/admin/verifier-whitelist/:userId', (req, res) => {
|
|
40
|
+
app.delete('/api/admin/verifier-whitelist/:userId', async (req, res) => {
|
|
38
41
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
39
42
|
if (!admin)
|
|
40
43
|
return;
|
|
@@ -43,10 +46,10 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
|
|
|
43
46
|
return void res.json({ error: '内部审核员不可移除' });
|
|
44
47
|
if (!adminCanOperateOn(admin, targetId, res))
|
|
45
48
|
return;
|
|
46
|
-
|
|
49
|
+
await dbRun('DELETE FROM verifier_whitelist WHERE user_id = ?', [targetId]);
|
|
47
50
|
res.json({ success: true });
|
|
48
51
|
});
|
|
49
|
-
app.post('/api/admin/verifier-whitelist/:userId/promote', (req, res) => {
|
|
52
|
+
app.post('/api/admin/verifier-whitelist/:userId/promote', async (req, res) => {
|
|
50
53
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
51
54
|
if (!admin)
|
|
52
55
|
return;
|
|
@@ -56,17 +59,16 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
|
|
|
56
59
|
if (!TIER_QUOTAS[tier])
|
|
57
60
|
return void res.json({ error: 'tier 无效' });
|
|
58
61
|
const targetId = req.params.userId;
|
|
59
|
-
const wl =
|
|
62
|
+
const wl = await dbOne("SELECT is_system FROM verifier_whitelist WHERE user_id = ?", [targetId]);
|
|
60
63
|
if (!wl)
|
|
61
64
|
return void res.json({ error: '该用户不在白名单' });
|
|
62
65
|
if (wl.is_system)
|
|
63
66
|
return void res.json({ error: '系统兜底账户不可手动 promote' });
|
|
64
|
-
|
|
65
|
-
.run(tier, TIER_QUOTAS[tier], targetId);
|
|
67
|
+
await dbRun("UPDATE verifier_whitelist SET tier = ?, daily_quota = ? WHERE user_id = ?", [tier, TIER_QUOTAS[tier], targetId]);
|
|
66
68
|
logAdminAction(admin.id, 'promote_verifier', 'user', targetId, { tier });
|
|
67
69
|
res.json({ success: true });
|
|
68
70
|
});
|
|
69
|
-
app.post('/api/admin/verifier-whitelist/:userId/suspend', (req, res) => {
|
|
71
|
+
app.post('/api/admin/verifier-whitelist/:userId/suspend', async (req, res) => {
|
|
70
72
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
71
73
|
if (!admin)
|
|
72
74
|
return;
|
|
@@ -76,12 +78,12 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
|
|
|
76
78
|
const targetId = req.params.userId;
|
|
77
79
|
const n = Number(days) > 0 ? Number(days) : 7;
|
|
78
80
|
const until = new Date(Date.now() + n * 86400_000).toISOString();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
await dbRun("INSERT OR IGNORE INTO verifier_stats (user_id) VALUES (?)", [targetId]);
|
|
82
|
+
await dbRun("UPDATE verifier_stats SET suspended_until = ? WHERE user_id = ?", [until, targetId]);
|
|
81
83
|
logAdminAction(admin.id, 'suspend_verifier', 'user', targetId, { days: n, reason: reason || null, until });
|
|
82
84
|
res.json({ success: true, suspended_until: until });
|
|
83
85
|
});
|
|
84
|
-
app.post('/api/admin/verifier-whitelist/:userId/revoke', (req, res) => {
|
|
86
|
+
app.post('/api/admin/verifier-whitelist/:userId/revoke', async (req, res) => {
|
|
85
87
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
86
88
|
if (!admin)
|
|
87
89
|
return;
|
|
@@ -89,22 +91,48 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
|
|
|
89
91
|
return;
|
|
90
92
|
const { reason } = req.body;
|
|
91
93
|
const targetId = req.params.userId;
|
|
92
|
-
|
|
94
|
+
// 友好预检查(读);真正的守恒 + 防重复没收门在事务内(重读 active 行 + cooldown guard)。
|
|
95
|
+
const wl = await dbOne("SELECT is_system, stake_amount FROM verifier_whitelist WHERE user_id = ?", [targetId]);
|
|
93
96
|
if (!wl)
|
|
94
97
|
return void res.json({ error: '该用户不在白名单' });
|
|
95
98
|
if (wl.is_system)
|
|
96
99
|
return void res.json({ error: '系统兜底账户不可撤销' });
|
|
97
100
|
const cooldownUntil = new Date(Date.now() + REVOKE_COOLDOWN_DAYS * 86400_000).toISOString();
|
|
98
|
-
// 没收 50%
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
// 原子段:重读 active 行 → 没收 50% + 退还另一半 + DELETE active + INSERT cooldown 一起落。
|
|
102
|
+
// cooldown guard 防并发两次 revoke 重复没收/退款。
|
|
103
|
+
let forfeit = 0;
|
|
104
|
+
try {
|
|
105
|
+
forfeit = db.transaction(() => {
|
|
106
|
+
const cur = db.prepare("SELECT is_system, stake_amount, cooldown_until FROM verifier_whitelist WHERE user_id = ?")
|
|
107
|
+
.get(targetId);
|
|
108
|
+
if (!cur)
|
|
109
|
+
throw new Error('REVOKE_GONE');
|
|
110
|
+
if (cur.is_system)
|
|
111
|
+
throw new Error('REVOKE_SYSTEM');
|
|
112
|
+
if (cur.cooldown_until)
|
|
113
|
+
throw new Error('REVOKE_ALREADY'); // 已在撤销冷却,不再没收一次
|
|
114
|
+
const stakeAmt = cur.stake_amount || 0;
|
|
115
|
+
const f = Math.round(stakeAmt * 0.5 * 100) / 100;
|
|
116
|
+
db.prepare("UPDATE wallets SET staked = staked - ? WHERE user_id = ?").run(stakeAmt, targetId);
|
|
117
|
+
if (stakeAmt > f)
|
|
118
|
+
db.prepare("UPDATE wallets SET balance = balance + ? WHERE user_id = ?").run(stakeAmt - f, targetId);
|
|
119
|
+
db.prepare("DELETE FROM verifier_whitelist WHERE user_id = ?").run(targetId);
|
|
120
|
+
db.prepare(`INSERT INTO verifier_whitelist (user_id, note, tier, daily_quota, cooldown_until, is_system) VALUES (?,?,?,?,?,0)`)
|
|
121
|
+
.run(targetId, `撤销冷却中: ${reason || ''}`, 'trial-1', 0, cooldownUntil);
|
|
122
|
+
return f;
|
|
123
|
+
})();
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
const msg = e.message;
|
|
127
|
+
if (msg === 'REVOKE_GONE')
|
|
128
|
+
return void res.json({ error: '该用户不在白名单' });
|
|
129
|
+
if (msg === 'REVOKE_SYSTEM')
|
|
130
|
+
return void res.json({ error: '系统兜底账户不可撤销' });
|
|
131
|
+
if (msg === 'REVOKE_ALREADY')
|
|
132
|
+
return void res.json({ error: '该用户已在撤销冷却中' });
|
|
133
|
+
console.error('[verifier revoke tx]', msg);
|
|
134
|
+
return void res.status(500).json({ error: '撤销失败,请重试' });
|
|
103
135
|
}
|
|
104
|
-
db.prepare("DELETE FROM verifier_whitelist WHERE user_id = ?").run(targetId);
|
|
105
|
-
// 用 cooldown 记录在 verifier_stats 上(应用层兼容)
|
|
106
|
-
db.prepare(`INSERT INTO verifier_whitelist (user_id, note, tier, daily_quota, cooldown_until, is_system) VALUES (?,?,?,?,?,0)`)
|
|
107
|
-
.run(targetId, `撤销冷却中: ${reason || ''}`, 'trial-1', 0, cooldownUntil);
|
|
108
136
|
logAdminAction(admin.id, 'revoke_verifier', 'user', targetId, { reason: reason || null, forfeit, cooldown_until: cooldownUntil });
|
|
109
137
|
res.json({ success: true, cooldown_until: cooldownUntil, forfeit });
|
|
110
138
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerAdminWalletOpsRoutes(app, deps) {
|
|
2
|
-
|
|
3
|
+
// db 已走 RFC-016 异步 seam(dbOne/dbAll),不再直接用 deps.db
|
|
4
|
+
const { requireProtocolAdmin, adminAuth, getPublicClient, getUsdcAddr, getUsdcAbi, getHotWalletAddr, wazToUsdc, getIsMainnet, getNetwork, executeWithdrawal, logAdminAction, resolveProtocolAdminSoft } = deps;
|
|
3
5
|
// P2-5: protocol 权限(区域 admin 看不到全局热钱包)
|
|
4
6
|
app.get('/api/admin/hot-wallet/status', async (req, res) => {
|
|
5
7
|
const admin = requireProtocolAdmin(req, res);
|
|
@@ -12,7 +14,7 @@ export function registerAdminWalletOpsRoutes(app, deps) {
|
|
|
12
14
|
address: getUsdcAddr(), abi: getUsdcAbi(), functionName: 'balanceOf', args: [hw],
|
|
13
15
|
});
|
|
14
16
|
const ethBal = await pc.getBalance({ address: hw });
|
|
15
|
-
const pending =
|
|
17
|
+
const pending = (await dbOne("SELECT COALESCE(SUM(amount), 0) as t FROM withdrawal_requests WHERE status = 'pending'"));
|
|
16
18
|
const pendingUsdc = wazToUsdc(Number(pending.t));
|
|
17
19
|
res.json({
|
|
18
20
|
address: hw,
|
|
@@ -45,22 +47,45 @@ export function registerAdminWalletOpsRoutes(app, deps) {
|
|
|
45
47
|
res.json({ address: hw, usdc_balance: null, error: e.message });
|
|
46
48
|
}
|
|
47
49
|
});
|
|
48
|
-
app.get('/api/admin/withdrawals', (req, res) => {
|
|
50
|
+
app.get('/api/admin/withdrawals', async (req, res) => {
|
|
49
51
|
if (!adminAuth(req, res))
|
|
50
52
|
return;
|
|
51
|
-
const list =
|
|
53
|
+
const list = await dbAll(`
|
|
52
54
|
SELECT wr.*, u.name as user_name
|
|
53
55
|
FROM withdrawal_requests wr JOIN users u ON wr.user_id = u.id
|
|
54
56
|
WHERE wr.status = 'pending' ORDER BY wr.created_at ASC
|
|
55
|
-
`)
|
|
57
|
+
`);
|
|
56
58
|
res.json(list);
|
|
57
59
|
});
|
|
58
60
|
app.post('/api/admin/withdrawals/:id/approve', async (req, res) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
// 双轨过渡鉴权:优先认登录的 protocol-admin(Bearer)→ 记其真实 admin id;
|
|
62
|
+
// 否则回落到共享 ADMIN_KEY(adminAuth,既有运维路径,行为不变)→ actor 记中性标记 'admin_key'。
|
|
63
|
+
// 仅认 protocol 权限的 admin;非 protocol 的 Bearer 不放行(soft 解析返回 null),不扩大访问面,只精确归属。
|
|
64
|
+
let actorId = 'admin_key';
|
|
65
|
+
let authMethod = 'admin_key';
|
|
66
|
+
const bearerAdmin = resolveProtocolAdminSoft(req);
|
|
67
|
+
if (bearerAdmin) {
|
|
68
|
+
actorId = String(bearerAdmin.id);
|
|
69
|
+
authMethod = 'bearer_admin';
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
if (!adminAuth(req, res))
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// 出金前读取目标(user + amount),便于审计;执行后用真实 txHash 记一条 admin_audit_log。
|
|
76
|
+
const wr = await dbOne('SELECT user_id, amount FROM withdrawal_requests WHERE id = ?', [req.params.id]);
|
|
61
77
|
const result = await executeWithdrawal(req.params.id).catch(e => ({ success: false, error: e.message, txHash: undefined }));
|
|
62
78
|
if (!result.success)
|
|
63
79
|
return void res.json({ error: result.error });
|
|
80
|
+
// 审计时机:仅在出金成功后写。actor = 真实 admin id(bearer_admin)或中性标记 'admin_key';不写任何密钥。
|
|
81
|
+
try {
|
|
82
|
+
logAdminAction(actorId, 'withdrawal_approve', 'withdrawal', req.params.id, {
|
|
83
|
+
user_id: wr?.user_id ?? null, amount: wr?.amount ?? null, tx_hash: result.txHash, network: getNetwork(), auth_method: authMethod,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
console.error('[withdrawal_approve audit]', e);
|
|
88
|
+
}
|
|
64
89
|
res.json({ success: true, tx_hash: result.txHash });
|
|
65
90
|
});
|
|
66
91
|
}
|
|
@@ -163,33 +163,57 @@ ${webazFormatted.length > 0 ? JSON.stringify(webazFormatted.map(p => ({
|
|
|
163
163
|
const now = new Date();
|
|
164
164
|
const expiresAt = new Date(now.getTime() + 10 * 60_000);
|
|
165
165
|
sessionToken = generateId('pst');
|
|
166
|
-
db.prepare(`INSERT INTO price_sessions (token, product_id, user_id, price, quantity, created_at, expires_at) VALUES (?,?,?,?,1,?,?)`)
|
|
167
|
-
.run(sessionToken, product.id, user.id, product.price, now.toISOString(), expiresAt.toISOString());
|
|
168
|
-
verifiedPrice = product.price;
|
|
169
166
|
const oId = generateId('ord');
|
|
170
167
|
const totalAmount = product.price;
|
|
171
168
|
const seller = db.prepare('SELECT id FROM users WHERE id = ?').get(product.seller_id);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
169
|
+
// 原子核心:余额扣款(balance>=amount 守卫)+ 库存 CAS(stock>=1)+ 建单 + 价格锁,任一 changes!==1 抛回滚整笔。
|
|
170
|
+
// 注:transition() 自带 db.transaction,不能嵌套进来(better-sqlite3 禁套娃);故状态推进 + 通知放 tx 提交后,
|
|
171
|
+
// 与原顺序一致(原本就是 insert(created) → 扣款 → transition(paid))。守卫杜绝并发超卖/超扣 + 半写(Phase 3 pg 安全)。
|
|
172
|
+
let committed = false;
|
|
173
|
+
try {
|
|
174
|
+
db.transaction(() => {
|
|
175
|
+
const deb = db.prepare('UPDATE wallets SET balance = balance - ?, escrowed = escrowed + ? WHERE user_id = ? AND balance >= ?')
|
|
176
|
+
.run(totalAmount, totalAmount, user.id, totalAmount);
|
|
177
|
+
if (deb.changes !== 1)
|
|
178
|
+
throw new Error('AGENTBUY_INSUFFICIENT_BALANCE');
|
|
179
|
+
const dec = db.prepare('UPDATE products SET stock = stock - 1 WHERE id = ? AND stock >= 1').run(product.id);
|
|
180
|
+
if (dec.changes !== 1)
|
|
181
|
+
throw new Error('AGENTBUY_OUT_OF_STOCK');
|
|
182
|
+
db.prepare(`INSERT INTO price_sessions (token, product_id, user_id, price, quantity, created_at, expires_at) VALUES (?,?,?,?,1,?,?)`)
|
|
183
|
+
.run(sessionToken, product.id, user.id, product.price, now.toISOString(), expiresAt.toISOString());
|
|
184
|
+
db.prepare(`INSERT INTO orders (
|
|
185
|
+
id, product_id, buyer_id, seller_id, quantity, unit_price, total_amount, escrow_amount,
|
|
186
|
+
status, shipping_address, notes, pay_deadline, accept_deadline, ship_deadline,
|
|
187
|
+
pickup_deadline, delivery_deadline, confirm_deadline
|
|
188
|
+
) VALUES (?,?,?,?,1,?,?,?,'created',?,?,?,?,?,?,?,?)`).run(oId, product.id, user.id, seller.id, totalAmount, totalAmount, totalAmount, shipping_address, `[智能下单] ${decision.reason}`, addHours(now, 24), addHours(now, 48), addHours(now, 120), addHours(now, 168), addHours(now, 336), addHours(now, 408));
|
|
189
|
+
db.prepare(`UPDATE price_sessions SET used_at = datetime('now') WHERE token = ?`).run(sessionToken);
|
|
190
|
+
committed = true;
|
|
191
|
+
})();
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
const m = e.message;
|
|
195
|
+
if (m !== 'AGENTBUY_INSUFFICIENT_BALANCE' && m !== 'AGENTBUY_OUT_OF_STOCK')
|
|
196
|
+
throw e;
|
|
197
|
+
// 并发售罄 / 余额已变 → 不下单(auto_bought=false),在 reason 里说明
|
|
198
|
+
sessionToken = null;
|
|
199
|
+
decision.reason = (decision.reason || '') + (m === 'AGENTBUY_OUT_OF_STOCK' ? ' · auto_buy 跳过:商品已售罄' : ' · auto_buy 跳过:余额不足');
|
|
200
|
+
}
|
|
201
|
+
if (committed) {
|
|
202
|
+
// tx 提交后:状态推进 + 通知(transition 自带事务,故置于此)
|
|
203
|
+
checkStockAndMaybeDelist(String(product.id));
|
|
204
|
+
transition(db, oId, 'paid', user.id, [], '智能下单:模拟支付完成');
|
|
205
|
+
notifyTransition(db, oId, 'created', 'paid');
|
|
206
|
+
if (shouldAutoAccept(db, oId)) {
|
|
207
|
+
const sys = db.prepare("SELECT id FROM users WHERE id = 'sys_protocol'").get();
|
|
208
|
+
if (sys) {
|
|
209
|
+
const ar = transition(db, oId, 'accepted', sys.id, [], '⚡ auto_accept Skill 自动接单');
|
|
210
|
+
if (ar.success)
|
|
211
|
+
notifyTransition(db, oId, 'paid', 'accepted');
|
|
212
|
+
}
|
|
190
213
|
}
|
|
214
|
+
verifiedPrice = product.price;
|
|
215
|
+
orderId = oId;
|
|
191
216
|
}
|
|
192
|
-
orderId = oId;
|
|
193
217
|
}
|
|
194
218
|
}
|
|
195
219
|
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { computeAgentPassport } from '../../layer1-agent/L1-2-identity/agent-passport.js';
|
|
2
|
+
import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
2
3
|
export function registerAgentGovernanceRoutes(app, deps) {
|
|
3
4
|
const { db, generateId, auth, requireRootAdmin, invalidateAgentBlockedCache, requireHumanPresence, issueAgentStrike, custodianFingerprint, signPassport, issuerAddress } = deps;
|
|
4
5
|
// /api/me/agents — 列出本账号所有 agent + declaration / strikes
|
|
5
|
-
app.get('/api/me/agents', (req, res) => {
|
|
6
|
+
app.get('/api/me/agents', async (req, res) => {
|
|
6
7
|
const user = auth(req, res);
|
|
7
8
|
if (!user)
|
|
8
9
|
return;
|
|
9
|
-
const keys =
|
|
10
|
-
const items = keys.map(k => {
|
|
11
|
-
const decl =
|
|
10
|
+
const keys = await dbAll(`SELECT api_key FROM users WHERE id = ? UNION SELECT api_key FROM agent_reputation WHERE user_id = ?`, [user.id, user.id]);
|
|
11
|
+
const items = await Promise.all(keys.map(async (k) => {
|
|
12
|
+
const decl = await dbOne(`SELECT operator_name, operator_contact, purpose, declared_scope, attestations, repo_url, revoked_at FROM agent_declarations WHERE api_key = ?`, [k.api_key]);
|
|
12
13
|
// P1 fix 4.4:附 signals JSON
|
|
13
|
-
const rep =
|
|
14
|
+
const rep = await dbOne(`SELECT trust_score, level, signals, last_calculated_at FROM agent_reputation WHERE api_key = ?`, [k.api_key]);
|
|
14
15
|
if (rep && rep.signals) {
|
|
15
16
|
try {
|
|
16
17
|
rep.signals = JSON.parse(rep.signals);
|
|
@@ -19,9 +20,9 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
19
20
|
rep.signals = null;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
|
-
const calls30d =
|
|
23
|
-
const last =
|
|
24
|
-
const strikes =
|
|
23
|
+
const calls30d = (await dbOne(`SELECT COUNT(*) as n FROM agent_call_log WHERE api_key = ? AND created_at > datetime('now', '-30 days')`, [k.api_key])).n;
|
|
24
|
+
const last = await dbOne(`SELECT endpoint, method, status_code, created_at FROM agent_call_log WHERE api_key = ? ORDER BY created_at DESC LIMIT 1`, [k.api_key]);
|
|
25
|
+
const strikes = await dbAll(`SELECT severity, reason_code, issued_at, expires_at, appeal_status FROM agent_strikes WHERE api_key = ? ORDER BY issued_at DESC LIMIT 5`, [k.api_key]);
|
|
25
26
|
let passport = null;
|
|
26
27
|
try {
|
|
27
28
|
passport = computeAgentPassport(db, k.api_key, user.id, custodianFingerprint);
|
|
@@ -37,9 +38,9 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
37
38
|
recent_strikes: strikes,
|
|
38
39
|
passport,
|
|
39
40
|
};
|
|
40
|
-
});
|
|
41
|
+
}));
|
|
41
42
|
// Phase 2 监护人总览(只读/软绑定):真人态 + 旗下 agent 聚合 + 连带
|
|
42
|
-
const hasPasskey = (
|
|
43
|
+
const hasPasskey = ((await dbOne('SELECT COUNT(*) AS n FROM webauthn_credentials WHERE user_id = ?', [user.id]))?.n || 0) > 0;
|
|
43
44
|
const pps = items.map(i => i.passport).filter(Boolean);
|
|
44
45
|
const depthRank = { shallow: 0, medium: 1, deep: 2, profound: 3 };
|
|
45
46
|
const deepest = pps.reduce((d, p) => (depthRank[p.engagement_depth] ?? 0) > (depthRank[d] ?? 0) ? p.engagement_depth : d, 'shallow');
|
|
@@ -65,7 +66,7 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
65
66
|
const prefix = String(req.params.apiKeyPrefix || '').replace('...', '');
|
|
66
67
|
if (prefix.length < 6)
|
|
67
68
|
return void res.status(400).json({ error: 'apiKeyPrefix 太短' });
|
|
68
|
-
const keys =
|
|
69
|
+
const keys = await dbAll(`SELECT api_key FROM users WHERE id = ? UNION SELECT api_key FROM agent_reputation WHERE user_id = ?`, [user.id, user.id]);
|
|
69
70
|
const match = keys.find(k => k.api_key.startsWith(prefix));
|
|
70
71
|
if (!match)
|
|
71
72
|
return void res.status(404).json({ error: '未找到该 agent(或不属于你)' });
|
|
@@ -135,32 +136,30 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
135
136
|
webaz_format, // 显式重复让消费者明确选哪个
|
|
136
137
|
});
|
|
137
138
|
});
|
|
138
|
-
app.get('/api/me/agents/:apiKeyPrefix/log', (req, res) => {
|
|
139
|
+
app.get('/api/me/agents/:apiKeyPrefix/log', async (req, res) => {
|
|
139
140
|
const user = auth(req, res);
|
|
140
141
|
if (!user)
|
|
141
142
|
return;
|
|
142
143
|
const prefix = String(req.params.apiKeyPrefix || '').replace(/[^A-Za-z0-9_]/g, '').slice(0, 32);
|
|
143
144
|
if (prefix.length < 8)
|
|
144
145
|
return void res.status(400).json({ error: 'apiKeyPrefix 至少 8 字符' });
|
|
145
|
-
const targetKey =
|
|
146
|
-
UNION SELECT api_key FROM agent_reputation WHERE user_id = ? AND api_key LIKE ? || '%'
|
|
147
|
-
.get(user.id, prefix, user.id, prefix);
|
|
146
|
+
const targetKey = await dbOne(`SELECT api_key FROM users WHERE id = ? AND api_key LIKE ? || '%'
|
|
147
|
+
UNION SELECT api_key FROM agent_reputation WHERE user_id = ? AND api_key LIKE ? || '%'`, [user.id, prefix, user.id, prefix]);
|
|
148
148
|
if (!targetKey)
|
|
149
149
|
return void res.status(404).json({ error: '未找到匹配的 agent api_key(仅可查本人的)' });
|
|
150
150
|
const limit = Math.min(500, Math.max(10, Number(req.query.limit) || 100));
|
|
151
|
-
const rows =
|
|
152
|
-
WHERE api_key = ? AND created_at > datetime('now', '-30 days') ORDER BY id DESC LIMIT
|
|
151
|
+
const rows = await dbAll(`SELECT endpoint, method, status_code, created_at FROM agent_call_log
|
|
152
|
+
WHERE api_key = ? AND created_at > datetime('now', '-30 days') ORDER BY id DESC LIMIT ?`, [targetKey.api_key, limit]);
|
|
153
153
|
res.json({ items: rows });
|
|
154
154
|
});
|
|
155
|
-
app.post('/api/me/agents/declarations', (req, res) => {
|
|
155
|
+
app.post('/api/me/agents/declarations', async (req, res) => {
|
|
156
156
|
const user = auth(req, res);
|
|
157
157
|
if (!user)
|
|
158
158
|
return;
|
|
159
159
|
const b = req.body;
|
|
160
160
|
const targetApiKey = b.api_key ? String(b.api_key) : user.api_key;
|
|
161
|
-
const ownership =
|
|
162
|
-
UNION SELECT user_id as id FROM agent_reputation WHERE user_id = ? AND api_key =
|
|
163
|
-
.get(user.id, targetApiKey, user.id, targetApiKey);
|
|
161
|
+
const ownership = await dbOne(`SELECT id FROM users WHERE id = ? AND api_key = ?
|
|
162
|
+
UNION SELECT user_id as id FROM agent_reputation WHERE user_id = ? AND api_key = ?`, [user.id, targetApiKey, user.id, targetApiKey]);
|
|
164
163
|
if (!ownership)
|
|
165
164
|
return void res.status(403).json({ error: 'api_key 不属于本账号' });
|
|
166
165
|
const operator_name = String(b.operator_name || '').trim().slice(0, 60);
|
|
@@ -185,7 +184,7 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
185
184
|
const attestationsJson = b.attestations && typeof b.attestations === 'object' ? JSON.stringify(b.attestations).slice(0, 2000) : null;
|
|
186
185
|
const repo_url = b.repo_url ? String(b.repo_url).slice(0, 200) : null;
|
|
187
186
|
const homepage = b.homepage ? String(b.homepage).slice(0, 200) : null;
|
|
188
|
-
|
|
187
|
+
await dbRun(`INSERT INTO agent_declarations (
|
|
189
188
|
api_key, user_id, operator_name, operator_contact, purpose, declared_scope, attestations, repo_url, homepage
|
|
190
189
|
) VALUES (?,?,?,?,?,?,?,?,?)
|
|
191
190
|
ON CONFLICT(api_key) DO UPDATE SET
|
|
@@ -197,36 +196,35 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
197
196
|
repo_url = excluded.repo_url,
|
|
198
197
|
homepage = excluded.homepage,
|
|
199
198
|
revoked_at = NULL,
|
|
200
|
-
updated_at = datetime('now')
|
|
199
|
+
updated_at = datetime('now')`, [targetApiKey, user.id, operator_name, operator_contact, purpose, scopeJson, attestationsJson, repo_url, homepage]);
|
|
201
200
|
invalidateAgentBlockedCache(targetApiKey);
|
|
202
201
|
res.json({ ok: true });
|
|
203
202
|
});
|
|
204
203
|
// 用户撤销 agent(铁律 §4 human presence)
|
|
205
|
-
app.post('/api/me/agents/:apiKeyPrefix/revoke', (req, res) => {
|
|
204
|
+
app.post('/api/me/agents/:apiKeyPrefix/revoke', async (req, res) => {
|
|
206
205
|
const user = auth(req, res);
|
|
207
206
|
if (!user)
|
|
208
207
|
return;
|
|
209
208
|
const prefix = String(req.params.apiKeyPrefix || '').replace(/[^A-Za-z0-9_]/g, '').slice(0, 32);
|
|
210
209
|
if (prefix.length < 8)
|
|
211
210
|
return void res.status(400).json({ error: 'apiKeyPrefix 至少 8 字符' });
|
|
212
|
-
const targetKey =
|
|
213
|
-
UNION SELECT api_key FROM agent_reputation WHERE user_id = ? AND api_key LIKE ? || '%'
|
|
214
|
-
.get(user.id, prefix, user.id, prefix);
|
|
211
|
+
const targetKey = await dbOne(`SELECT api_key FROM users WHERE id = ? AND api_key LIKE ? || '%'
|
|
212
|
+
UNION SELECT api_key FROM agent_reputation WHERE user_id = ? AND api_key LIKE ? || '%'`, [user.id, prefix, user.id, prefix]);
|
|
215
213
|
if (!targetKey)
|
|
216
214
|
return void res.status(404).json({ error: '未找到匹配的 agent api_key' });
|
|
217
215
|
const hpCheck = requireHumanPresence(user.id, 'agent_revoke', (req.body || {}).webauthn_token, 'require_human_presence_for_agent_revoke', () => true);
|
|
218
216
|
if (!hpCheck.ok)
|
|
219
217
|
return void res.status(412).json({ error: hpCheck.reason, error_code: hpCheck.error_code });
|
|
220
218
|
const reason = String((req.body || {}).reason || '').slice(0, 300);
|
|
221
|
-
|
|
219
|
+
await dbRun(`INSERT INTO agent_revocations (target_kind, target_value, revoked_by, revoked_by_role, reason)
|
|
222
220
|
VALUES ('api_key', ?, ?, 'self', ?)
|
|
223
|
-
ON CONFLICT(target_kind, target_value, revoked_by) DO NOTHING
|
|
224
|
-
|
|
221
|
+
ON CONFLICT(target_kind, target_value, revoked_by) DO NOTHING`, [targetKey.api_key, user.id, reason]);
|
|
222
|
+
await dbRun(`UPDATE agent_declarations SET revoked_at = datetime('now'), revoked_reason = ? WHERE api_key = ?`, [reason, targetKey.api_key]);
|
|
225
223
|
invalidateAgentBlockedCache(targetKey.api_key);
|
|
226
224
|
res.json({ ok: true });
|
|
227
225
|
});
|
|
228
226
|
// 撤销同 operator 名下所有 agent(仅撤销本用户给 operator 旗下 agent 的 attestation)
|
|
229
|
-
app.post('/api/me/agents/operators/:operator_name/revoke', (req, res) => {
|
|
227
|
+
app.post('/api/me/agents/operators/:operator_name/revoke', async (req, res) => {
|
|
230
228
|
const user = auth(req, res);
|
|
231
229
|
if (!user)
|
|
232
230
|
return;
|
|
@@ -237,27 +235,26 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
237
235
|
if (!hpCheck.ok)
|
|
238
236
|
return void res.status(412).json({ error: hpCheck.reason, error_code: hpCheck.error_code });
|
|
239
237
|
const reason = String((req.body || {}).reason || '').slice(0, 300);
|
|
240
|
-
const affected =
|
|
238
|
+
const affected = await dbRun(`UPDATE agent_attestations SET revoked_at = datetime('now')
|
|
241
239
|
WHERE user_id = ? AND revoked_at IS NULL
|
|
242
|
-
AND api_key IN (SELECT api_key FROM agent_declarations WHERE operator_name = ?)
|
|
243
|
-
|
|
244
|
-
db.prepare(`INSERT INTO agent_revocations (target_kind, target_value, revoked_by, revoked_by_role, reason)
|
|
240
|
+
AND api_key IN (SELECT api_key FROM agent_declarations WHERE operator_name = ?)`, [user.id, opName]);
|
|
241
|
+
await dbRun(`INSERT INTO agent_revocations (target_kind, target_value, revoked_by, revoked_by_role, reason)
|
|
245
242
|
VALUES ('operator_name', ?, ?, 'self', ?)
|
|
246
|
-
ON CONFLICT(target_kind, target_value, revoked_by) DO NOTHING
|
|
247
|
-
const keys =
|
|
243
|
+
ON CONFLICT(target_kind, target_value, revoked_by) DO NOTHING`, [opName, user.id, reason]);
|
|
244
|
+
const keys = await dbAll(`SELECT api_key FROM agent_declarations WHERE operator_name = ?`, [opName]);
|
|
248
245
|
for (const k of keys)
|
|
249
246
|
invalidateAgentBlockedCache(k.api_key);
|
|
250
247
|
res.json({ ok: true, attestations_revoked: affected.changes });
|
|
251
248
|
});
|
|
252
249
|
// P0 audit fix 4.2: 申诉 strike
|
|
253
|
-
app.post('/api/me/agents/strikes/:strikeId/appeal', (req, res) => {
|
|
250
|
+
app.post('/api/me/agents/strikes/:strikeId/appeal', async (req, res) => {
|
|
254
251
|
const user = auth(req, res);
|
|
255
252
|
if (!user)
|
|
256
253
|
return;
|
|
257
254
|
const strikeId = Number(req.params.strikeId);
|
|
258
255
|
if (!Number.isInteger(strikeId) || strikeId <= 0)
|
|
259
256
|
return void res.status(400).json({ error: 'strikeId 必须是正整数' });
|
|
260
|
-
const strike =
|
|
257
|
+
const strike = await dbOne(`SELECT id, api_key, user_id, severity, issued_at, appeal_status FROM agent_strikes WHERE id = ?`, [strikeId]);
|
|
261
258
|
if (!strike)
|
|
262
259
|
return void res.status(404).json({ error: 'strike 不存在' });
|
|
263
260
|
if (strike.user_id !== user.id)
|
|
@@ -270,11 +267,11 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
270
267
|
const reason = String((req.body || {}).reason || '').trim().slice(0, 500);
|
|
271
268
|
if (reason.length < 10)
|
|
272
269
|
return void res.status(400).json({ error: '申诉理由 ≥10 字' });
|
|
273
|
-
|
|
270
|
+
await dbRun(`UPDATE agent_strikes SET appeal_status = 'pending', appeal_reason = ? WHERE id = ?`, [reason, strikeId]);
|
|
274
271
|
res.json({ ok: true, message: '申诉已提交,等待 root admin 审核' });
|
|
275
272
|
});
|
|
276
273
|
// Admin: 审核 strike 申诉
|
|
277
|
-
app.post('/api/admin/agent-strikes/:strikeId/decide', (req, res) => {
|
|
274
|
+
app.post('/api/admin/agent-strikes/:strikeId/decide', async (req, res) => {
|
|
278
275
|
const user = requireRootAdmin(req, res);
|
|
279
276
|
if (!user)
|
|
280
277
|
return;
|
|
@@ -284,21 +281,20 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
284
281
|
const decision = String((req.body || {}).decision || '');
|
|
285
282
|
if (!['approved', 'denied'].includes(decision))
|
|
286
283
|
return void res.status(400).json({ error: 'decision 必须是 approved / denied' });
|
|
287
|
-
const strike =
|
|
284
|
+
const strike = await dbOne(`SELECT id, api_key, appeal_status FROM agent_strikes WHERE id = ?`, [strikeId]);
|
|
288
285
|
if (!strike)
|
|
289
286
|
return void res.status(404).json({ error: 'strike 不存在' });
|
|
290
287
|
if (strike.appeal_status !== 'pending')
|
|
291
288
|
return void res.status(409).json({ error: `当前状态 ${strike.appeal_status} 不可裁决` });
|
|
292
|
-
|
|
293
|
-
.run(decision, user.id, strikeId);
|
|
289
|
+
await dbRun(`UPDATE agent_strikes SET appeal_status = ?, appeal_decided_by = ?, appeal_decided_at = datetime('now') WHERE id = ?`, [decision, user.id, strikeId]);
|
|
294
290
|
invalidateAgentBlockedCache(strike.api_key);
|
|
295
291
|
// P1 fix 5.3: appeal approved → 恢复因 strike 自动停用的 skills
|
|
296
292
|
if (decision === 'approved') {
|
|
297
293
|
try {
|
|
298
|
-
const uRow =
|
|
294
|
+
const uRow = await dbOne(`SELECT id FROM users WHERE api_key = ?`, [strike.api_key]);
|
|
299
295
|
if (uRow) {
|
|
300
|
-
const r =
|
|
301
|
-
WHERE seller_id = ? AND disabled_by_strike_at IS NOT NULL AND active = 0
|
|
296
|
+
const r = await dbRun(`UPDATE skills SET active = 1, disabled_by_strike_at = NULL
|
|
297
|
+
WHERE seller_id = ? AND disabled_by_strike_at IS NOT NULL AND active = 0`, [uRow.id]);
|
|
302
298
|
if (r.changes > 0)
|
|
303
299
|
console.log(`[appeal approved→skill] restored ${r.changes} skills for ${uRow.id}`);
|
|
304
300
|
}
|
|
@@ -310,17 +306,17 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
310
306
|
res.json({ ok: true, decision });
|
|
311
307
|
});
|
|
312
308
|
// Admin: 列出待审 strike 申诉
|
|
313
|
-
app.get('/api/admin/agent-strikes/pending', (req, res) => {
|
|
309
|
+
app.get('/api/admin/agent-strikes/pending', async (req, res) => {
|
|
314
310
|
const user = requireRootAdmin(req, res);
|
|
315
311
|
if (!user)
|
|
316
312
|
return;
|
|
317
|
-
const rows =
|
|
313
|
+
const rows = await dbAll(`SELECT s.id, s.api_key, s.user_id, u.handle, s.severity, s.reason_code, s.reason_detail, s.issued_at, s.appeal_reason
|
|
318
314
|
FROM agent_strikes s JOIN users u ON u.id = s.user_id
|
|
319
|
-
WHERE s.appeal_status = 'pending' ORDER BY s.id DESC LIMIT 100`)
|
|
315
|
+
WHERE s.appeal_status = 'pending' ORDER BY s.id DESC LIMIT 100`);
|
|
320
316
|
res.json({ items: rows });
|
|
321
317
|
});
|
|
322
318
|
// P1 fix 4.3: admin 主动 issue strike
|
|
323
|
-
app.post('/api/admin/agent-strikes/issue', (req, res) => {
|
|
319
|
+
app.post('/api/admin/agent-strikes/issue', async (req, res) => {
|
|
324
320
|
const adminUser = requireRootAdmin(req, res);
|
|
325
321
|
if (!adminUser)
|
|
326
322
|
return;
|
|
@@ -328,7 +324,7 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
328
324
|
const apiKey = String(b.api_key || '').trim();
|
|
329
325
|
if (apiKey.length < 8)
|
|
330
326
|
return void res.status(400).json({ error: 'api_key 必填' });
|
|
331
|
-
const targetUser =
|
|
327
|
+
const targetUser = await dbOne(`SELECT id, handle FROM users WHERE api_key = ?`, [apiKey]);
|
|
332
328
|
if (!targetUser)
|
|
333
329
|
return void res.status(404).json({ error: '未找到该 api_key' });
|
|
334
330
|
const reasonCode = String(b.reason_code || '').trim().slice(0, 40);
|
|
@@ -349,7 +345,7 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
349
345
|
res.json({ ok: true, target_handle: targetUser.handle, ...result });
|
|
350
346
|
});
|
|
351
347
|
// bilateral attestation(用户批准某 agent 的 scope)
|
|
352
|
-
app.post('/api/me/agents/attestations', (req, res) => {
|
|
348
|
+
app.post('/api/me/agents/attestations', async (req, res) => {
|
|
353
349
|
const user = auth(req, res);
|
|
354
350
|
if (!user)
|
|
355
351
|
return;
|
|
@@ -357,7 +353,7 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
357
353
|
const apiKey = String(b.api_key || '');
|
|
358
354
|
if (!apiKey)
|
|
359
355
|
return void res.status(400).json({ error: 'api_key 必填' });
|
|
360
|
-
const decl =
|
|
356
|
+
const decl = await dbOne(`SELECT declared_scope, operator_name, purpose FROM agent_declarations WHERE api_key = ? AND revoked_at IS NULL`, [apiKey]);
|
|
361
357
|
if (!decl)
|
|
362
358
|
return void res.status(404).json({ error: '该 agent 未声明 / 已撤销,无法授权' });
|
|
363
359
|
let approvedScopeJson;
|
|
@@ -373,14 +369,14 @@ export function registerAgentGovernanceRoutes(app, deps) {
|
|
|
373
369
|
const spendCapPerOrder = b.spend_cap_per_order != null ? Math.max(0, Number(b.spend_cap_per_order)) : null;
|
|
374
370
|
const spendCapDaily = b.spend_cap_daily != null ? Math.max(0, Number(b.spend_cap_daily)) : null;
|
|
375
371
|
const id = generateId('aat');
|
|
376
|
-
|
|
372
|
+
await dbRun(`INSERT INTO agent_attestations (id, api_key, user_id, approved_scope, spend_cap_per_order, spend_cap_daily)
|
|
377
373
|
VALUES (?,?,?,?,?,?)
|
|
378
374
|
ON CONFLICT(api_key, user_id) DO UPDATE SET
|
|
379
375
|
approved_scope = excluded.approved_scope,
|
|
380
376
|
spend_cap_per_order = excluded.spend_cap_per_order,
|
|
381
377
|
spend_cap_daily = excluded.spend_cap_daily,
|
|
382
378
|
revoked_at = NULL,
|
|
383
|
-
granted_at = datetime('now')
|
|
379
|
+
granted_at = datetime('now')`, [id, apiKey, user.id, approvedScopeJson, spendCapPerOrder, spendCapDaily]);
|
|
384
380
|
res.json({ ok: true });
|
|
385
381
|
});
|
|
386
382
|
}
|