@aamp/protocol 1.1.4 ā 1.1.6
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/aamp-protocol-1.1.5.tgz +0 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +9 -13
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +12 -9
- package/dist/crypto.js +9 -13
- package/dist/express.d.ts +1 -1
- package/dist/express.js +9 -12
- package/dist/index.d.ts +7 -7
- package/dist/index.js +7 -25
- package/dist/nextjs.d.ts +1 -1
- package/dist/nextjs.js +9 -12
- package/dist/proof.d.ts +9 -0
- package/dist/proof.js +27 -0
- package/dist/publisher.d.ts +6 -1
- package/dist/publisher.js +124 -39
- package/dist/types.d.ts +21 -2
- package/dist/types.js +6 -9
- package/package.json +10 -5
- package/src/agent.ts +8 -8
- package/src/constants.ts +13 -4
- package/src/crypto.ts +6 -1
- package/src/express.ts +8 -7
- package/src/index.ts +7 -7
- package/src/nextjs.ts +11 -10
- package/src/proof.ts +36 -0
- package/src/publisher.ts +157 -47
- package/src/types.ts +42 -9
- package/test/handshake.spec.ts +6 -6
- package/tsconfig.json +9 -3
package/src/publisher.ts
CHANGED
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
* Layer 2: Publisher Middleware
|
|
3
3
|
* Used by content owners to enforce policy, log access, and filter bots.
|
|
4
4
|
*/
|
|
5
|
-
import { HEADERS, MAX_CLOCK_SKEW_MS, WELL_KNOWN_AGENT_PATH } from './constants';
|
|
6
|
-
import { exportPublicKey, signData, verifySignature } from './crypto';
|
|
7
|
-
import { AccessPolicy, AccessPurpose, AgentIdentityManifest, ContentOrigin, EvaluationResult, IdentityCache, SignedAccessRequest, UnauthenticatedStrategy } from './types';
|
|
5
|
+
import { HEADERS, MAX_CLOCK_SKEW_MS, WELL_KNOWN_AGENT_PATH } from './constants.js';
|
|
6
|
+
import { exportPublicKey, signData, verifySignature } from './crypto.js';
|
|
7
|
+
import { AccessPolicy, AccessPurpose, AgentIdentityManifest, ContentOrigin, EvaluationResult, IdentityCache, SignedAccessRequest, UnauthenticatedStrategy } from './types.js';
|
|
8
|
+
import { verifyJwt } from './proof.js';
|
|
8
9
|
|
|
9
10
|
interface VerificationResult {
|
|
10
11
|
allowed: boolean;
|
|
11
12
|
reason: string;
|
|
12
13
|
identityVerified: boolean;
|
|
14
|
+
proofUsed?: string; // "WHITELIST", "CREDENTIAL_JWT", "AD_JWT"
|
|
15
|
+
visitorType?: string; // For audit logs
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
/**
|
|
@@ -18,7 +21,7 @@ interface VerificationResult {
|
|
|
18
21
|
*/
|
|
19
22
|
class MemoryCache implements IdentityCache {
|
|
20
23
|
private store = new Map<string, { val: string, exp: number }>();
|
|
21
|
-
|
|
24
|
+
|
|
22
25
|
async get(key: string): Promise<string | null> {
|
|
23
26
|
const item = this.store.get(key);
|
|
24
27
|
if (!item) return null;
|
|
@@ -28,11 +31,11 @@ class MemoryCache implements IdentityCache {
|
|
|
28
31
|
}
|
|
29
32
|
return item.val;
|
|
30
33
|
}
|
|
31
|
-
|
|
34
|
+
|
|
32
35
|
async set(key: string, value: string, ttlSeconds: number): Promise<void> {
|
|
33
|
-
this.store.set(key, {
|
|
34
|
-
val: value,
|
|
35
|
-
exp: Date.now() + (ttlSeconds * 1000)
|
|
36
|
+
this.store.set(key, {
|
|
37
|
+
val: value,
|
|
38
|
+
exp: Date.now() + (ttlSeconds * 1000)
|
|
36
39
|
});
|
|
37
40
|
}
|
|
38
41
|
}
|
|
@@ -42,14 +45,14 @@ export class AAMPPublisher {
|
|
|
42
45
|
private keyPair: CryptoKeyPair | null = null;
|
|
43
46
|
private unauthenticatedStrategy: UnauthenticatedStrategy;
|
|
44
47
|
private cache: IdentityCache;
|
|
45
|
-
|
|
48
|
+
|
|
46
49
|
// Default TTL: 1 Hour
|
|
47
|
-
private readonly CACHE_TTL_SECONDS = 3600;
|
|
50
|
+
private readonly CACHE_TTL_SECONDS = 3600;
|
|
48
51
|
|
|
49
52
|
constructor(
|
|
50
|
-
policy: AccessPolicy,
|
|
53
|
+
policy: AccessPolicy,
|
|
51
54
|
strategy: UnauthenticatedStrategy = 'PASSIVE',
|
|
52
|
-
cacheImpl?: IdentityCache
|
|
55
|
+
cacheImpl?: IdentityCache
|
|
53
56
|
) {
|
|
54
57
|
this.policy = policy;
|
|
55
58
|
this.unauthenticatedStrategy = strategy;
|
|
@@ -68,14 +71,20 @@ export class AAMPPublisher {
|
|
|
68
71
|
* Main Entry Point: Evaluate ANY visitor (Human, Bot, or Agent)
|
|
69
72
|
*/
|
|
70
73
|
async evaluateVisitor(
|
|
71
|
-
reqHeaders: Record<string, string | undefined>,
|
|
74
|
+
reqHeaders: Record<string, string | undefined>,
|
|
72
75
|
rawPayload?: string
|
|
73
76
|
): Promise<EvaluationResult> {
|
|
74
|
-
|
|
77
|
+
|
|
75
78
|
// 1. Check for AAMP Headers
|
|
76
79
|
const hasAamp = reqHeaders[HEADERS.PAYLOAD] && reqHeaders[HEADERS.SIGNATURE] && reqHeaders[HEADERS.PUBLIC_KEY];
|
|
77
80
|
|
|
81
|
+
const feedbackToken = reqHeaders[HEADERS.FEEDBACK];
|
|
82
|
+
if (feedbackToken) {
|
|
83
|
+
await this.handleFeedback(feedbackToken, reqHeaders);
|
|
84
|
+
}
|
|
85
|
+
|
|
78
86
|
if (hasAamp) {
|
|
87
|
+
console.log("\nš [AAMP Middleware] Detected Agent Headers. Starting Verification...");
|
|
79
88
|
// It claims to be an Agent. Verify it.
|
|
80
89
|
return await this.handleAgent(reqHeaders, rawPayload);
|
|
81
90
|
}
|
|
@@ -95,13 +104,13 @@ export class AAMPPublisher {
|
|
|
95
104
|
allowed: true,
|
|
96
105
|
status: 200,
|
|
97
106
|
reason: "PASSIVE_MODE: Allowed without verification.",
|
|
98
|
-
visitorType: 'LIKELY_HUMAN'
|
|
107
|
+
visitorType: 'LIKELY_HUMAN'
|
|
99
108
|
};
|
|
100
109
|
}
|
|
101
110
|
|
|
102
111
|
// 3. HYBRID MODE: Heuristic Analysis (The "Lazy Bot" Filter)
|
|
103
112
|
const isHuman = this.performBrowserHeuristics(reqHeaders);
|
|
104
|
-
|
|
113
|
+
|
|
105
114
|
if (isHuman) {
|
|
106
115
|
return {
|
|
107
116
|
allowed: true,
|
|
@@ -127,38 +136,27 @@ export class AAMPPublisher {
|
|
|
127
136
|
*/
|
|
128
137
|
private performBrowserHeuristics(headers: Record<string, string | undefined>): boolean {
|
|
129
138
|
const userAgent = headers['user-agent'] || '';
|
|
130
|
-
|
|
139
|
+
|
|
131
140
|
// A. The "Obvious Bot" Blocklist (Fast Fail)
|
|
132
141
|
const botSignatures = ['python-requests', 'curl', 'wget', 'scrapy', 'bot', 'crawler', 'spider'];
|
|
133
|
-
// Exception: Googlebot (if you want SEO). We'll treat Googlebot as a bot,
|
|
134
|
-
// real implementations might white-list it via IP verification (not possible in just JS headers).
|
|
135
142
|
if (botSignatures.some(sig => userAgent.toLowerCase().includes(sig))) {
|
|
136
|
-
|
|
143
|
+
return false;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
// B. Trusted Infrastructure Signals (The Real World Solution)
|
|
140
|
-
// If Cloudflare or Vercel says "This is a real user", we trust them.
|
|
141
|
-
// Cloudflare: 'cf-visitor' exists. 'cf-ipcountry' exists.
|
|
142
147
|
if (headers['cf-visitor'] || headers['cf-ray']) return true;
|
|
143
|
-
|
|
144
|
-
// Vercel: 'x-vercel-id'
|
|
145
148
|
if (headers['x-vercel-id']) return true;
|
|
146
|
-
|
|
147
|
-
// AWS CloudFront: 'cloudfront-viewer-address'
|
|
148
149
|
if (headers['cloudfront-viewer-address']) return true;
|
|
149
150
|
|
|
150
151
|
// C. The "Browser Fingerprint" (Fallback for direct connections)
|
|
151
|
-
// Real browsers almost always send these headers
|
|
152
152
|
const hasAcceptLanguage = !!headers['accept-language'];
|
|
153
|
-
const hasSecFetchDest = !!headers['sec-fetch-dest'];
|
|
153
|
+
const hasSecFetchDest = !!headers['sec-fetch-dest'];
|
|
154
154
|
const hasUpgradeInsecure = !!headers['upgrade-insecure-requests'];
|
|
155
155
|
|
|
156
|
-
// If it has typical browser headers, we allow it.
|
|
157
156
|
if (hasAcceptLanguage && (hasSecFetchDest || hasUpgradeInsecure)) {
|
|
158
157
|
return true;
|
|
159
158
|
}
|
|
160
159
|
|
|
161
|
-
// If it has no browser headers and no trusted proxy headers -> It's likely a script.
|
|
162
160
|
return false;
|
|
163
161
|
}
|
|
164
162
|
|
|
@@ -171,9 +169,9 @@ export class AAMPPublisher {
|
|
|
171
169
|
const sigHeader = reqHeaders[HEADERS.SIGNATURE]!;
|
|
172
170
|
const keyHeader = reqHeaders[HEADERS.PUBLIC_KEY]!;
|
|
173
171
|
|
|
174
|
-
const headerJson = atob(payloadHeader);
|
|
172
|
+
const headerJson = atob(payloadHeader);
|
|
175
173
|
const requestHeader = JSON.parse(headerJson);
|
|
176
|
-
|
|
174
|
+
|
|
177
175
|
const signedRequest: SignedAccessRequest = {
|
|
178
176
|
header: requestHeader,
|
|
179
177
|
signature: sigHeader,
|
|
@@ -188,10 +186,20 @@ export class AAMPPublisher {
|
|
|
188
186
|
["verify"]
|
|
189
187
|
);
|
|
190
188
|
|
|
189
|
+
const proofToken = reqHeaders[HEADERS.PROOF_TOKEN];
|
|
190
|
+
const paymentCredential = reqHeaders[HEADERS.PAYMENT_CREDENTIAL];
|
|
191
|
+
|
|
191
192
|
// Verify Core Logic
|
|
192
|
-
const result = await this.verifyRequestLogic(signedRequest, agentKey, headerJson);
|
|
193
|
+
const result = await this.verifyRequestLogic(signedRequest, agentKey, proofToken, paymentCredential, headerJson);
|
|
193
194
|
|
|
194
195
|
if (!result.allowed) {
|
|
196
|
+
console.log(`ā [AAMP BLOCK]
|
|
197
|
+
Agent: ${requestHeader.agent_id}
|
|
198
|
+
Reason: ${result.reason}
|
|
199
|
+
VisitorType: ${result.visitorType}
|
|
200
|
+
Proof: ${result.proofUsed || 'None'}
|
|
201
|
+
Identity Verified: ${result.identityVerified}`);
|
|
202
|
+
|
|
195
203
|
return {
|
|
196
204
|
allowed: false,
|
|
197
205
|
status: 403,
|
|
@@ -200,6 +208,12 @@ export class AAMPPublisher {
|
|
|
200
208
|
};
|
|
201
209
|
}
|
|
202
210
|
|
|
211
|
+
console.log(`ā
[AAMP ALLOW]
|
|
212
|
+
Agent: ${requestHeader.agent_id}
|
|
213
|
+
Reason: AAMP_VERIFIED
|
|
214
|
+
Payment Method: ${result.proofUsed}
|
|
215
|
+
Identity Verified: ${result.identityVerified}`);
|
|
216
|
+
|
|
203
217
|
return {
|
|
204
218
|
allowed: true,
|
|
205
219
|
status: 200,
|
|
@@ -209,16 +223,19 @@ export class AAMPPublisher {
|
|
|
209
223
|
};
|
|
210
224
|
|
|
211
225
|
} catch (e) {
|
|
226
|
+
console.error(e);
|
|
212
227
|
return { allowed: false, status: 400, reason: "INVALID_SIGNATURE", visitorType: 'UNIDENTIFIED_BOT' };
|
|
213
228
|
}
|
|
214
229
|
}
|
|
215
230
|
|
|
216
231
|
private async verifyRequestLogic(
|
|
217
|
-
request: SignedAccessRequest,
|
|
232
|
+
request: SignedAccessRequest,
|
|
218
233
|
requestPublicKey: CryptoKey,
|
|
234
|
+
proofToken?: string,
|
|
235
|
+
paymentCredential?: string,
|
|
219
236
|
rawPayload?: string
|
|
220
237
|
): Promise<VerificationResult> {
|
|
221
|
-
|
|
238
|
+
|
|
222
239
|
// 1. Replay Attack Prevention
|
|
223
240
|
const requestTime = new Date(request.header.ts).getTime();
|
|
224
241
|
if (Math.abs(Date.now() - requestTime) > MAX_CLOCK_SKEW_MS) {
|
|
@@ -234,11 +251,14 @@ export class AAMPPublisher {
|
|
|
234
251
|
let identityVerified = false;
|
|
235
252
|
const claimedDomain = request.header.agent_id;
|
|
236
253
|
const pubKeyString = await exportPublicKey(requestPublicKey);
|
|
237
|
-
|
|
254
|
+
|
|
255
|
+
console.log(` š [AAMP Identity] Verifying DNS Binding for: ${claimedDomain}`);
|
|
256
|
+
|
|
238
257
|
// Check Cache First
|
|
239
258
|
const cachedKey = await this.cache.get(claimedDomain);
|
|
240
|
-
|
|
259
|
+
|
|
241
260
|
if (cachedKey === pubKeyString) {
|
|
261
|
+
console.log(" ā” [AAMP Cache] Identity found in cache.");
|
|
242
262
|
identityVerified = true;
|
|
243
263
|
} else if (this.isDomain(claimedDomain)) {
|
|
244
264
|
// Cache Miss: Perform DNS Fetch
|
|
@@ -260,11 +280,53 @@ export class AAMPPublisher {
|
|
|
260
280
|
return { allowed: false, reason: 'POLICY_DENIED: RAG not allowed.', identityVerified };
|
|
261
281
|
}
|
|
262
282
|
|
|
263
|
-
// 5. Policy Check: Economics
|
|
283
|
+
// 5. Policy Check: Economics (v1.2)
|
|
264
284
|
if (this.policy.requiresPayment) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
285
|
+
let paymentSatisfied = false;
|
|
286
|
+
|
|
287
|
+
// Method A: Flexible Payment Callback (DB / Custom Logic)
|
|
288
|
+
if (this.policy.monetization?.checkPayment) {
|
|
289
|
+
const isPaid = await this.policy.monetization.checkPayment(request.header.agent_id, request.header.purpose);
|
|
290
|
+
if (isPaid) {
|
|
291
|
+
console.log(` š° [AAMP Audit] Whitelist Check Passed via Callback.`);
|
|
292
|
+
return { allowed: true, reason: 'OK', identityVerified, proofUsed: 'WHITELIST_CALLBACK' };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Method B: Payment Credentials (Unified JWT)
|
|
297
|
+
if (!paymentSatisfied && this.policy.monetization?.paymentConfig && paymentCredential) {
|
|
298
|
+
const { jwksUrl, issuer } = this.policy.monetization.paymentConfig;
|
|
299
|
+
console.log(` š [AAMP Audit] Verifying Payment Credential (Issuer: ${issuer})...`);
|
|
300
|
+
|
|
301
|
+
const isValidCredential = await verifyJwt(paymentCredential, jwksUrl, issuer);
|
|
302
|
+
if (isValidCredential) {
|
|
303
|
+
console.log(` ā
[AAMP Audit] Credential Signature VALID.`);
|
|
304
|
+
return { allowed: true, reason: 'OK', identityVerified, proofUsed: 'PAYMENT_CREDENTIAL_JWT' };
|
|
305
|
+
} else {
|
|
306
|
+
console.log(` ā [AAMP Audit] Credential Signature INVALID.`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Method C: Ad-Supported (Proof Verification)
|
|
311
|
+
if (!paymentSatisfied && this.policy.allowAdSupportedAccess && request.header.context.ads_displayed) {
|
|
312
|
+
if (proofToken && this.policy.monetization?.adNetwork) {
|
|
313
|
+
const { jwksUrl, issuer } = this.policy.monetization.adNetwork;
|
|
314
|
+
console.log(` šŗ [AAMP Audit] Verifying Ad Proof (Issuer: ${issuer})...`);
|
|
315
|
+
|
|
316
|
+
const isValidProof = await verifyJwt(proofToken, jwksUrl, issuer);
|
|
317
|
+
if (isValidProof) {
|
|
318
|
+
console.log(` ā
[AAMP Audit] Ad Proof Signature VALID.`);
|
|
319
|
+
return { allowed: true, reason: 'OK', identityVerified, proofUsed: 'AD_PROOF_JWT' };
|
|
320
|
+
} else {
|
|
321
|
+
console.log(` ā [AAMP Audit] Ad Proof Signature INVALID.`);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
console.log(` ā ļø [AAMP Audit] Ad Proof MISSING.`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!paymentSatisfied) {
|
|
329
|
+
return { allowed: false, reason: 'PAYMENT_REQUIRED: Whitelist, Credential, and Ad Proof checks ALL failed.', identityVerified, proofUsed: 'NONE', visitorType: 'VERIFIED_AGENT' };
|
|
268
330
|
}
|
|
269
331
|
}
|
|
270
332
|
|
|
@@ -276,18 +338,42 @@ export class AAMPPublisher {
|
|
|
276
338
|
// Allow HTTP for localhost testing
|
|
277
339
|
const protocol = (domain.includes('localhost') || domain.match(/:\d+$/)) ? 'http' : 'https';
|
|
278
340
|
const url = `${protocol}://${domain}${WELL_KNOWN_AGENT_PATH}`;
|
|
279
|
-
|
|
341
|
+
|
|
342
|
+
console.log(` š [AAMP DNS] Fetching Manifest: ${url} ...`);
|
|
343
|
+
|
|
280
344
|
// In production, we need a short timeout to prevent hanging
|
|
281
345
|
const controller = new AbortController();
|
|
282
346
|
const timeoutId = setTimeout(() => controller.abort(), 1500); // 1.5s max for DNS check
|
|
283
|
-
|
|
347
|
+
|
|
284
348
|
const response = await fetch(url, { signal: controller.signal });
|
|
285
349
|
clearTimeout(timeoutId);
|
|
286
|
-
|
|
287
|
-
if (!response.ok)
|
|
350
|
+
|
|
351
|
+
if (!response.ok) {
|
|
352
|
+
console.log(` ā [AAMP DNS] Fetch Failed: ${response.status}`);
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
|
|
288
356
|
const manifest = await response.json() as AgentIdentityManifest;
|
|
289
|
-
|
|
290
|
-
|
|
357
|
+
console.log(` š [AAMP DNS] Manifest received. Agent ID: ${manifest.agent_id}`);
|
|
358
|
+
|
|
359
|
+
// CHECK 1: Does the manifest actually belong to the domain?
|
|
360
|
+
if (manifest.agent_id !== domain) {
|
|
361
|
+
console.log(` ā [AAMP DNS] Mismatch: Manifest ID ${manifest.agent_id} != Claimed ${domain}`);
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// CHECK 2: Does the key match?
|
|
366
|
+
if (manifest.public_key !== requestKeySpki) {
|
|
367
|
+
console.log(` ā [AAMP DNS] Key Mismatch: DNS Key != Request Key`);
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
console.log(` ā
[AAMP DNS] Identity Confirmed.`);
|
|
372
|
+
return true;
|
|
373
|
+
} catch (e: any) {
|
|
374
|
+
console.log(` ā [AAMP DNS] Error: ${e.message}`);
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
291
377
|
}
|
|
292
378
|
|
|
293
379
|
private isDomain(s: string): boolean {
|
|
@@ -304,4 +390,28 @@ export class AAMPPublisher {
|
|
|
304
390
|
[HEADERS.PROVENANCE_SIG]: signature
|
|
305
391
|
};
|
|
306
392
|
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Handling Quality Feedback (The "Dispute" Layer)
|
|
396
|
+
* This runs when an Agent sends 'x-aamp-feedback'.
|
|
397
|
+
*/
|
|
398
|
+
private async handleFeedback(token: string, headers: Record<string, string | undefined>) {
|
|
399
|
+
// NOTE: In production, you would fetch the Agent's specific key.
|
|
400
|
+
// For now, we assume standard Discovery or a centralized Key Set (like adNetwork).
|
|
401
|
+
// Ideally, the SDK config should have a 'qualityOracle' key set.
|
|
402
|
+
|
|
403
|
+
// 1. We just Decode it to Log it (Verification is optional but recommended)
|
|
404
|
+
try {
|
|
405
|
+
const parts = token.split('.');
|
|
406
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
407
|
+
|
|
408
|
+
console.log(`\nš¢ [AAMP QUALITY ALERT] Feedback Received from ${payload.agent_id}`);
|
|
409
|
+
console.log(` Reason: ${payload.reason} | Score: ${payload.quality_score}`);
|
|
410
|
+
console.log(` Resource: ${payload.url}`);
|
|
411
|
+
console.log(` (Signature available for dispute evidence)`);
|
|
412
|
+
|
|
413
|
+
} catch (e) {
|
|
414
|
+
console.log(` ā ļø [AAMP Warning] Malformed Feedback Token.`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
307
417
|
}
|
package/src/types.ts
CHANGED
|
@@ -43,14 +43,35 @@ export interface IdentityCache {
|
|
|
43
43
|
set(key: string, value: string, ttlSeconds: number): Promise<void>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Optional Monetization (The Settlement Layer)
|
|
48
|
+
*/
|
|
46
49
|
/**
|
|
47
50
|
* Optional Monetization (The Settlement Layer)
|
|
48
51
|
*/
|
|
49
52
|
export interface MonetizationConfig {
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
// Method 1: Payments (Flexible Callback)
|
|
54
|
+
// Developers implement their own logic (Database check, CMS lookup, etc.)
|
|
55
|
+
// Returns TRUE if the agent is a paid subscriber for this specific purpose.
|
|
56
|
+
checkPayment?: (agentId: string, purpose: string) => boolean | Promise<boolean>;
|
|
57
|
+
|
|
58
|
+
// Method 2: Ads (Proof Verification)
|
|
59
|
+
// Configuration to verify tokens from your Ad Provider (e.g. Google)
|
|
60
|
+
adNetwork?: {
|
|
61
|
+
jwksUrl: string; // e.g. "https://www.googleapis.com/oauth2/v3/certs"
|
|
62
|
+
issuer: string; // e.g. "https://accounts.google.com"
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Method 3: Payment Credentials (Unified JWT)
|
|
66
|
+
// Verifies "x-aamp-credential" for Broker or Direct payments.
|
|
67
|
+
paymentConfig?: {
|
|
68
|
+
jwksUrl: string; // e.g. "https://my-site.com/.well-known/jwks.json"
|
|
69
|
+
issuer: string; // e.g. "my-site.com"
|
|
70
|
+
};
|
|
52
71
|
}
|
|
53
72
|
|
|
73
|
+
|
|
74
|
+
|
|
54
75
|
/**
|
|
55
76
|
* Handling Non-AAMP Visitors
|
|
56
77
|
*
|
|
@@ -65,15 +86,15 @@ export interface AccessPolicy {
|
|
|
65
86
|
allowTraining: boolean;
|
|
66
87
|
allowRAG: boolean;
|
|
67
88
|
attributionRequired: boolean;
|
|
68
|
-
|
|
89
|
+
|
|
69
90
|
// Economic Signals
|
|
70
|
-
allowAdSupportedAccess: boolean;
|
|
71
|
-
requiresPayment: boolean;
|
|
91
|
+
allowAdSupportedAccess: boolean;
|
|
92
|
+
requiresPayment: boolean;
|
|
72
93
|
paymentPointer?: string;
|
|
73
94
|
|
|
74
95
|
// Identity Strictness
|
|
75
|
-
requireIdentityBinding?: boolean;
|
|
76
|
-
|
|
96
|
+
requireIdentityBinding?: boolean;
|
|
97
|
+
|
|
77
98
|
// V1.1: Optional Settlement Info
|
|
78
99
|
monetization?: MonetizationConfig;
|
|
79
100
|
}
|
|
@@ -81,7 +102,7 @@ export interface AccessPolicy {
|
|
|
81
102
|
export interface ProtocolHeader {
|
|
82
103
|
v: '1.1';
|
|
83
104
|
ts: string;
|
|
84
|
-
agent_id: string;
|
|
105
|
+
agent_id: string;
|
|
85
106
|
resource: string;
|
|
86
107
|
purpose: AccessPurpose;
|
|
87
108
|
context: {
|
|
@@ -98,11 +119,12 @@ export interface SignedAccessRequest {
|
|
|
98
119
|
export interface FeedbackSignal {
|
|
99
120
|
target_resource: string;
|
|
100
121
|
agent_id: string;
|
|
101
|
-
quality_score: number;
|
|
122
|
+
quality_score: number;
|
|
102
123
|
flags: QualityFlag[];
|
|
103
124
|
timestamp: string;
|
|
104
125
|
}
|
|
105
126
|
|
|
127
|
+
// Result of the full evaluation pipeline
|
|
106
128
|
// Result of the full evaluation pipeline
|
|
107
129
|
export interface EvaluationResult {
|
|
108
130
|
allowed: boolean;
|
|
@@ -110,4 +132,15 @@ export interface EvaluationResult {
|
|
|
110
132
|
reason: string;
|
|
111
133
|
visitorType: 'VERIFIED_AGENT' | 'LIKELY_HUMAN' | 'UNIDENTIFIED_BOT';
|
|
112
134
|
metadata?: any;
|
|
135
|
+
payment_status?: 'PAID_SUBSCRIBER' | 'AD_FUNDED' | 'UNPAID';
|
|
136
|
+
proofUsed?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Signed Quality Feedback (The "Report Card")
|
|
140
|
+
export interface FeedbackSignalToken {
|
|
141
|
+
url: string; // The resource being flagged
|
|
142
|
+
agent_id: string; // Who is flagging it (e.g. "bot.openai.com")
|
|
143
|
+
quality_score: number; // 0.0 to 1.0
|
|
144
|
+
reason: string; // e.g. "SEO_SPAM", "HATE_SPEECH"
|
|
145
|
+
timestamp: number;
|
|
113
146
|
}
|
package/test/handshake.spec.ts
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Run using: npm test
|
|
4
4
|
* Location: sdk/typescript/test/handshake.spec.ts
|
|
5
5
|
*/
|
|
6
|
-
import { AAMPAgent } from '../src/agent';
|
|
7
|
-
import { AAMPPublisher } from '../src/publisher';
|
|
8
|
-
import { AccessPurpose } from '../src/types';
|
|
9
|
-
import { HEADERS } from '../src/constants';
|
|
6
|
+
import { AAMPAgent } from '../src/agent.js';
|
|
7
|
+
import { AAMPPublisher } from '../src/publisher.js';
|
|
8
|
+
import { AccessPurpose } from '../src/types.js';
|
|
9
|
+
import { HEADERS } from '../src/constants.js';
|
|
10
10
|
|
|
11
11
|
async function runTest() {
|
|
12
12
|
console.log("--- STARTING AAMP HANDSHAKE TEST ---");
|
|
@@ -29,7 +29,7 @@ async function runTest() {
|
|
|
29
29
|
// TEST CASE A: Requesting RAG without Ads (Should FAIL due to Payment Requirement)
|
|
30
30
|
console.log("\n[TEST A] Requesting RAG (No Ads)...");
|
|
31
31
|
const reqA = await agent.createAccessRequest('/doc/1', AccessPurpose.RAG_RETRIEVAL, { adsDisplayed: false });
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
const payloadA = JSON.stringify(reqA.header);
|
|
34
34
|
const headersA = {
|
|
35
35
|
[HEADERS.PAYLOAD]: btoa(payloadA),
|
|
@@ -45,7 +45,7 @@ async function runTest() {
|
|
|
45
45
|
// TEST CASE B: Requesting RAG WITH Ads (Should SUCCEED via Exemption)
|
|
46
46
|
console.log("\n[TEST B] Requesting RAG (With Ads)...");
|
|
47
47
|
const reqB = await agent.createAccessRequest('/doc/1', AccessPurpose.RAG_RETRIEVAL, { adsDisplayed: true });
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
const payloadB = JSON.stringify(reqB.header);
|
|
50
50
|
const headersB = {
|
|
51
51
|
[HEADERS.PAYLOAD]: btoa(payloadB),
|
package/tsconfig.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES2020",
|
|
4
|
-
"module": "
|
|
5
|
-
"
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2020",
|
|
8
|
+
"DOM"
|
|
9
|
+
],
|
|
6
10
|
"declaration": true,
|
|
7
11
|
"outDir": "./dist",
|
|
8
12
|
"rootDir": "./src",
|
|
@@ -11,5 +15,7 @@
|
|
|
11
15
|
"skipLibCheck": true,
|
|
12
16
|
"forceConsistentCasingInFileNames": true
|
|
13
17
|
},
|
|
14
|
-
"include": [
|
|
18
|
+
"include": [
|
|
19
|
+
"src/**/*"
|
|
20
|
+
]
|
|
15
21
|
}
|