@aamp/protocol 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +7 -2
- package/dist/agent.js +15 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +4 -1
- package/dist/express.d.ts +3 -1
- package/dist/express.js +28 -36
- package/dist/nextjs.d.ts +3 -1
- package/dist/nextjs.js +15 -30
- package/dist/publisher.d.ts +20 -17
- package/dist/publisher.js +205 -39
- package/dist/types.d.ts +30 -17
- package/package.json +23 -23
- package/src/agent.ts +87 -71
- package/src/constants.ts +38 -34
- package/src/crypto.ts +68 -68
- package/src/express.ts +94 -103
- package/src/index.ts +12 -12
- package/src/nextjs.ts +91 -108
- package/src/publisher.ts +302 -106
- package/src/types.ts +112 -97
- package/test/handshake.spec.ts +62 -66
- package/tsconfig.json +14 -14
package/src/publisher.ts
CHANGED
|
@@ -1,107 +1,303 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 2: Publisher Middleware
|
|
3
|
-
* Used by content owners to enforce policy and
|
|
4
|
-
*/
|
|
5
|
-
import { AccessPolicy, AccessPurpose, SignedAccessRequest, ContentOrigin, FeedbackSignal } from './types';
|
|
6
|
-
import { verifySignature, signData } from './crypto';
|
|
7
|
-
import { MAX_CLOCK_SKEW_MS, HEADERS } from './constants';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
allowed: boolean;
|
|
11
|
-
reason: string;
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2: Publisher Middleware
|
|
3
|
+
* Used by content owners to enforce policy, log access, and filter bots.
|
|
4
|
+
*/
|
|
5
|
+
import { AccessPolicy, AccessPurpose, SignedAccessRequest, ContentOrigin, FeedbackSignal, AgentIdentityManifest, EvaluationResult, UnauthenticatedStrategy, IdentityCache } from './types';
|
|
6
|
+
import { verifySignature, signData, exportPublicKey } from './crypto';
|
|
7
|
+
import { MAX_CLOCK_SKEW_MS, HEADERS, WELL_KNOWN_AGENT_PATH } from './constants';
|
|
8
|
+
|
|
9
|
+
interface VerificationResult {
|
|
10
|
+
allowed: boolean;
|
|
11
|
+
reason: string;
|
|
12
|
+
identityVerified: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default In-Memory Cache (Fallback only)
|
|
17
|
+
* NOT recommended for high-traffic Serverless production.
|
|
18
|
+
*/
|
|
19
|
+
class MemoryCache implements IdentityCache {
|
|
20
|
+
private store = new Map<string, { val: string, exp: number }>();
|
|
21
|
+
|
|
22
|
+
async get(key: string): Promise<string | null> {
|
|
23
|
+
const item = this.store.get(key);
|
|
24
|
+
if (!item) return null;
|
|
25
|
+
if (Date.now() > item.exp) {
|
|
26
|
+
this.store.delete(key);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return item.val;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
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
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class AAMPPublisher {
|
|
41
|
+
private policy: AccessPolicy;
|
|
42
|
+
private keyPair: CryptoKeyPair | null = null;
|
|
43
|
+
private unauthenticatedStrategy: UnauthenticatedStrategy;
|
|
44
|
+
private cache: IdentityCache;
|
|
45
|
+
|
|
46
|
+
// Default TTL: 1 Hour
|
|
47
|
+
private readonly CACHE_TTL_SECONDS = 3600;
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
policy: AccessPolicy,
|
|
51
|
+
strategy: UnauthenticatedStrategy = 'PASSIVE',
|
|
52
|
+
cacheImpl?: IdentityCache
|
|
53
|
+
) {
|
|
54
|
+
this.policy = policy;
|
|
55
|
+
this.unauthenticatedStrategy = strategy;
|
|
56
|
+
this.cache = cacheImpl || new MemoryCache();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async initialize(keyPair: CryptoKeyPair) {
|
|
60
|
+
this.keyPair = keyPair;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getPolicy(): AccessPolicy {
|
|
64
|
+
return this.policy;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Main Entry Point: Evaluate ANY visitor (Human, Bot, or Agent)
|
|
69
|
+
*/
|
|
70
|
+
async evaluateVisitor(
|
|
71
|
+
reqHeaders: Record<string, string | undefined>,
|
|
72
|
+
rawPayload?: string
|
|
73
|
+
): Promise<EvaluationResult> {
|
|
74
|
+
|
|
75
|
+
// 1. Check for AAMP Headers
|
|
76
|
+
const hasAamp = reqHeaders[HEADERS.PAYLOAD] && reqHeaders[HEADERS.SIGNATURE] && reqHeaders[HEADERS.PUBLIC_KEY];
|
|
77
|
+
|
|
78
|
+
if (hasAamp) {
|
|
79
|
+
// It claims to be an Agent. Verify it.
|
|
80
|
+
return await this.handleAgent(reqHeaders, rawPayload);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 2. It's not an AAMP Agent. Apply Strategy.
|
|
84
|
+
if (this.unauthenticatedStrategy === 'STRICT') {
|
|
85
|
+
return {
|
|
86
|
+
allowed: false,
|
|
87
|
+
status: 401,
|
|
88
|
+
reason: "STRICT_MODE: Only AAMP verified agents allowed.",
|
|
89
|
+
visitorType: 'UNIDENTIFIED_BOT'
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (this.unauthenticatedStrategy === 'PASSIVE') {
|
|
94
|
+
return {
|
|
95
|
+
allowed: true,
|
|
96
|
+
status: 200,
|
|
97
|
+
reason: "PASSIVE_MODE: Allowed without verification.",
|
|
98
|
+
visitorType: 'LIKELY_HUMAN'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 3. HYBRID MODE: Heuristic Analysis (The "Lazy Bot" Filter)
|
|
103
|
+
const isHuman = this.performBrowserHeuristics(reqHeaders);
|
|
104
|
+
|
|
105
|
+
if (isHuman) {
|
|
106
|
+
return {
|
|
107
|
+
allowed: true,
|
|
108
|
+
status: 200,
|
|
109
|
+
reason: "BROWSER_VERIFIED: Heuristics passed.",
|
|
110
|
+
visitorType: 'LIKELY_HUMAN'
|
|
111
|
+
};
|
|
112
|
+
} else {
|
|
113
|
+
return {
|
|
114
|
+
allowed: false,
|
|
115
|
+
status: 403,
|
|
116
|
+
reason: "BOT_DETECTED: Request lacks browser signatures and AAMP headers.",
|
|
117
|
+
visitorType: 'UNIDENTIFIED_BOT'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Browser Heuristics (Hardened)
|
|
124
|
+
* 1. Checks Known Bot Signatures (Fast Fail)
|
|
125
|
+
* 2. Checks Trusted Upstream Signals (Cloudflare/Vercel)
|
|
126
|
+
* 3. Checks Browser Header Consistency
|
|
127
|
+
*/
|
|
128
|
+
private performBrowserHeuristics(headers: Record<string, string | undefined>): boolean {
|
|
129
|
+
const userAgent = headers['user-agent'] || '';
|
|
130
|
+
|
|
131
|
+
// A. The "Obvious Bot" Blocklist (Fast Fail)
|
|
132
|
+
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
|
+
if (botSignatures.some(sig => userAgent.toLowerCase().includes(sig))) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 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
|
+
if (headers['cf-visitor'] || headers['cf-ray']) return true;
|
|
143
|
+
|
|
144
|
+
// Vercel: 'x-vercel-id'
|
|
145
|
+
if (headers['x-vercel-id']) return true;
|
|
146
|
+
|
|
147
|
+
// AWS CloudFront: 'cloudfront-viewer-address'
|
|
148
|
+
if (headers['cloudfront-viewer-address']) return true;
|
|
149
|
+
|
|
150
|
+
// C. The "Browser Fingerprint" (Fallback for direct connections)
|
|
151
|
+
// Real browsers almost always send these headers
|
|
152
|
+
const hasAcceptLanguage = !!headers['accept-language'];
|
|
153
|
+
const hasSecFetchDest = !!headers['sec-fetch-dest'];
|
|
154
|
+
const hasUpgradeInsecure = !!headers['upgrade-insecure-requests'];
|
|
155
|
+
|
|
156
|
+
// If it has typical browser headers, we allow it.
|
|
157
|
+
if (hasAcceptLanguage && (hasSecFetchDest || hasUpgradeInsecure)) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If it has no browser headers and no trusted proxy headers -> It's likely a script.
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Handle AAMP Protocol Logic
|
|
167
|
+
*/
|
|
168
|
+
private async handleAgent(reqHeaders: Record<string, string | undefined>, rawPayload?: string): Promise<EvaluationResult> {
|
|
169
|
+
try {
|
|
170
|
+
const payloadHeader = reqHeaders[HEADERS.PAYLOAD]!;
|
|
171
|
+
const sigHeader = reqHeaders[HEADERS.SIGNATURE]!;
|
|
172
|
+
const keyHeader = reqHeaders[HEADERS.PUBLIC_KEY]!;
|
|
173
|
+
|
|
174
|
+
const headerJson = atob(payloadHeader);
|
|
175
|
+
const requestHeader = JSON.parse(headerJson);
|
|
176
|
+
|
|
177
|
+
const signedRequest: SignedAccessRequest = {
|
|
178
|
+
header: requestHeader,
|
|
179
|
+
signature: sigHeader,
|
|
180
|
+
publicKey: keyHeader
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const agentKey = await crypto.subtle.importKey(
|
|
184
|
+
"spki",
|
|
185
|
+
new Uint8Array(atob(keyHeader).split('').map(c => c.charCodeAt(0))),
|
|
186
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
187
|
+
true,
|
|
188
|
+
["verify"]
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Verify Core Logic
|
|
192
|
+
const result = await this.verifyRequestLogic(signedRequest, agentKey, headerJson);
|
|
193
|
+
|
|
194
|
+
if (!result.allowed) {
|
|
195
|
+
return {
|
|
196
|
+
allowed: false,
|
|
197
|
+
status: 403,
|
|
198
|
+
reason: result.reason,
|
|
199
|
+
visitorType: 'VERIFIED_AGENT'
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
allowed: true,
|
|
205
|
+
status: 200,
|
|
206
|
+
reason: "AAMP_VERIFIED",
|
|
207
|
+
visitorType: 'VERIFIED_AGENT',
|
|
208
|
+
metadata: requestHeader
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
} catch (e) {
|
|
212
|
+
return { allowed: false, status: 400, reason: "INVALID_SIGNATURE", visitorType: 'UNIDENTIFIED_BOT' };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async verifyRequestLogic(
|
|
217
|
+
request: SignedAccessRequest,
|
|
218
|
+
requestPublicKey: CryptoKey,
|
|
219
|
+
rawPayload?: string
|
|
220
|
+
): Promise<VerificationResult> {
|
|
221
|
+
|
|
222
|
+
// 1. Replay Attack Prevention
|
|
223
|
+
const requestTime = new Date(request.header.ts).getTime();
|
|
224
|
+
if (Math.abs(Date.now() - requestTime) > MAX_CLOCK_SKEW_MS) {
|
|
225
|
+
return { allowed: false, reason: 'TIMESTAMP_INVALID', identityVerified: false };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 2. Crypto Verification
|
|
229
|
+
const signableString = rawPayload || JSON.stringify(request.header);
|
|
230
|
+
const isCryptoValid = await verifySignature(requestPublicKey, signableString, request.signature);
|
|
231
|
+
if (!isCryptoValid) return { allowed: false, reason: 'CRYPTO_FAIL', identityVerified: false };
|
|
232
|
+
|
|
233
|
+
// 3. Identity Verification (DNS Binding) with Cache
|
|
234
|
+
let identityVerified = false;
|
|
235
|
+
const claimedDomain = request.header.agent_id;
|
|
236
|
+
const pubKeyString = await exportPublicKey(requestPublicKey);
|
|
237
|
+
|
|
238
|
+
// Check Cache First
|
|
239
|
+
const cachedKey = await this.cache.get(claimedDomain);
|
|
240
|
+
|
|
241
|
+
if (cachedKey === pubKeyString) {
|
|
242
|
+
identityVerified = true;
|
|
243
|
+
} else if (this.isDomain(claimedDomain)) {
|
|
244
|
+
// Cache Miss: Perform DNS Fetch
|
|
245
|
+
identityVerified = await this.verifyDnsBinding(claimedDomain, pubKeyString);
|
|
246
|
+
if (identityVerified) {
|
|
247
|
+
await this.cache.set(claimedDomain, pubKeyString, this.CACHE_TTL_SECONDS);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (this.policy.requireIdentityBinding && !identityVerified) {
|
|
252
|
+
return { allowed: false, reason: 'IDENTITY_FAIL: DNS Binding could not be verified.', identityVerified: false };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 4. Policy Check: Purpose
|
|
256
|
+
if (request.header.purpose === AccessPurpose.CRAWL_TRAINING && !this.policy.allowTraining) {
|
|
257
|
+
return { allowed: false, reason: 'POLICY_DENIED: Training not allowed.', identityVerified };
|
|
258
|
+
}
|
|
259
|
+
if (request.header.purpose === AccessPurpose.RAG_RETRIEVAL && !this.policy.allowRAG) {
|
|
260
|
+
return { allowed: false, reason: 'POLICY_DENIED: RAG not allowed.', identityVerified };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 5. Policy Check: Economics
|
|
264
|
+
if (this.policy.requiresPayment) {
|
|
265
|
+
const isAdExempt = this.policy.allowAdSupportedAccess && request.header.context.ads_displayed;
|
|
266
|
+
if (!isAdExempt) {
|
|
267
|
+
return { allowed: false, reason: 'PAYMENT_REQUIRED: Content requires payment or ads.', identityVerified };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { allowed: true, reason: 'OK', identityVerified };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private async verifyDnsBinding(domain: string, requestKeySpki: string): Promise<boolean> {
|
|
275
|
+
try {
|
|
276
|
+
const url = `https://${domain}${WELL_KNOWN_AGENT_PATH}`;
|
|
277
|
+
// In production, we need a short timeout to prevent hanging
|
|
278
|
+
const controller = new AbortController();
|
|
279
|
+
const timeoutId = setTimeout(() => controller.abort(), 1500); // 1.5s max for DNS check
|
|
280
|
+
|
|
281
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
282
|
+
clearTimeout(timeoutId);
|
|
283
|
+
|
|
284
|
+
if (!response.ok) return false;
|
|
285
|
+
const manifest = await response.json() as AgentIdentityManifest;
|
|
286
|
+
return manifest.agent_id === domain && manifest.public_key === requestKeySpki;
|
|
287
|
+
} catch { return false; }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private isDomain(s: string): boolean {
|
|
291
|
+
return /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(s);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async generateResponseHeaders(origin: ContentOrigin): Promise<Record<string, string>> {
|
|
295
|
+
if (!this.keyPair) throw new Error("Publisher keys not initialized");
|
|
296
|
+
const payload = JSON.stringify({ origin, ts: Date.now() });
|
|
297
|
+
const signature = await signData(this.keyPair.privateKey, payload);
|
|
298
|
+
return {
|
|
299
|
+
[HEADERS.CONTENT_ORIGIN]: origin,
|
|
300
|
+
[HEADERS.PROVENANCE_SIG]: signature
|
|
301
|
+
};
|
|
302
|
+
}
|
|
107
303
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,98 +1,113 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 1: Protocol Definitions
|
|
3
|
-
* Shared types used by both Agent and Publisher.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export enum AccessPurpose {
|
|
7
|
-
CRAWL_TRAINING = 'CRAWL_TRAINING',
|
|
8
|
-
RAG_RETRIEVAL = 'RAG_RETRIEVAL',
|
|
9
|
-
SUMMARY = 'SUMMARY',
|
|
10
|
-
QUOTATION = 'QUOTATION',
|
|
11
|
-
EMBEDDING = 'EMBEDDING'
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export enum ContentOrigin {
|
|
15
|
-
HUMAN = 'HUMAN', // Created by humans. High training value.
|
|
16
|
-
SYNTHETIC = 'SYNTHETIC', // Created by AI. Risk of model collapse.
|
|
17
|
-
HYBRID = 'HYBRID' // Edited by humans, drafted by AI.
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export enum QualityFlag {
|
|
21
|
-
SEO_SPAM = 'SEO_SPAM',
|
|
22
|
-
INACCURATE = 'INACCURATE',
|
|
23
|
-
HATE_SPEECH = 'HATE_SPEECH',
|
|
24
|
-
HIGH_QUALITY = 'HIGH_QUALITY'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*/
|
|
31
|
-
export interface
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1: Protocol Definitions
|
|
3
|
+
* Shared types used by both Agent and Publisher.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export enum AccessPurpose {
|
|
7
|
+
CRAWL_TRAINING = 'CRAWL_TRAINING',
|
|
8
|
+
RAG_RETRIEVAL = 'RAG_RETRIEVAL',
|
|
9
|
+
SUMMARY = 'SUMMARY',
|
|
10
|
+
QUOTATION = 'QUOTATION',
|
|
11
|
+
EMBEDDING = 'EMBEDDING'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export enum ContentOrigin {
|
|
15
|
+
HUMAN = 'HUMAN', // Created by humans. High training value.
|
|
16
|
+
SYNTHETIC = 'SYNTHETIC', // Created by AI. Risk of model collapse.
|
|
17
|
+
HYBRID = 'HYBRID' // Edited by humans, drafted by AI.
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export enum QualityFlag {
|
|
21
|
+
SEO_SPAM = 'SEO_SPAM',
|
|
22
|
+
INACCURATE = 'INACCURATE',
|
|
23
|
+
HATE_SPEECH = 'HATE_SPEECH',
|
|
24
|
+
HIGH_QUALITY = 'HIGH_QUALITY'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* DNS Identity Manifest
|
|
29
|
+
* Hosted at: https://{agent_id}/.well-known/aamp-agent.json
|
|
30
|
+
*/
|
|
31
|
+
export interface AgentIdentityManifest {
|
|
32
|
+
agent_id: string; // e.g. "bot.openai.com"
|
|
33
|
+
public_key: string; // Base64 SPKI
|
|
34
|
+
contact_email?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* PRODUCTION INFRASTRUCTURE: Cache Interface
|
|
39
|
+
* Required for Serverless/Edge environments to prevent repeated DNS fetches.
|
|
40
|
+
*/
|
|
41
|
+
export interface IdentityCache {
|
|
42
|
+
get(key: string): Promise<string | null>; // Returns stored PublicKey
|
|
43
|
+
set(key: string, value: string, ttlSeconds: number): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Optional Monetization (The Settlement Layer)
|
|
48
|
+
*/
|
|
49
|
+
export interface MonetizationConfig {
|
|
50
|
+
method: 'BROKER' | 'CRYPTO' | 'PRIVATE_TREATY';
|
|
51
|
+
location: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Handling Non-AAMP Visitors
|
|
56
|
+
*
|
|
57
|
+
* PASSIVE: Allow everyone (Legacy web behavior).
|
|
58
|
+
* HYBRID: Allow verified Agents AND likely Humans (Browser Heuristics). Block bots.
|
|
59
|
+
* STRICT: Allow ONLY verified AAMP Agents. (API Mode).
|
|
60
|
+
*/
|
|
61
|
+
export type UnauthenticatedStrategy = 'PASSIVE' | 'HYBRID' | 'STRICT';
|
|
62
|
+
|
|
63
|
+
export interface AccessPolicy {
|
|
64
|
+
version: '1.1';
|
|
65
|
+
allowTraining: boolean;
|
|
66
|
+
allowRAG: boolean;
|
|
67
|
+
attributionRequired: boolean;
|
|
68
|
+
|
|
69
|
+
// Economic Signals
|
|
70
|
+
allowAdSupportedAccess: boolean;
|
|
71
|
+
requiresPayment: boolean;
|
|
72
|
+
paymentPointer?: string;
|
|
73
|
+
|
|
74
|
+
// Identity Strictness
|
|
75
|
+
requireIdentityBinding?: boolean;
|
|
76
|
+
|
|
77
|
+
// V1.1: Optional Settlement Info
|
|
78
|
+
monetization?: MonetizationConfig;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ProtocolHeader {
|
|
82
|
+
v: '1.1';
|
|
83
|
+
ts: string;
|
|
84
|
+
agent_id: string;
|
|
85
|
+
resource: string;
|
|
86
|
+
purpose: AccessPurpose;
|
|
87
|
+
context: {
|
|
88
|
+
ads_displayed: boolean;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface SignedAccessRequest {
|
|
93
|
+
header: ProtocolHeader;
|
|
94
|
+
signature: string;
|
|
95
|
+
publicKey?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface FeedbackSignal {
|
|
99
|
+
target_resource: string;
|
|
100
|
+
agent_id: string;
|
|
101
|
+
quality_score: number;
|
|
102
|
+
flags: QualityFlag[];
|
|
103
|
+
timestamp: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Result of the full evaluation pipeline
|
|
107
|
+
export interface EvaluationResult {
|
|
108
|
+
allowed: boolean;
|
|
109
|
+
status: 200 | 400 | 401 | 403;
|
|
110
|
+
reason: string;
|
|
111
|
+
visitorType: 'VERIFIED_AGENT' | 'LIKELY_HUMAN' | 'UNIDENTIFIED_BOT';
|
|
112
|
+
metadata?: any;
|
|
98
113
|
}
|