@dupecom/botcha-cloudflare 0.16.0 → 0.19.0
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 +1 -1
- package/dist/auth.d.ts +48 -3
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +89 -21
- package/dist/dashboard/docs.d.ts +15 -0
- package/dist/dashboard/docs.d.ts.map +1 -0
- package/dist/dashboard/docs.js +556 -0
- package/dist/dashboard/layout.d.ts +12 -0
- package/dist/dashboard/layout.d.ts.map +1 -1
- package/dist/dashboard/layout.js +12 -5
- package/dist/dashboard/showcase.d.ts.map +1 -1
- package/dist/dashboard/showcase.js +2 -1
- package/dist/dashboard/whitepaper.d.ts.map +1 -1
- package/dist/dashboard/whitepaper.js +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -13
- package/dist/static.d.ts +592 -2
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +422 -9
- package/dist/tap-attestation-routes.d.ts +204 -0
- package/dist/tap-attestation-routes.d.ts.map +1 -0
- package/dist/tap-attestation-routes.js +396 -0
- package/dist/tap-attestation.d.ts +178 -0
- package/dist/tap-attestation.d.ts.map +1 -0
- package/dist/tap-attestation.js +416 -0
- package/dist/tap-delegation-routes.d.ts +236 -0
- package/dist/tap-delegation-routes.d.ts.map +1 -0
- package/dist/tap-delegation-routes.js +378 -0
- package/dist/tap-delegation.d.ts +127 -0
- package/dist/tap-delegation.d.ts.map +1 -0
- package/dist/tap-delegation.js +490 -0
- package/dist/tap-jwks.d.ts +2 -1
- package/dist/tap-jwks.d.ts.map +1 -1
- package/dist/tap-jwks.js +31 -7
- package/dist/tap-reputation-routes.d.ts +154 -0
- package/dist/tap-reputation-routes.d.ts.map +1 -0
- package/dist/tap-reputation-routes.js +341 -0
- package/dist/tap-reputation.d.ts +136 -0
- package/dist/tap-reputation.d.ts.map +1 -0
- package/dist/tap-reputation.js +346 -0
- package/package.json +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAP Agent Reputation Scoring
|
|
3
|
+
*
|
|
4
|
+
* The "credit score" for AI agents. Persistent identity enables behavioral
|
|
5
|
+
* tracking over time, producing trust scores that unlock higher rate limits,
|
|
6
|
+
* faster verification, and access to sensitive APIs.
|
|
7
|
+
*
|
|
8
|
+
* Scoring model:
|
|
9
|
+
* - Base score: 500 (neutral, no history)
|
|
10
|
+
* - Range: 0..1000
|
|
11
|
+
* - Events adjust score via weighted deltas
|
|
12
|
+
* - Decay: scores trend toward 500 over time without activity (mean reversion)
|
|
13
|
+
* - Tiers: untrusted (0-199), low (200-399), neutral (400-599),
|
|
14
|
+
* good (600-799), excellent (800-1000)
|
|
15
|
+
*
|
|
16
|
+
* Event categories:
|
|
17
|
+
* - verification: challenge solved, auth success/failure
|
|
18
|
+
* - attestation: issued, verified, revoked
|
|
19
|
+
* - delegation: granted, received, revoked
|
|
20
|
+
* - session: created, expired normally, force-terminated
|
|
21
|
+
* - violation: rate limit exceeded, invalid token, abuse detected
|
|
22
|
+
* - endorsement: explicit trust signal from another agent or app
|
|
23
|
+
*
|
|
24
|
+
* KV storage (SESSIONS namespace):
|
|
25
|
+
* - reputation:{agent_id} — ReputationScore record
|
|
26
|
+
* - reputation_events:{agent_id} — Array of event IDs (index)
|
|
27
|
+
* - reputation_event:{event_id} — Individual ReputationEvent (with TTL)
|
|
28
|
+
*/
|
|
29
|
+
import type { KVNamespace } from './agents.js';
|
|
30
|
+
export type ReputationTier = 'untrusted' | 'low' | 'neutral' | 'good' | 'excellent';
|
|
31
|
+
export type ReputationEventCategory = 'verification' | 'attestation' | 'delegation' | 'session' | 'violation' | 'endorsement';
|
|
32
|
+
export type ReputationEventAction = 'challenge_solved' | 'challenge_failed' | 'auth_success' | 'auth_failure' | 'attestation_issued' | 'attestation_verified' | 'attestation_revoked' | 'delegation_granted' | 'delegation_received' | 'delegation_revoked' | 'session_created' | 'session_expired' | 'session_terminated' | 'rate_limit_exceeded' | 'invalid_token' | 'abuse_detected' | 'endorsement_received' | 'endorsement_given';
|
|
33
|
+
export interface ReputationEvent {
|
|
34
|
+
event_id: string;
|
|
35
|
+
agent_id: string;
|
|
36
|
+
app_id: string;
|
|
37
|
+
category: ReputationEventCategory;
|
|
38
|
+
action: ReputationEventAction;
|
|
39
|
+
delta: number;
|
|
40
|
+
score_before: number;
|
|
41
|
+
score_after: number;
|
|
42
|
+
source_agent_id?: string;
|
|
43
|
+
metadata?: Record<string, string>;
|
|
44
|
+
created_at: number;
|
|
45
|
+
}
|
|
46
|
+
export interface ReputationScore {
|
|
47
|
+
agent_id: string;
|
|
48
|
+
app_id: string;
|
|
49
|
+
score: number;
|
|
50
|
+
tier: ReputationTier;
|
|
51
|
+
event_count: number;
|
|
52
|
+
positive_events: number;
|
|
53
|
+
negative_events: number;
|
|
54
|
+
last_event_at: number | null;
|
|
55
|
+
created_at: number;
|
|
56
|
+
updated_at: number;
|
|
57
|
+
category_scores: {
|
|
58
|
+
verification: number;
|
|
59
|
+
attestation: number;
|
|
60
|
+
delegation: number;
|
|
61
|
+
session: number;
|
|
62
|
+
violation: number;
|
|
63
|
+
endorsement: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export interface RecordEventOptions {
|
|
67
|
+
agent_id: string;
|
|
68
|
+
category: ReputationEventCategory;
|
|
69
|
+
action: ReputationEventAction;
|
|
70
|
+
source_agent_id?: string;
|
|
71
|
+
metadata?: Record<string, string>;
|
|
72
|
+
}
|
|
73
|
+
export interface ReputationResult {
|
|
74
|
+
success: boolean;
|
|
75
|
+
score?: ReputationScore;
|
|
76
|
+
error?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface EventResult {
|
|
79
|
+
success: boolean;
|
|
80
|
+
event?: ReputationEvent;
|
|
81
|
+
score?: ReputationScore;
|
|
82
|
+
error?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface EventListResult {
|
|
85
|
+
success: boolean;
|
|
86
|
+
events?: ReputationEvent[];
|
|
87
|
+
count?: number;
|
|
88
|
+
error?: string;
|
|
89
|
+
}
|
|
90
|
+
export declare function getTier(score: number): ReputationTier;
|
|
91
|
+
/**
|
|
92
|
+
* Apply mean-reversion decay. For every 7 days since last activity,
|
|
93
|
+
* nudge the score 1% toward BASE_SCORE.
|
|
94
|
+
*/
|
|
95
|
+
export declare function applyDecay(score: ReputationScore): ReputationScore;
|
|
96
|
+
/**
|
|
97
|
+
* Get the reputation score for an agent. Creates a default score if none exists.
|
|
98
|
+
*/
|
|
99
|
+
export declare function getReputationScore(sessions: KVNamespace, agents: KVNamespace, agentId: string, appId: string): Promise<ReputationResult>;
|
|
100
|
+
/**
|
|
101
|
+
* Record a reputation event for an agent.
|
|
102
|
+
* Creates the reputation record if it doesn't exist yet.
|
|
103
|
+
*/
|
|
104
|
+
export declare function recordReputationEvent(sessions: KVNamespace, agents: KVNamespace, appId: string, options: RecordEventOptions): Promise<EventResult>;
|
|
105
|
+
/**
|
|
106
|
+
* List reputation events for an agent.
|
|
107
|
+
* Returns the most recent events (up to limit).
|
|
108
|
+
*/
|
|
109
|
+
export declare function listReputationEvents(sessions: KVNamespace, agentId: string, options?: {
|
|
110
|
+
category?: ReputationEventCategory;
|
|
111
|
+
limit?: number;
|
|
112
|
+
}): Promise<EventListResult>;
|
|
113
|
+
/**
|
|
114
|
+
* Reset an agent's reputation to default. Used by app admins.
|
|
115
|
+
*/
|
|
116
|
+
export declare function resetReputation(sessions: KVNamespace, agents: KVNamespace, agentId: string, appId: string): Promise<ReputationResult>;
|
|
117
|
+
export declare function isValidCategoryAction(category: ReputationEventCategory, action: ReputationEventAction): boolean;
|
|
118
|
+
export declare function isValidCategory(category: string): category is ReputationEventCategory;
|
|
119
|
+
export declare function isValidAction(action: string): action is ReputationEventAction;
|
|
120
|
+
declare const _default: {
|
|
121
|
+
getReputationScore: typeof getReputationScore;
|
|
122
|
+
recordReputationEvent: typeof recordReputationEvent;
|
|
123
|
+
listReputationEvents: typeof listReputationEvents;
|
|
124
|
+
resetReputation: typeof resetReputation;
|
|
125
|
+
getTier: typeof getTier;
|
|
126
|
+
applyDecay: typeof applyDecay;
|
|
127
|
+
isValidCategoryAction: typeof isValidCategoryAction;
|
|
128
|
+
isValidCategory: typeof isValidCategory;
|
|
129
|
+
isValidAction: typeof isValidAction;
|
|
130
|
+
ACTION_DELTAS: Record<ReputationEventAction, number>;
|
|
131
|
+
BASE_SCORE: number;
|
|
132
|
+
MIN_SCORE: number;
|
|
133
|
+
MAX_SCORE: number;
|
|
134
|
+
};
|
|
135
|
+
export default _default;
|
|
136
|
+
//# sourceMappingURL=tap-reputation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap-reputation.d.ts","sourceRoot":"","sources":["../src/tap-reputation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAP Agent Reputation Scoring
|
|
3
|
+
*
|
|
4
|
+
* The "credit score" for AI agents. Persistent identity enables behavioral
|
|
5
|
+
* tracking over time, producing trust scores that unlock higher rate limits,
|
|
6
|
+
* faster verification, and access to sensitive APIs.
|
|
7
|
+
*
|
|
8
|
+
* Scoring model:
|
|
9
|
+
* - Base score: 500 (neutral, no history)
|
|
10
|
+
* - Range: 0..1000
|
|
11
|
+
* - Events adjust score via weighted deltas
|
|
12
|
+
* - Decay: scores trend toward 500 over time without activity (mean reversion)
|
|
13
|
+
* - Tiers: untrusted (0-199), low (200-399), neutral (400-599),
|
|
14
|
+
* good (600-799), excellent (800-1000)
|
|
15
|
+
*
|
|
16
|
+
* Event categories:
|
|
17
|
+
* - verification: challenge solved, auth success/failure
|
|
18
|
+
* - attestation: issued, verified, revoked
|
|
19
|
+
* - delegation: granted, received, revoked
|
|
20
|
+
* - session: created, expired normally, force-terminated
|
|
21
|
+
* - violation: rate limit exceeded, invalid token, abuse detected
|
|
22
|
+
* - endorsement: explicit trust signal from another agent or app
|
|
23
|
+
*
|
|
24
|
+
* KV storage (SESSIONS namespace):
|
|
25
|
+
* - reputation:{agent_id} — ReputationScore record
|
|
26
|
+
* - reputation_events:{agent_id} — Array of event IDs (index)
|
|
27
|
+
* - reputation_event:{event_id} — Individual ReputationEvent (with TTL)
|
|
28
|
+
*/
|
|
29
|
+
import { getTAPAgent } from './tap-agents.js';
|
|
30
|
+
// ============ CONSTANTS ============
|
|
31
|
+
const BASE_SCORE = 500;
|
|
32
|
+
const MIN_SCORE = 0;
|
|
33
|
+
const MAX_SCORE = 1000;
|
|
34
|
+
/** How long individual events are retained in KV (90 days) */
|
|
35
|
+
const EVENT_TTL_SECONDS = 90 * 24 * 3600;
|
|
36
|
+
/** Max events to keep in the index per agent */
|
|
37
|
+
const MAX_EVENT_INDEX = 1000;
|
|
38
|
+
/** Score deltas per action — positive values increase score, negative decrease */
|
|
39
|
+
const ACTION_DELTAS = {
|
|
40
|
+
// verification (+/-)
|
|
41
|
+
challenge_solved: 5,
|
|
42
|
+
challenge_failed: -3,
|
|
43
|
+
auth_success: 3,
|
|
44
|
+
auth_failure: -5,
|
|
45
|
+
// attestation
|
|
46
|
+
attestation_issued: 8,
|
|
47
|
+
attestation_verified: 4,
|
|
48
|
+
attestation_revoked: -10,
|
|
49
|
+
// delegation
|
|
50
|
+
delegation_granted: 6,
|
|
51
|
+
delegation_received: 10,
|
|
52
|
+
delegation_revoked: -8,
|
|
53
|
+
// session
|
|
54
|
+
session_created: 2,
|
|
55
|
+
session_expired: 1, // normal expiry is fine
|
|
56
|
+
session_terminated: -5, // force-terminated is suspicious
|
|
57
|
+
// violation
|
|
58
|
+
rate_limit_exceeded: -15,
|
|
59
|
+
invalid_token: -10,
|
|
60
|
+
abuse_detected: -50,
|
|
61
|
+
// endorsement
|
|
62
|
+
endorsement_received: 20,
|
|
63
|
+
endorsement_given: 3,
|
|
64
|
+
};
|
|
65
|
+
// ============ TIER LOGIC ============
|
|
66
|
+
export function getTier(score) {
|
|
67
|
+
if (score < 200)
|
|
68
|
+
return 'untrusted';
|
|
69
|
+
if (score < 400)
|
|
70
|
+
return 'low';
|
|
71
|
+
if (score < 600)
|
|
72
|
+
return 'neutral';
|
|
73
|
+
if (score < 800)
|
|
74
|
+
return 'good';
|
|
75
|
+
return 'excellent';
|
|
76
|
+
}
|
|
77
|
+
// ============ SCORE OPERATIONS ============
|
|
78
|
+
/**
|
|
79
|
+
* Clamp score to [MIN_SCORE, MAX_SCORE].
|
|
80
|
+
*/
|
|
81
|
+
function clamp(value) {
|
|
82
|
+
return Math.max(MIN_SCORE, Math.min(MAX_SCORE, value));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create a fresh reputation score for a new agent.
|
|
86
|
+
*/
|
|
87
|
+
function createDefaultScore(agentId, appId) {
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
return {
|
|
90
|
+
agent_id: agentId,
|
|
91
|
+
app_id: appId,
|
|
92
|
+
score: BASE_SCORE,
|
|
93
|
+
tier: getTier(BASE_SCORE),
|
|
94
|
+
event_count: 0,
|
|
95
|
+
positive_events: 0,
|
|
96
|
+
negative_events: 0,
|
|
97
|
+
last_event_at: null,
|
|
98
|
+
created_at: now,
|
|
99
|
+
updated_at: now,
|
|
100
|
+
category_scores: {
|
|
101
|
+
verification: 0,
|
|
102
|
+
attestation: 0,
|
|
103
|
+
delegation: 0,
|
|
104
|
+
session: 0,
|
|
105
|
+
violation: 0,
|
|
106
|
+
endorsement: 0,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Apply mean-reversion decay. For every 7 days since last activity,
|
|
112
|
+
* nudge the score 1% toward BASE_SCORE.
|
|
113
|
+
*/
|
|
114
|
+
export function applyDecay(score) {
|
|
115
|
+
if (!score.last_event_at)
|
|
116
|
+
return score;
|
|
117
|
+
const daysSinceActivity = (Date.now() - score.last_event_at) / (1000 * 60 * 60 * 24);
|
|
118
|
+
const decayPeriods = Math.floor(daysSinceActivity / 7);
|
|
119
|
+
if (decayPeriods <= 0)
|
|
120
|
+
return score;
|
|
121
|
+
const diff = score.score - BASE_SCORE;
|
|
122
|
+
// Each period decays 1% of distance from base
|
|
123
|
+
const decayFactor = Math.pow(0.99, decayPeriods);
|
|
124
|
+
const newScore = clamp(Math.round(BASE_SCORE + diff * decayFactor));
|
|
125
|
+
return {
|
|
126
|
+
...score,
|
|
127
|
+
score: newScore,
|
|
128
|
+
tier: getTier(newScore),
|
|
129
|
+
updated_at: Date.now(),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// ============ CORE FUNCTIONS ============
|
|
133
|
+
/**
|
|
134
|
+
* Get the reputation score for an agent. Creates a default score if none exists.
|
|
135
|
+
*/
|
|
136
|
+
export async function getReputationScore(sessions, agents, agentId, appId) {
|
|
137
|
+
try {
|
|
138
|
+
// Verify agent exists
|
|
139
|
+
const agentResult = await getTAPAgent(agents, agentId);
|
|
140
|
+
if (!agentResult.success || !agentResult.agent) {
|
|
141
|
+
return { success: false, error: 'Agent not found' };
|
|
142
|
+
}
|
|
143
|
+
const data = await sessions.get(`reputation:${agentId}`, 'text');
|
|
144
|
+
if (!data) {
|
|
145
|
+
// Return default score (don't persist until first event)
|
|
146
|
+
const defaultScore = createDefaultScore(agentId, appId);
|
|
147
|
+
return { success: true, score: defaultScore };
|
|
148
|
+
}
|
|
149
|
+
let score = JSON.parse(data);
|
|
150
|
+
// Apply decay
|
|
151
|
+
score = applyDecay(score);
|
|
152
|
+
return { success: true, score };
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.error('Failed to get reputation score:', error);
|
|
156
|
+
return { success: false, error: 'Internal server error' };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Record a reputation event for an agent.
|
|
161
|
+
* Creates the reputation record if it doesn't exist yet.
|
|
162
|
+
*/
|
|
163
|
+
export async function recordReputationEvent(sessions, agents, appId, options) {
|
|
164
|
+
try {
|
|
165
|
+
// Validate agent exists and belongs to app
|
|
166
|
+
const agentResult = await getTAPAgent(agents, options.agent_id);
|
|
167
|
+
if (!agentResult.success || !agentResult.agent) {
|
|
168
|
+
return { success: false, error: 'Agent not found' };
|
|
169
|
+
}
|
|
170
|
+
if (agentResult.agent.app_id !== appId) {
|
|
171
|
+
return { success: false, error: 'Agent does not belong to this app' };
|
|
172
|
+
}
|
|
173
|
+
// Validate source agent if provided (for endorsements)
|
|
174
|
+
if (options.source_agent_id) {
|
|
175
|
+
const sourceResult = await getTAPAgent(agents, options.source_agent_id);
|
|
176
|
+
if (!sourceResult.success || !sourceResult.agent) {
|
|
177
|
+
return { success: false, error: 'Source agent not found' };
|
|
178
|
+
}
|
|
179
|
+
if (sourceResult.agent.app_id !== appId) {
|
|
180
|
+
return { success: false, error: 'Source agent does not belong to this app' };
|
|
181
|
+
}
|
|
182
|
+
// Cannot endorse yourself
|
|
183
|
+
if (options.source_agent_id === options.agent_id) {
|
|
184
|
+
return { success: false, error: 'Agent cannot endorse itself' };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Validate action belongs to category
|
|
188
|
+
if (!isValidCategoryAction(options.category, options.action)) {
|
|
189
|
+
return { success: false, error: `Action "${options.action}" does not belong to category "${options.category}"` };
|
|
190
|
+
}
|
|
191
|
+
// Get or create score
|
|
192
|
+
const data = await sessions.get(`reputation:${options.agent_id}`, 'text');
|
|
193
|
+
let score = data
|
|
194
|
+
? JSON.parse(data)
|
|
195
|
+
: createDefaultScore(options.agent_id, appId);
|
|
196
|
+
// Apply decay before recording event
|
|
197
|
+
score = applyDecay(score);
|
|
198
|
+
// Calculate delta
|
|
199
|
+
const delta = ACTION_DELTAS[options.action] ?? 0;
|
|
200
|
+
const scoreBefore = score.score;
|
|
201
|
+
const scoreAfter = clamp(scoreBefore + delta);
|
|
202
|
+
// Create event record
|
|
203
|
+
const eventId = crypto.randomUUID();
|
|
204
|
+
const now = Date.now();
|
|
205
|
+
const event = {
|
|
206
|
+
event_id: eventId,
|
|
207
|
+
agent_id: options.agent_id,
|
|
208
|
+
app_id: appId,
|
|
209
|
+
category: options.category,
|
|
210
|
+
action: options.action,
|
|
211
|
+
delta,
|
|
212
|
+
score_before: scoreBefore,
|
|
213
|
+
score_after: scoreAfter,
|
|
214
|
+
source_agent_id: options.source_agent_id,
|
|
215
|
+
metadata: options.metadata,
|
|
216
|
+
created_at: now,
|
|
217
|
+
};
|
|
218
|
+
// Update score
|
|
219
|
+
score.score = scoreAfter;
|
|
220
|
+
score.tier = getTier(scoreAfter);
|
|
221
|
+
score.event_count += 1;
|
|
222
|
+
if (delta > 0)
|
|
223
|
+
score.positive_events += 1;
|
|
224
|
+
if (delta < 0)
|
|
225
|
+
score.negative_events += 1;
|
|
226
|
+
score.last_event_at = now;
|
|
227
|
+
score.updated_at = now;
|
|
228
|
+
score.category_scores[options.category] += delta;
|
|
229
|
+
// Persist score
|
|
230
|
+
await sessions.put(`reputation:${options.agent_id}`, JSON.stringify(score));
|
|
231
|
+
// Persist event with TTL
|
|
232
|
+
await sessions.put(`reputation_event:${eventId}`, JSON.stringify(event), { expirationTtl: EVENT_TTL_SECONDS });
|
|
233
|
+
// Update event index
|
|
234
|
+
await updateEventIndex(sessions, options.agent_id, eventId);
|
|
235
|
+
return { success: true, event, score };
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.error('Failed to record reputation event:', error);
|
|
239
|
+
return { success: false, error: 'Internal server error' };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* List reputation events for an agent.
|
|
244
|
+
* Returns the most recent events (up to limit).
|
|
245
|
+
*/
|
|
246
|
+
export async function listReputationEvents(sessions, agentId, options) {
|
|
247
|
+
try {
|
|
248
|
+
const limit = Math.min(options?.limit ?? 50, 100);
|
|
249
|
+
const indexKey = `reputation_events:${agentId}`;
|
|
250
|
+
const indexData = await sessions.get(indexKey, 'text');
|
|
251
|
+
const eventIds = indexData ? JSON.parse(indexData) : [];
|
|
252
|
+
// Most recent first (index is append-order, reverse for recency)
|
|
253
|
+
const recentIds = eventIds.slice(-limit).reverse();
|
|
254
|
+
const events = [];
|
|
255
|
+
for (const id of recentIds) {
|
|
256
|
+
const data = await sessions.get(`reputation_event:${id}`, 'text');
|
|
257
|
+
if (data) {
|
|
258
|
+
const event = JSON.parse(data);
|
|
259
|
+
if (!options?.category || event.category === options.category) {
|
|
260
|
+
events.push(event);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return { success: true, events, count: events.length };
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
console.error('Failed to list reputation events:', error);
|
|
268
|
+
return { success: false, error: 'Internal server error' };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Reset an agent's reputation to default. Used by app admins.
|
|
273
|
+
*/
|
|
274
|
+
export async function resetReputation(sessions, agents, agentId, appId) {
|
|
275
|
+
try {
|
|
276
|
+
// Verify agent exists and belongs to app
|
|
277
|
+
const agentResult = await getTAPAgent(agents, agentId);
|
|
278
|
+
if (!agentResult.success || !agentResult.agent) {
|
|
279
|
+
return { success: false, error: 'Agent not found' };
|
|
280
|
+
}
|
|
281
|
+
if (agentResult.agent.app_id !== appId) {
|
|
282
|
+
return { success: false, error: 'Agent does not belong to this app' };
|
|
283
|
+
}
|
|
284
|
+
const score = createDefaultScore(agentId, appId);
|
|
285
|
+
await sessions.put(`reputation:${agentId}`, JSON.stringify(score));
|
|
286
|
+
// Clear event index (events themselves expire via TTL)
|
|
287
|
+
await sessions.put(`reputation_events:${agentId}`, JSON.stringify([]));
|
|
288
|
+
return { success: true, score };
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
console.error('Failed to reset reputation:', error);
|
|
292
|
+
return { success: false, error: 'Internal server error' };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// ============ VALIDATION ============
|
|
296
|
+
const CATEGORY_ACTIONS = {
|
|
297
|
+
verification: ['challenge_solved', 'challenge_failed', 'auth_success', 'auth_failure'],
|
|
298
|
+
attestation: ['attestation_issued', 'attestation_verified', 'attestation_revoked'],
|
|
299
|
+
delegation: ['delegation_granted', 'delegation_received', 'delegation_revoked'],
|
|
300
|
+
session: ['session_created', 'session_expired', 'session_terminated'],
|
|
301
|
+
violation: ['rate_limit_exceeded', 'invalid_token', 'abuse_detected'],
|
|
302
|
+
endorsement: ['endorsement_received', 'endorsement_given'],
|
|
303
|
+
};
|
|
304
|
+
export function isValidCategoryAction(category, action) {
|
|
305
|
+
const validActions = CATEGORY_ACTIONS[category];
|
|
306
|
+
return validActions ? validActions.includes(action) : false;
|
|
307
|
+
}
|
|
308
|
+
export function isValidCategory(category) {
|
|
309
|
+
return category in CATEGORY_ACTIONS;
|
|
310
|
+
}
|
|
311
|
+
export function isValidAction(action) {
|
|
312
|
+
return action in ACTION_DELTAS;
|
|
313
|
+
}
|
|
314
|
+
// ============ UTILITY ============
|
|
315
|
+
async function updateEventIndex(sessions, agentId, eventId) {
|
|
316
|
+
try {
|
|
317
|
+
const key = `reputation_events:${agentId}`;
|
|
318
|
+
const data = await sessions.get(key, 'text');
|
|
319
|
+
let ids = data ? JSON.parse(data) : [];
|
|
320
|
+
ids.push(eventId);
|
|
321
|
+
// Trim to max size (keep most recent)
|
|
322
|
+
if (ids.length > MAX_EVENT_INDEX) {
|
|
323
|
+
ids = ids.slice(-MAX_EVENT_INDEX);
|
|
324
|
+
}
|
|
325
|
+
await sessions.put(key, JSON.stringify(ids));
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
console.error('Failed to update reputation event index:', error);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// ============ EXPORTS ============
|
|
332
|
+
export default {
|
|
333
|
+
getReputationScore,
|
|
334
|
+
recordReputationEvent,
|
|
335
|
+
listReputationEvents,
|
|
336
|
+
resetReputation,
|
|
337
|
+
getTier,
|
|
338
|
+
applyDecay,
|
|
339
|
+
isValidCategoryAction,
|
|
340
|
+
isValidCategory,
|
|
341
|
+
isValidAction,
|
|
342
|
+
ACTION_DELTAS,
|
|
343
|
+
BASE_SCORE,
|
|
344
|
+
MIN_SCORE,
|
|
345
|
+
MAX_SCORE,
|
|
346
|
+
};
|