@peac/mappings-content-signals 0.12.1 → 0.12.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/README.md +2 -2
- package/dist/content-usage.d.ts +5 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/observation.d.ts +2 -2
- package/dist/resolve.d.ts +3 -3
- package/dist/robots.d.ts +1 -1
- package/dist/tdmrep.d.ts +1 -1
- package/dist/types.d.ts +5 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Parses signals from multiple sources and resolves them using priority precedence
|
|
|
9
9
|
| Source | Standard | Priority |
|
|
10
10
|
| ------------- | ------------------------------------------------------------------------------------- | -------- |
|
|
11
11
|
| tdmrep.json | EU Directive 2019/790, Art. 4 | Highest |
|
|
12
|
-
| Content-Usage | AIPREF attach draft (draft-ietf-aipref-attach-04), vocab (draft-ietf-aipref-vocab-
|
|
12
|
+
| Content-Usage | AIPREF attach draft (draft-ietf-aipref-attach-04), vocab (draft-ietf-aipref-vocab-05) | 2 |
|
|
13
13
|
| robots.txt | RFC 9309 | Lowest |
|
|
14
14
|
|
|
15
15
|
Content-Signal header support is reserved for a future version.
|
|
@@ -61,7 +61,7 @@ The Content-Usage header is parsed as an RFC 9651 Structured Fields Dictionary w
|
|
|
61
61
|
| `train-genai` | `ai-generative` | Leaf | `y`=allow, `n`=deny |
|
|
62
62
|
| `search` | `ai-search` | Leaf | `y`=allow, `n`=deny |
|
|
63
63
|
|
|
64
|
-
`bots` is a parent-only key used for hierarchy propagation (Section 5.2 of vocab-
|
|
64
|
+
`bots` is a parent-only key used for hierarchy propagation (Section 5.2 of vocab-05). It does not produce its own output entry; its preference propagates to child keys when they have no explicit value.
|
|
65
65
|
|
|
66
66
|
Values are SF Tokens (not Booleans). Bare keys (`train-ai` without `=y`/`=n`), String values (`"n"`), and Boolean values (`?1`/`?0`) all produce `unspecified`.
|
|
67
67
|
|
package/dist/content-usage.d.ts
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* Content-Usage Header Parser (AIPREF attach draft, draft-ietf-aipref-attach-04)
|
|
3
3
|
*
|
|
4
4
|
* Parses Content-Usage HTTP header values as Structured Fields Dictionaries
|
|
5
|
-
* per RFC 9651. Maps AIPREF vocabulary keys (draft-ietf-aipref-vocab-
|
|
5
|
+
* per RFC 9651. Maps AIPREF vocabulary keys (draft-ietf-aipref-vocab-05)
|
|
6
6
|
* to PEAC ContentPurpose values.
|
|
7
7
|
*
|
|
8
8
|
* Scope: HTTP header parsing only. Does NOT parse robots.txt directives
|
|
9
9
|
* or any other signal source. Receives pre-fetched header value (no network
|
|
10
|
-
* I/O
|
|
10
|
+
* I/O).
|
|
11
11
|
*
|
|
12
|
-
* AIPREF vocabulary keys (draft-ietf-aipref-vocab-
|
|
12
|
+
* AIPREF vocabulary keys (draft-ietf-aipref-vocab-05, Table 1):
|
|
13
13
|
* - bots: Automated processing (parent of train-ai and search)
|
|
14
14
|
* - train-ai: AI training (parent of train-genai)
|
|
15
15
|
* - train-genai: Generative AI training
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Values are SF Tokens: y = allow, n = disallow, anything else = unknown.
|
|
19
19
|
* Bare keys (Boolean true per SF rules) are NOT y and produce unknown.
|
|
20
20
|
*
|
|
21
|
-
* Hierarchy propagation (Section 5.2 of vocab-
|
|
21
|
+
* Hierarchy propagation (Section 5.2 of vocab-05):
|
|
22
22
|
* bots -> train-ai -> train-genai
|
|
23
23
|
* bots -> search
|
|
24
24
|
* When a specific key has no explicit preference, inherit from parent.
|
|
@@ -28,7 +28,7 @@ import type { ContentUsageParseResult } from './types.js';
|
|
|
28
28
|
* Parse Content-Usage header value and extract signal entries.
|
|
29
29
|
*
|
|
30
30
|
* Implements the AIPREF attach draft (draft-ietf-aipref-attach-04) with
|
|
31
|
-
* vocabulary from draft-ietf-aipref-vocab-
|
|
31
|
+
* vocabulary from draft-ietf-aipref-vocab-05. Header is parsed as an
|
|
32
32
|
* SF Dictionary (RFC 9651). Values must be Tokens: y = allow, n = disallow.
|
|
33
33
|
*
|
|
34
34
|
* Returns a structured result preserving all parse pipeline stages:
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/robots.ts","../src/tdmrep.ts","../src/content-usage.ts","../src/resolve.ts","../src/observation.ts"],"names":["key"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Content Signal Types (DD-136, DD-137)\n *\n * Types for content use policy signal observation.\n * Signals RECORD observations, never enforce (DD-95 rail neutrality).\n */\n\n// ---------------------------------------------------------------------------\n// Core types\n// ---------------------------------------------------------------------------\n\n/**\n * Signal source identifier (DD-137 precedence order).\n *\n * Note: Content-Signal header is reserved for a future version when a parser\n * is implemented. Only sources with shipped parsers are included here.\n */\nexport type SignalSource = 'tdmrep-json' | 'content-usage-header' | 'robots-txt';\n\n/** Three-state signal decision (DD-136) */\nexport type SignalDecision = 'allow' | 'deny' | 'unspecified';\n\n/**\n * Canonical purpose token for content signals.\n *\n * Subset of PEAC CanonicalPurpose relevant to content use policy signals.\n */\nexport type ContentPurpose = 'ai-training' | 'ai-inference' | 'ai-search' | 'ai-generative' | 'tdm';\n\n/** Single content signal entry from a specific source */\nexport interface ContentSignalEntry {\n /** Purpose this signal applies to */\n purpose: ContentPurpose;\n /** Three-state decision */\n decision: SignalDecision;\n /** Which source produced this signal */\n source: SignalSource;\n /** Raw value from the source (for debugging) */\n raw_value?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Structured Fields types (RFC 9651)\n// ---------------------------------------------------------------------------\n\n/** SF value type classification per RFC 9651 */\nexport type SfValueType = 'token' | 'string' | 'boolean' | 'inner-list' | 'byte-sequence';\n\n/** Single parsed Structured Fields Dictionary member (RFC 9651) */\nexport interface SfDictionaryMember {\n /** Member key (lowercase, as parsed) */\n key: string;\n /** Raw member string from the header (key=value with parameters) */\n raw: string;\n /** SF value type classification */\n valueType: SfValueType;\n /** Token value if valueType is 'token', null otherwise */\n tokenValue: string | null;\n}\n\n/** Full parse result from Content-Usage header parsing */\nexport interface ContentUsageParseResult {\n /** Original raw header value */\n raw: string;\n /** All parsed SF Dictionary members (known and unknown) */\n parsed: SfDictionaryMember[];\n /** Mapped signal entries for recognized AIPREF vocabulary keys */\n entries: ContentSignalEntry[];\n /** Unrecognized dictionary members (forward-compatible pass-through) */\n extensions: SfDictionaryMember[];\n}\n\n/** Aggregated content signal observation */\nexport interface ContentSignalObservation {\n /** When the signals were observed (ISO 8601) */\n observed_at: string;\n /** URI the signals apply to */\n target_uri: string;\n /** Resolved signals (one per purpose, highest-priority source wins) */\n signals: ContentSignalEntry[];\n /** Content digest for integrity binding */\n digest?: { alg: 'sha-256'; val: string };\n /** Which sources were checked */\n sources_checked: SignalSource[];\n}\n\n// ---------------------------------------------------------------------------\n// Input types (pre-fetched content)\n// ---------------------------------------------------------------------------\n\n/** Pre-fetched robots.txt content */\nexport interface RobotsTxtInput {\n /** Raw text content of robots.txt */\n content: string;\n}\n\n/** Pre-fetched tdmrep.json content */\nexport interface TdmrepInput {\n /** Raw JSON string of tdmrep.json */\n content: string;\n}\n\n/** Pre-fetched Content-Usage header value */\nexport interface ContentUsageInput {\n /** Raw header value */\n value: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Maximum input size for robots.txt (500 KB) */\nexport const MAX_ROBOTS_TXT_SIZE = 512000;\n\n/** Maximum input size for tdmrep.json (64 KB) */\nexport const MAX_TDMREP_SIZE = 65536;\n\n/** Maximum header value size (8 KB) */\nexport const MAX_HEADER_SIZE = 8192;\n\n/**\n * AI-relevant user-agent strings for robots.txt parsing.\n *\n * Maps user-agent tokens to the content purposes they represent.\n */\nexport const AI_USER_AGENTS: Record<string, ContentPurpose[]> = {\n gptbot: ['ai-training', 'ai-inference'],\n 'chatgpt-user': ['ai-inference'],\n 'anthropic-ai': ['ai-training', 'ai-inference'],\n claudebot: ['ai-training'],\n 'google-extended': ['ai-training', 'ai-generative'],\n ccbot: ['ai-training'],\n perplexitybot: ['ai-search'],\n 'cohere-ai': ['ai-training', 'ai-inference'],\n bytespider: ['ai-training'],\n};\n\n/**\n * Signal source precedence (DD-137).\n * Lower index = higher priority.\n *\n * Note: Content-Signal header is reserved for a future version.\n * When implemented, it will slot between tdmrep-json and content-usage-header.\n */\nexport const SOURCE_PRECEDENCE: readonly SignalSource[] = [\n 'tdmrep-json',\n 'content-usage-header',\n 'robots-txt',\n] as const;\n","/**\n * robots.txt Parser (RFC 9309)\n *\n * Parses robots.txt content and extracts AI-relevant signals.\n * Receives pre-fetched text content (no network I/O, DD-55).\n */\n\nimport type { ContentSignalEntry, ContentPurpose, SignalDecision } from './types.js';\nimport { AI_USER_AGENTS, MAX_ROBOTS_TXT_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface RobotGroup {\n userAgents: string[];\n disallow: string[];\n allow: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Parser\n// ---------------------------------------------------------------------------\n\n/**\n * Parse robots.txt content into structured groups.\n *\n * Follows RFC 9309 (Robots Exclusion Protocol, Sep 2022):\n * - Groups are defined by User-agent lines\n * - Disallow/Allow directives apply to the preceding user-agent group\n * - Case-insensitive directive matching\n * - Lines starting with # are comments\n */\nfunction parseRobotGroups(content: string): RobotGroup[] {\n const groups: RobotGroup[] = [];\n let currentGroup: RobotGroup | null = null;\n\n const lines = content.split(/\\r?\\n/);\n for (const rawLine of lines) {\n const line = rawLine.trim();\n\n // Skip empty lines and comments\n if (line === '' || line.startsWith('#')) {\n // Empty line between groups ends the current group\n if (line === '' && currentGroup && currentGroup.userAgents.length > 0) {\n groups.push(currentGroup);\n currentGroup = null;\n }\n continue;\n }\n\n // Parse directive: \"Field: Value\"\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) continue;\n\n const field = line.slice(0, colonIdx).trim().toLowerCase();\n const value = line\n .slice(colonIdx + 1)\n .split('#')[0]\n .trim(); // Strip inline comments\n\n if (field === 'user-agent') {\n if (!currentGroup) {\n currentGroup = { userAgents: [], disallow: [], allow: [] };\n }\n currentGroup.userAgents.push(value.toLowerCase());\n } else if (field === 'disallow') {\n if (!currentGroup) {\n currentGroup = { userAgents: ['*'], disallow: [], allow: [] };\n }\n currentGroup.disallow.push(value);\n } else if (field === 'allow') {\n if (!currentGroup) {\n currentGroup = { userAgents: ['*'], disallow: [], allow: [] };\n }\n currentGroup.allow.push(value);\n }\n // Sitemap and other directives are ignored for signal purposes\n }\n\n // Push final group if any\n if (currentGroup && currentGroup.userAgents.length > 0) {\n groups.push(currentGroup);\n }\n\n return groups;\n}\n\n/**\n * Determine signal decision for a group.\n *\n * Per RFC 9309:\n * - Disallow: / means deny all\n * - Empty Disallow: means allow all\n * - More specific paths are not evaluated (we only check root-level signals)\n */\nfunction groupDecision(group: RobotGroup): SignalDecision {\n // Filter out empty Disallow values (empty string = allow per RFC 9309)\n const effectiveDisallow = group.disallow.filter((d) => d.length > 0);\n\n // No effective Disallow lines means allow\n if (effectiveDisallow.length === 0) {\n return 'allow';\n }\n\n // Check for blanket deny (Disallow: /)\n if (effectiveDisallow.some((d) => d === '/')) {\n // Check if there's a more specific Allow (e.g., Allow: /public/)\n // For signal purposes, Disallow: / is a deny unless Allow: / overrides\n if (group.allow.some((a) => a === '/')) {\n return 'allow';\n }\n return 'deny';\n }\n\n // Disallow present but not root-level: partial deny, treated as unspecified\n // for signal purposes (we don't resolve path-specific rules)\n return 'unspecified';\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse robots.txt content and extract AI-relevant signals.\n *\n * @param content - Raw text content of robots.txt (pre-fetched)\n * @returns Array of ContentSignalEntry for matched AI user-agents\n */\nexport function parseRobotsTxt(content: string): ContentSignalEntry[] {\n if (content.length > MAX_ROBOTS_TXT_SIZE) {\n return [];\n }\n\n const groups = parseRobotGroups(content);\n const entries: ContentSignalEntry[] = [];\n const seenPurposes = new Set<ContentPurpose>();\n\n // First pass: check specific AI user-agents\n for (const group of groups) {\n for (const ua of group.userAgents) {\n const purposes = AI_USER_AGENTS[ua];\n if (!purposes) continue;\n\n const decision = groupDecision(group);\n for (const purpose of purposes) {\n if (seenPurposes.has(purpose)) continue;\n seenPurposes.add(purpose);\n entries.push({\n purpose,\n decision,\n source: 'robots-txt',\n raw_value: `User-agent: ${ua}`,\n });\n }\n }\n }\n\n // Second pass: check wildcard * for remaining purposes\n const wildcardGroup = groups.find((g) => g.userAgents.includes('*'));\n if (wildcardGroup) {\n const decision = groupDecision(wildcardGroup);\n const allPurposes: ContentPurpose[] = [\n 'ai-training',\n 'ai-inference',\n 'ai-search',\n 'ai-generative',\n ];\n for (const purpose of allPurposes) {\n if (seenPurposes.has(purpose)) continue;\n seenPurposes.add(purpose);\n entries.push({\n purpose,\n decision,\n source: 'robots-txt',\n raw_value: 'User-agent: *',\n });\n }\n }\n\n return entries;\n}\n","/**\n * tdmrep.json Parser (EU TDM Directive 2019/790, Art. 4)\n *\n * Parses tdmrep.json content for EU Text and Data Mining reservation signals.\n * Receives pre-fetched JSON content (no network I/O, DD-55).\n */\n\nimport type { ContentSignalEntry, SignalDecision } from './types.js';\nimport { MAX_TDMREP_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface TdmrepData {\n 'tdm-reservation'?: number;\n 'tdm-policy'?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse tdmrep.json content and extract TDM reservation signals.\n *\n * EU Directive 2019/790, Article 4:\n * - tdm-reservation: 0 = no reservation (allow TDM), 1 = reserved (deny TDM)\n * - tdm-policy: URL to machine-readable license terms\n *\n * @param content - Raw JSON string of tdmrep.json (pre-fetched)\n * @returns Array of ContentSignalEntry for TDM purposes\n */\nexport function parseTdmrep(content: string): ContentSignalEntry[] {\n if (content.length > MAX_TDMREP_SIZE) {\n return [];\n }\n\n let data: TdmrepData;\n try {\n data = JSON.parse(content) as TdmrepData;\n } catch {\n // Malformed JSON: return unspecified\n return [];\n }\n\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n return [];\n }\n\n const reservation = data['tdm-reservation'];\n\n let decision: SignalDecision;\n let rawValue: string;\n\n if (reservation === 1) {\n decision = 'deny';\n rawValue = 'tdm-reservation: 1';\n } else if (reservation === 0) {\n decision = 'allow';\n rawValue = 'tdm-reservation: 0';\n } else {\n // Absent or invalid value\n return [];\n }\n\n const entries: ContentSignalEntry[] = [\n {\n purpose: 'tdm',\n decision,\n source: 'tdmrep-json',\n raw_value: rawValue,\n },\n {\n purpose: 'ai-training',\n decision,\n source: 'tdmrep-json',\n raw_value: rawValue,\n },\n ];\n\n // If a tdm-policy URL is present, include it in the raw_value\n if (typeof data['tdm-policy'] === 'string') {\n for (const entry of entries) {\n entry.raw_value = `${entry.raw_value}, tdm-policy: ${data['tdm-policy']}`;\n }\n }\n\n return entries;\n}\n","/**\n * Content-Usage Header Parser (AIPREF attach draft, draft-ietf-aipref-attach-04)\n *\n * Parses Content-Usage HTTP header values as Structured Fields Dictionaries\n * per RFC 9651. Maps AIPREF vocabulary keys (draft-ietf-aipref-vocab-03)\n * to PEAC ContentPurpose values.\n *\n * Scope: HTTP header parsing only. Does NOT parse robots.txt directives\n * or any other signal source. Receives pre-fetched header value (no network\n * I/O, DD-55).\n *\n * AIPREF vocabulary keys (draft-ietf-aipref-vocab-03, Table 1):\n * - bots: Automated processing (parent of train-ai and search)\n * - train-ai: AI training (parent of train-genai)\n * - train-genai: Generative AI training\n * - search: Search applications\n *\n * Values are SF Tokens: y = allow, n = disallow, anything else = unknown.\n * Bare keys (Boolean true per SF rules) are NOT y and produce unknown.\n *\n * Hierarchy propagation (Section 5.2 of vocab-03):\n * bots -> train-ai -> train-genai\n * bots -> search\n * When a specific key has no explicit preference, inherit from parent.\n */\n\nimport type {\n ContentSignalEntry,\n ContentPurpose,\n SignalDecision,\n SfDictionaryMember,\n SfValueType,\n ContentUsageParseResult,\n} from './types.js';\nimport { MAX_HEADER_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// AIPREF vocabulary\n// ---------------------------------------------------------------------------\n\n/**\n * All recognized AIPREF vocabulary keys (draft-ietf-aipref-vocab-03, Table 1).\n * Used for parsing; includes parent-only keys that do not produce output entries.\n */\nconst AIPREF_KNOWN_KEYS = new Set(['bots', 'train-ai', 'train-genai', 'search']);\n\n/**\n * AIPREF leaf/child vocabulary keys mapped to PEAC ContentPurpose.\n * Per draft-ietf-aipref-vocab-03, Table 1.\n *\n * Note: `bots` is a parent-only key used solely for hierarchy propagation\n * (Section 5.2). It does not produce its own output entry.\n */\nconst AIPREF_KEY_MAP: Record<string, ContentPurpose> = {\n 'train-ai': 'ai-training',\n 'train-genai': 'ai-generative',\n search: 'ai-search',\n};\n\n/**\n * Hierarchy for AIPREF propagation (Section 5.2 of vocab-03).\n * Maps each key to its parent key. Root keys have no parent.\n */\nconst AIPREF_PARENT: Record<string, string | undefined> = {\n 'train-genai': 'train-ai',\n 'train-ai': 'bots',\n search: 'bots',\n bots: undefined,\n};\n\n// ---------------------------------------------------------------------------\n// Structured Fields Dictionary Parser (RFC 9651 subset)\n// ---------------------------------------------------------------------------\n\n/**\n * Classify SF value type from the raw value portion of a member.\n */\nfunction classifySfValue(valPart: string): { valueType: SfValueType; tokenValue: string | null } {\n if (valPart.startsWith('\"')) {\n return { valueType: 'string', tokenValue: null };\n }\n if (valPart.startsWith('?')) {\n return { valueType: 'boolean', tokenValue: null };\n }\n if (valPart.startsWith('(')) {\n return { valueType: 'inner-list', tokenValue: null };\n }\n if (valPart.startsWith(':')) {\n return { valueType: 'byte-sequence', tokenValue: null };\n }\n // Token value (alphanumeric + limited special chars per RFC 9651)\n const tokenValue = valPart === 'y' || valPart === 'n' ? valPart : null;\n return { valueType: 'token', tokenValue };\n}\n\n/**\n * Parse an SF Dictionary header value (RFC 9651, Section 4.2.2).\n *\n * This is a minimal parser sufficient for AIPREF Content-Usage headers.\n * Handles: Token values, String values, Boolean bare items,\n * parameters (stripped). Does NOT handle Inner Lists.\n */\nfunction parseSfDictionary(input: string): SfDictionaryMember[] {\n const members: SfDictionaryMember[] = [];\n const parts = input.split(',');\n\n for (const part of parts) {\n const trimmed = part.trim();\n if (!trimmed) continue;\n\n const raw = trimmed;\n const eqIdx = trimmed.indexOf('=');\n\n if (eqIdx === -1) {\n // Bare key: per SF rules, this is Boolean true, not Token y/n\n const key = stripParams(trimmed).trim().toLowerCase();\n if (key) {\n members.push({ key, raw, valueType: 'boolean', tokenValue: null });\n }\n continue;\n }\n\n const key = trimmed.slice(0, eqIdx).trim().toLowerCase();\n let valPart = trimmed.slice(eqIdx + 1).trim();\n\n // Strip parameters from value (;key=value portions)\n valPart = stripParams(valPart).trim();\n\n const { valueType, tokenValue } = classifySfValue(valPart);\n members.push({ key, raw, valueType, tokenValue });\n }\n\n return members;\n}\n\n/**\n * Strip SF parameters from a value or bare key.\n * Parameters start with ';' and are key=value or key pairs.\n */\nfunction stripParams(s: string): string {\n const semiIdx = s.indexOf(';');\n return semiIdx === -1 ? s : s.slice(0, semiIdx);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse Content-Usage header value and extract signal entries.\n *\n * Implements the AIPREF attach draft (draft-ietf-aipref-attach-04) with\n * vocabulary from draft-ietf-aipref-vocab-03. Header is parsed as an\n * SF Dictionary (RFC 9651). Values must be Tokens: y = allow, n = disallow.\n *\n * Returns a structured result preserving all parse pipeline stages:\n * - `raw`: original header string\n * - `parsed`: all SF Dictionary members (known and unknown)\n * - `entries`: mapped signal entries for recognized AIPREF keys\n * - `extensions`: unrecognized dictionary members (forward-compatible)\n *\n * Scope: HTTP Content-Usage header only. Does not handle robots.txt\n * or any other signal source.\n *\n * @param value - Raw Content-Usage header value (pre-fetched)\n * @returns Structured parse result with entries and extensions\n */\nexport function parseContentUsage(value: string): ContentUsageParseResult {\n const emptyResult: ContentUsageParseResult = {\n raw: value,\n parsed: [],\n entries: [],\n extensions: [],\n };\n\n if (value.length > MAX_HEADER_SIZE) {\n return emptyResult;\n }\n\n // Step 1: Parse SF Dictionary\n const sfMembers = parseSfDictionary(value);\n\n // Step 2: Build raw preference map for known AIPREF keys\n // Track all known AIPREF keys (including parent-only keys like bots) for inheritance.\n // Separate unknown keys into extensions (forward-compatible pass-through).\n const rawPrefs = new Map<string, { decision: SignalDecision; raw: string }>();\n const extensions: SfDictionaryMember[] = [];\n\n for (const member of sfMembers) {\n if (!AIPREF_KNOWN_KEYS.has(member.key)) {\n // Unknown key: store as extension (never drop)\n extensions.push(member);\n continue;\n }\n\n let decision: SignalDecision;\n if (member.tokenValue === 'y') {\n decision = 'allow';\n } else if (member.tokenValue === 'n') {\n decision = 'deny';\n } else {\n decision = 'unspecified'; // Non-Token, unknown Token, or bare key\n }\n\n // Last value wins (SF Dictionary duplicate key rule)\n rawPrefs.set(member.key, { decision, raw: member.raw });\n }\n\n // Step 3: Apply hierarchy propagation (Section 5.2 of vocab-03)\n // For each leaf AIPREF key, if preference is missing or unspecified,\n // inherit from parent.\n const resolvedPrefs = new Map<string, { decision: SignalDecision; raw: string }>();\n for (const aiprefKey of Object.keys(AIPREF_KEY_MAP)) {\n const explicit = rawPrefs.get(aiprefKey);\n if (explicit && explicit.decision !== 'unspecified') {\n resolvedPrefs.set(aiprefKey, explicit);\n continue;\n }\n\n // Walk up hierarchy for inheritance\n let current: string | undefined = AIPREF_PARENT[aiprefKey];\n let inherited: { decision: SignalDecision; raw: string } | undefined;\n while (current) {\n const parentPref = rawPrefs.get(current);\n if (parentPref && parentPref.decision !== 'unspecified') {\n inherited = {\n decision: parentPref.decision,\n raw: `${aiprefKey} (inherited from ${current}=${parentPref.raw})`,\n };\n break;\n }\n current = AIPREF_PARENT[current];\n }\n\n if (inherited) {\n resolvedPrefs.set(aiprefKey, inherited);\n }\n // If no inheritance found, key stays absent (unspecified)\n }\n\n // Step 4: Convert to ContentSignalEntry\n const entries: ContentSignalEntry[] = [];\n for (const [aiprefKey, pref] of resolvedPrefs) {\n const purpose = AIPREF_KEY_MAP[aiprefKey];\n if (!purpose) continue;\n\n entries.push({\n purpose,\n decision: pref.decision,\n source: 'content-usage-header',\n raw_value: pref.raw,\n });\n }\n\n return {\n raw: value,\n parsed: sfMembers,\n entries,\n extensions,\n };\n}\n","/**\n * Signal Priority Resolution (DD-137)\n *\n * Resolves signals from multiple sources using precedence rules.\n * tdmrep.json > Content-Usage > robots.txt\n */\n\nimport type { ContentSignalEntry, ContentPurpose, SignalDecision, SignalSource } from './types.js';\nimport { SOURCE_PRECEDENCE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve signals from multiple sources using DD-137 precedence.\n *\n * Per DD-137, when multiple sources provide signals for the same purpose,\n * the highest-priority source with a definitive signal (allow or deny) wins.\n * If all sources return unspecified, the resolved signal is unspecified.\n *\n * @param entries - All signal entries from all sources\n * @returns Resolved entries (one per purpose, highest-priority source wins)\n */\nexport function resolveSignals(entries: ContentSignalEntry[]): ContentSignalEntry[] {\n // Group entries by purpose\n const byPurpose = new Map<ContentPurpose, ContentSignalEntry[]>();\n for (const entry of entries) {\n const list = byPurpose.get(entry.purpose) || [];\n list.push(entry);\n byPurpose.set(entry.purpose, list);\n }\n\n const resolved: ContentSignalEntry[] = [];\n\n for (const purposeEntries of byPurpose.values()) {\n // Sort by source precedence (lower index = higher priority)\n const sorted = [...purposeEntries].sort((a, b) => {\n const aIdx = SOURCE_PRECEDENCE.indexOf(a.source);\n const bIdx = SOURCE_PRECEDENCE.indexOf(b.source);\n return aIdx - bIdx;\n });\n\n // Find first definitive signal (allow or deny)\n let winner: ContentSignalEntry | null = null;\n for (const entry of sorted) {\n if (entry.decision === 'allow' || entry.decision === 'deny') {\n winner = entry;\n break;\n }\n }\n\n if (winner) {\n resolved.push(winner);\n } else {\n // All unspecified: use highest-priority source's entry\n resolved.push(sorted[0]);\n }\n }\n\n return resolved;\n}\n\n/**\n * Get the precedence index for a signal source.\n *\n * Lower number = higher priority. Returns -1 for unknown sources.\n */\nexport function getSourcePrecedence(source: SignalSource): number {\n return SOURCE_PRECEDENCE.indexOf(source);\n}\n\n/**\n * Check if source A has higher priority than source B.\n */\nexport function hasHigherPriority(a: SignalSource, b: SignalSource): boolean {\n const aIdx = SOURCE_PRECEDENCE.indexOf(a);\n const bIdx = SOURCE_PRECEDENCE.indexOf(b);\n return aIdx < bIdx;\n}\n\n/**\n * Get the effective decision for a specific purpose from resolved signals.\n *\n * @returns The signal decision, or 'unspecified' if no signal for the purpose\n */\nexport function getDecisionForPurpose(\n resolved: ContentSignalEntry[],\n purpose: ContentPurpose\n): SignalDecision {\n const entry = resolved.find((e) => e.purpose === purpose);\n return entry?.decision ?? 'unspecified';\n}\n","/**\n * Content Signal Observation Factory\n *\n * Creates ContentSignalObservation objects from parsed signals.\n */\n\nimport type {\n ContentSignalObservation,\n ContentSignalEntry,\n SignalSource,\n ContentPurpose,\n} from './types.js';\nimport { parseRobotsTxt } from './robots.js';\nimport { parseTdmrep } from './tdmrep.js';\nimport { parseContentUsage } from './content-usage.js';\nimport { resolveSignals } from './resolve.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Input for creating an observation */\nexport interface CreateObservationInput {\n /** Target URI the signals apply to */\n target_uri: string;\n /** Pre-fetched robots.txt content (optional) */\n robots_txt?: string;\n /** Pre-fetched tdmrep.json content (optional) */\n tdmrep_json?: string;\n /** Pre-fetched Content-Usage header value (optional) */\n content_usage?: string;\n /** Content digest for integrity binding (optional) */\n digest?: { alg: 'sha-256'; val: string };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a ContentSignalObservation from pre-fetched signal sources.\n *\n * All inputs are pre-fetched content (no network I/O, DD-55).\n * Signals are parsed from each source and resolved using DD-137 precedence.\n *\n * @param input - Pre-fetched signal sources\n * @returns ContentSignalObservation with resolved signals\n */\nexport function createObservation(input: CreateObservationInput): ContentSignalObservation {\n const allEntries: ContentSignalEntry[] = [];\n const sourcesChecked: SignalSource[] = [];\n\n // Parse each available source\n if (input.tdmrep_json !== undefined) {\n sourcesChecked.push('tdmrep-json');\n const entries = parseTdmrep(input.tdmrep_json);\n allEntries.push(...entries);\n }\n\n if (input.content_usage !== undefined) {\n sourcesChecked.push('content-usage-header');\n const result = parseContentUsage(input.content_usage);\n allEntries.push(...result.entries);\n }\n\n if (input.robots_txt !== undefined) {\n sourcesChecked.push('robots-txt');\n const entries = parseRobotsTxt(input.robots_txt);\n allEntries.push(...entries);\n }\n\n // Resolve signals using DD-137 precedence\n const resolved = resolveSignals(allEntries);\n\n return {\n observed_at: new Date().toISOString(),\n target_uri: input.target_uri,\n signals: resolved,\n digest: input.digest,\n sources_checked: sourcesChecked,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/robots.ts","../src/tdmrep.ts","../src/content-usage.ts","../src/resolve.ts","../src/observation.ts"],"names":["key"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Content Signal Types\n *\n * Types for content use policy signal observation.\n * Signals RECORD observations, never enforce (rail neutrality).\n */\n\n// ---------------------------------------------------------------------------\n// Core types\n// ---------------------------------------------------------------------------\n\n/**\n * Signal source identifier (precedence order).\n *\n * Note: Content-Signal header is reserved for a future version when a parser\n * is implemented. Only sources with shipped parsers are included here.\n */\nexport type SignalSource = 'tdmrep-json' | 'content-usage-header' | 'robots-txt';\n\n/** Three-state signal decision */\nexport type SignalDecision = 'allow' | 'deny' | 'unspecified';\n\n/**\n * Canonical purpose token for content signals.\n *\n * Subset of PEAC CanonicalPurpose relevant to content use policy signals.\n */\nexport type ContentPurpose = 'ai-training' | 'ai-inference' | 'ai-search' | 'ai-generative' | 'tdm';\n\n/** Single content signal entry from a specific source */\nexport interface ContentSignalEntry {\n /** Purpose this signal applies to */\n purpose: ContentPurpose;\n /** Three-state decision */\n decision: SignalDecision;\n /** Which source produced this signal */\n source: SignalSource;\n /** Raw value from the source (for debugging) */\n raw_value?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Structured Fields types (RFC 9651)\n// ---------------------------------------------------------------------------\n\n/** SF value type classification per RFC 9651 */\nexport type SfValueType = 'token' | 'string' | 'boolean' | 'inner-list' | 'byte-sequence';\n\n/** Single parsed Structured Fields Dictionary member (RFC 9651) */\nexport interface SfDictionaryMember {\n /** Member key (lowercase, as parsed) */\n key: string;\n /** Raw member string from the header (key=value with parameters) */\n raw: string;\n /** SF value type classification */\n valueType: SfValueType;\n /** Token value if valueType is 'token', null otherwise */\n tokenValue: string | null;\n}\n\n/** Full parse result from Content-Usage header parsing */\nexport interface ContentUsageParseResult {\n /** Original raw header value */\n raw: string;\n /** All parsed SF Dictionary members (known and unknown) */\n parsed: SfDictionaryMember[];\n /** Mapped signal entries for recognized AIPREF vocabulary keys */\n entries: ContentSignalEntry[];\n /** Unrecognized dictionary members (forward-compatible pass-through) */\n extensions: SfDictionaryMember[];\n}\n\n/** Aggregated content signal observation */\nexport interface ContentSignalObservation {\n /** When the signals were observed (ISO 8601) */\n observed_at: string;\n /** URI the signals apply to */\n target_uri: string;\n /** Resolved signals (one per purpose, highest-priority source wins) */\n signals: ContentSignalEntry[];\n /** Content digest for integrity binding */\n digest?: { alg: 'sha-256'; val: string };\n /** Which sources were checked */\n sources_checked: SignalSource[];\n}\n\n// ---------------------------------------------------------------------------\n// Input types (pre-fetched content)\n// ---------------------------------------------------------------------------\n\n/** Pre-fetched robots.txt content */\nexport interface RobotsTxtInput {\n /** Raw text content of robots.txt */\n content: string;\n}\n\n/** Pre-fetched tdmrep.json content */\nexport interface TdmrepInput {\n /** Raw JSON string of tdmrep.json */\n content: string;\n}\n\n/** Pre-fetched Content-Usage header value */\nexport interface ContentUsageInput {\n /** Raw header value */\n value: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Maximum input size for robots.txt (500 KB) */\nexport const MAX_ROBOTS_TXT_SIZE = 512000;\n\n/** Maximum input size for tdmrep.json (64 KB) */\nexport const MAX_TDMREP_SIZE = 65536;\n\n/** Maximum header value size (8 KB) */\nexport const MAX_HEADER_SIZE = 8192;\n\n/**\n * AI-relevant user-agent strings for robots.txt parsing.\n *\n * Maps user-agent tokens to the content purposes they represent.\n */\nexport const AI_USER_AGENTS: Record<string, ContentPurpose[]> = {\n gptbot: ['ai-training', 'ai-inference'],\n 'chatgpt-user': ['ai-inference'],\n 'anthropic-ai': ['ai-training', 'ai-inference'],\n claudebot: ['ai-training'],\n 'google-extended': ['ai-training', 'ai-generative'],\n ccbot: ['ai-training'],\n perplexitybot: ['ai-search'],\n 'cohere-ai': ['ai-training', 'ai-inference'],\n bytespider: ['ai-training'],\n};\n\n/**\n * Signal source precedence.\n * Lower index = higher priority.\n *\n * Note: Content-Signal header is reserved for a future version.\n * When implemented, it will slot between tdmrep-json and content-usage-header.\n */\nexport const SOURCE_PRECEDENCE: readonly SignalSource[] = [\n 'tdmrep-json',\n 'content-usage-header',\n 'robots-txt',\n] as const;\n","/**\n * robots.txt Parser (RFC 9309)\n *\n * Parses robots.txt content and extracts AI-relevant signals.\n * Receives pre-fetched text content (no network I/O).\n */\n\nimport type { ContentSignalEntry, ContentPurpose, SignalDecision } from './types.js';\nimport { AI_USER_AGENTS, MAX_ROBOTS_TXT_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface RobotGroup {\n userAgents: string[];\n disallow: string[];\n allow: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Parser\n// ---------------------------------------------------------------------------\n\n/**\n * Parse robots.txt content into structured groups.\n *\n * Follows RFC 9309 (Robots Exclusion Protocol, Sep 2022):\n * - Groups are defined by User-agent lines\n * - Disallow/Allow directives apply to the preceding user-agent group\n * - Case-insensitive directive matching\n * - Lines starting with # are comments\n */\nfunction parseRobotGroups(content: string): RobotGroup[] {\n const groups: RobotGroup[] = [];\n let currentGroup: RobotGroup | null = null;\n\n const lines = content.split(/\\r?\\n/);\n for (const rawLine of lines) {\n const line = rawLine.trim();\n\n // Skip empty lines and comments\n if (line === '' || line.startsWith('#')) {\n // Empty line between groups ends the current group\n if (line === '' && currentGroup && currentGroup.userAgents.length > 0) {\n groups.push(currentGroup);\n currentGroup = null;\n }\n continue;\n }\n\n // Parse directive: \"Field: Value\"\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) continue;\n\n const field = line.slice(0, colonIdx).trim().toLowerCase();\n const value = line\n .slice(colonIdx + 1)\n .split('#')[0]\n .trim(); // Strip inline comments\n\n if (field === 'user-agent') {\n if (!currentGroup) {\n currentGroup = { userAgents: [], disallow: [], allow: [] };\n }\n currentGroup.userAgents.push(value.toLowerCase());\n } else if (field === 'disallow') {\n if (!currentGroup) {\n currentGroup = { userAgents: ['*'], disallow: [], allow: [] };\n }\n currentGroup.disallow.push(value);\n } else if (field === 'allow') {\n if (!currentGroup) {\n currentGroup = { userAgents: ['*'], disallow: [], allow: [] };\n }\n currentGroup.allow.push(value);\n }\n // Sitemap and other directives are ignored for signal purposes\n }\n\n // Push final group if any\n if (currentGroup && currentGroup.userAgents.length > 0) {\n groups.push(currentGroup);\n }\n\n return groups;\n}\n\n/**\n * Determine signal decision for a group.\n *\n * Per RFC 9309:\n * - Disallow: / means deny all\n * - Empty Disallow: means allow all\n * - More specific paths are not evaluated (we only check root-level signals)\n */\nfunction groupDecision(group: RobotGroup): SignalDecision {\n // Filter out empty Disallow values (empty string = allow per RFC 9309)\n const effectiveDisallow = group.disallow.filter((d) => d.length > 0);\n\n // No effective Disallow lines means allow\n if (effectiveDisallow.length === 0) {\n return 'allow';\n }\n\n // Check for blanket deny (Disallow: /)\n if (effectiveDisallow.some((d) => d === '/')) {\n // Check if there's a more specific Allow (e.g., Allow: /public/)\n // For signal purposes, Disallow: / is a deny unless Allow: / overrides\n if (group.allow.some((a) => a === '/')) {\n return 'allow';\n }\n return 'deny';\n }\n\n // Disallow present but not root-level: partial deny, treated as unspecified\n // for signal purposes (we don't resolve path-specific rules)\n return 'unspecified';\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse robots.txt content and extract AI-relevant signals.\n *\n * @param content - Raw text content of robots.txt (pre-fetched)\n * @returns Array of ContentSignalEntry for matched AI user-agents\n */\nexport function parseRobotsTxt(content: string): ContentSignalEntry[] {\n if (content.length > MAX_ROBOTS_TXT_SIZE) {\n return [];\n }\n\n const groups = parseRobotGroups(content);\n const entries: ContentSignalEntry[] = [];\n const seenPurposes = new Set<ContentPurpose>();\n\n // First pass: check specific AI user-agents\n for (const group of groups) {\n for (const ua of group.userAgents) {\n const purposes = AI_USER_AGENTS[ua];\n if (!purposes) continue;\n\n const decision = groupDecision(group);\n for (const purpose of purposes) {\n if (seenPurposes.has(purpose)) continue;\n seenPurposes.add(purpose);\n entries.push({\n purpose,\n decision,\n source: 'robots-txt',\n raw_value: `User-agent: ${ua}`,\n });\n }\n }\n }\n\n // Second pass: check wildcard * for remaining purposes\n const wildcardGroup = groups.find((g) => g.userAgents.includes('*'));\n if (wildcardGroup) {\n const decision = groupDecision(wildcardGroup);\n const allPurposes: ContentPurpose[] = [\n 'ai-training',\n 'ai-inference',\n 'ai-search',\n 'ai-generative',\n ];\n for (const purpose of allPurposes) {\n if (seenPurposes.has(purpose)) continue;\n seenPurposes.add(purpose);\n entries.push({\n purpose,\n decision,\n source: 'robots-txt',\n raw_value: 'User-agent: *',\n });\n }\n }\n\n return entries;\n}\n","/**\n * tdmrep.json Parser (EU TDM Directive 2019/790, Art. 4)\n *\n * Parses tdmrep.json content for EU Text and Data Mining reservation signals.\n * Receives pre-fetched JSON content (no network I/O).\n */\n\nimport type { ContentSignalEntry, SignalDecision } from './types.js';\nimport { MAX_TDMREP_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface TdmrepData {\n 'tdm-reservation'?: number;\n 'tdm-policy'?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse tdmrep.json content and extract TDM reservation signals.\n *\n * EU Directive 2019/790, Article 4:\n * - tdm-reservation: 0 = no reservation (allow TDM), 1 = reserved (deny TDM)\n * - tdm-policy: URL to machine-readable license terms\n *\n * @param content - Raw JSON string of tdmrep.json (pre-fetched)\n * @returns Array of ContentSignalEntry for TDM purposes\n */\nexport function parseTdmrep(content: string): ContentSignalEntry[] {\n if (content.length > MAX_TDMREP_SIZE) {\n return [];\n }\n\n let data: TdmrepData;\n try {\n data = JSON.parse(content) as TdmrepData;\n } catch {\n // Malformed JSON: return unspecified\n return [];\n }\n\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n return [];\n }\n\n const reservation = data['tdm-reservation'];\n\n let decision: SignalDecision;\n let rawValue: string;\n\n if (reservation === 1) {\n decision = 'deny';\n rawValue = 'tdm-reservation: 1';\n } else if (reservation === 0) {\n decision = 'allow';\n rawValue = 'tdm-reservation: 0';\n } else {\n // Absent or invalid value\n return [];\n }\n\n const entries: ContentSignalEntry[] = [\n {\n purpose: 'tdm',\n decision,\n source: 'tdmrep-json',\n raw_value: rawValue,\n },\n {\n purpose: 'ai-training',\n decision,\n source: 'tdmrep-json',\n raw_value: rawValue,\n },\n ];\n\n // If a tdm-policy URL is present, include it in the raw_value\n if (typeof data['tdm-policy'] === 'string') {\n for (const entry of entries) {\n entry.raw_value = `${entry.raw_value}, tdm-policy: ${data['tdm-policy']}`;\n }\n }\n\n return entries;\n}\n","/**\n * Content-Usage Header Parser (AIPREF attach draft, draft-ietf-aipref-attach-04)\n *\n * Parses Content-Usage HTTP header values as Structured Fields Dictionaries\n * per RFC 9651. Maps AIPREF vocabulary keys (draft-ietf-aipref-vocab-05)\n * to PEAC ContentPurpose values.\n *\n * Scope: HTTP header parsing only. Does NOT parse robots.txt directives\n * or any other signal source. Receives pre-fetched header value (no network\n * I/O).\n *\n * AIPREF vocabulary keys (draft-ietf-aipref-vocab-05, Table 1):\n * - bots: Automated processing (parent of train-ai and search)\n * - train-ai: AI training (parent of train-genai)\n * - train-genai: Generative AI training\n * - search: Search applications\n *\n * Values are SF Tokens: y = allow, n = disallow, anything else = unknown.\n * Bare keys (Boolean true per SF rules) are NOT y and produce unknown.\n *\n * Hierarchy propagation (Section 5.2 of vocab-05):\n * bots -> train-ai -> train-genai\n * bots -> search\n * When a specific key has no explicit preference, inherit from parent.\n */\n\nimport type {\n ContentSignalEntry,\n ContentPurpose,\n SignalDecision,\n SfDictionaryMember,\n SfValueType,\n ContentUsageParseResult,\n} from './types.js';\nimport { MAX_HEADER_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// AIPREF vocabulary\n// ---------------------------------------------------------------------------\n\n/**\n * All recognized AIPREF vocabulary keys (draft-ietf-aipref-vocab-05, Table 1).\n * Used for parsing; includes parent-only keys that do not produce output entries.\n */\nconst AIPREF_KNOWN_KEYS = new Set(['bots', 'train-ai', 'train-genai', 'search']);\n\n/**\n * AIPREF leaf/child vocabulary keys mapped to PEAC ContentPurpose.\n * Per draft-ietf-aipref-vocab-05, Table 1.\n *\n * Note: `bots` is a parent-only key used solely for hierarchy propagation\n * (Section 5.2). It does not produce its own output entry.\n */\nconst AIPREF_KEY_MAP: Record<string, ContentPurpose> = {\n 'train-ai': 'ai-training',\n 'train-genai': 'ai-generative',\n search: 'ai-search',\n};\n\n/**\n * Hierarchy for AIPREF propagation (Section 5.2 of vocab-05).\n * Maps each key to its parent key. Root keys have no parent.\n */\nconst AIPREF_PARENT: Record<string, string | undefined> = {\n 'train-genai': 'train-ai',\n 'train-ai': 'bots',\n search: 'bots',\n bots: undefined,\n};\n\n// ---------------------------------------------------------------------------\n// Structured Fields Dictionary Parser (RFC 9651 subset)\n// ---------------------------------------------------------------------------\n\n/**\n * Classify SF value type from the raw value portion of a member.\n */\nfunction classifySfValue(valPart: string): { valueType: SfValueType; tokenValue: string | null } {\n if (valPart.startsWith('\"')) {\n return { valueType: 'string', tokenValue: null };\n }\n if (valPart.startsWith('?')) {\n return { valueType: 'boolean', tokenValue: null };\n }\n if (valPart.startsWith('(')) {\n return { valueType: 'inner-list', tokenValue: null };\n }\n if (valPart.startsWith(':')) {\n return { valueType: 'byte-sequence', tokenValue: null };\n }\n // Token value (alphanumeric + limited special chars per RFC 9651)\n const tokenValue = valPart === 'y' || valPart === 'n' ? valPart : null;\n return { valueType: 'token', tokenValue };\n}\n\n/**\n * Parse an SF Dictionary header value (RFC 9651, Section 4.2.2).\n *\n * This is a minimal parser sufficient for AIPREF Content-Usage headers.\n * Handles: Token values, String values, Boolean bare items,\n * parameters (stripped). Does NOT handle Inner Lists.\n */\nfunction parseSfDictionary(input: string): SfDictionaryMember[] {\n const members: SfDictionaryMember[] = [];\n const parts = input.split(',');\n\n for (const part of parts) {\n const trimmed = part.trim();\n if (!trimmed) continue;\n\n const raw = trimmed;\n const eqIdx = trimmed.indexOf('=');\n\n if (eqIdx === -1) {\n // Bare key: per SF rules, this is Boolean true, not Token y/n\n const key = stripParams(trimmed).trim().toLowerCase();\n if (key) {\n members.push({ key, raw, valueType: 'boolean', tokenValue: null });\n }\n continue;\n }\n\n const key = trimmed.slice(0, eqIdx).trim().toLowerCase();\n let valPart = trimmed.slice(eqIdx + 1).trim();\n\n // Strip parameters from value (;key=value portions)\n valPart = stripParams(valPart).trim();\n\n const { valueType, tokenValue } = classifySfValue(valPart);\n members.push({ key, raw, valueType, tokenValue });\n }\n\n return members;\n}\n\n/**\n * Strip SF parameters from a value or bare key.\n * Parameters start with ';' and are key=value or key pairs.\n */\nfunction stripParams(s: string): string {\n const semiIdx = s.indexOf(';');\n return semiIdx === -1 ? s : s.slice(0, semiIdx);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse Content-Usage header value and extract signal entries.\n *\n * Implements the AIPREF attach draft (draft-ietf-aipref-attach-04) with\n * vocabulary from draft-ietf-aipref-vocab-05. Header is parsed as an\n * SF Dictionary (RFC 9651). Values must be Tokens: y = allow, n = disallow.\n *\n * Returns a structured result preserving all parse pipeline stages:\n * - `raw`: original header string\n * - `parsed`: all SF Dictionary members (known and unknown)\n * - `entries`: mapped signal entries for recognized AIPREF keys\n * - `extensions`: unrecognized dictionary members (forward-compatible)\n *\n * Scope: HTTP Content-Usage header only. Does not handle robots.txt\n * or any other signal source.\n *\n * @param value - Raw Content-Usage header value (pre-fetched)\n * @returns Structured parse result with entries and extensions\n */\nexport function parseContentUsage(value: string): ContentUsageParseResult {\n const emptyResult: ContentUsageParseResult = {\n raw: value,\n parsed: [],\n entries: [],\n extensions: [],\n };\n\n if (value.length > MAX_HEADER_SIZE) {\n return emptyResult;\n }\n\n // Step 1: Parse SF Dictionary\n const sfMembers = parseSfDictionary(value);\n\n // Step 2: Build raw preference map for known AIPREF keys\n // Track all known AIPREF keys (including parent-only keys like bots) for inheritance.\n // Separate unknown keys into extensions (forward-compatible pass-through).\n const rawPrefs = new Map<string, { decision: SignalDecision; raw: string }>();\n const extensions: SfDictionaryMember[] = [];\n\n for (const member of sfMembers) {\n if (!AIPREF_KNOWN_KEYS.has(member.key)) {\n // Unknown key: store as extension (never drop)\n extensions.push(member);\n continue;\n }\n\n let decision: SignalDecision;\n if (member.tokenValue === 'y') {\n decision = 'allow';\n } else if (member.tokenValue === 'n') {\n decision = 'deny';\n } else {\n decision = 'unspecified'; // Non-Token, unknown Token, or bare key\n }\n\n // Last value wins (SF Dictionary duplicate key rule)\n rawPrefs.set(member.key, { decision, raw: member.raw });\n }\n\n // Step 3: Apply hierarchy propagation (Section 5.2 of vocab-05)\n // For each leaf AIPREF key, if preference is missing or unspecified,\n // inherit from parent.\n const resolvedPrefs = new Map<string, { decision: SignalDecision; raw: string }>();\n for (const aiprefKey of Object.keys(AIPREF_KEY_MAP)) {\n const explicit = rawPrefs.get(aiprefKey);\n if (explicit && explicit.decision !== 'unspecified') {\n resolvedPrefs.set(aiprefKey, explicit);\n continue;\n }\n\n // Walk up hierarchy for inheritance\n let current: string | undefined = AIPREF_PARENT[aiprefKey];\n let inherited: { decision: SignalDecision; raw: string } | undefined;\n while (current) {\n const parentPref = rawPrefs.get(current);\n if (parentPref && parentPref.decision !== 'unspecified') {\n inherited = {\n decision: parentPref.decision,\n raw: `${aiprefKey} (inherited from ${current}=${parentPref.raw})`,\n };\n break;\n }\n current = AIPREF_PARENT[current];\n }\n\n if (inherited) {\n resolvedPrefs.set(aiprefKey, inherited);\n }\n // If no inheritance found, key stays absent (unspecified)\n }\n\n // Step 4: Convert to ContentSignalEntry\n const entries: ContentSignalEntry[] = [];\n for (const [aiprefKey, pref] of resolvedPrefs) {\n const purpose = AIPREF_KEY_MAP[aiprefKey];\n if (!purpose) continue;\n\n entries.push({\n purpose,\n decision: pref.decision,\n source: 'content-usage-header',\n raw_value: pref.raw,\n });\n }\n\n return {\n raw: value,\n parsed: sfMembers,\n entries,\n extensions,\n };\n}\n","/**\n * Signal Priority Resolution\n *\n * Resolves signals from multiple sources using precedence rules.\n * tdmrep.json > Content-Usage > robots.txt\n */\n\nimport type { ContentSignalEntry, ContentPurpose, SignalDecision, SignalSource } from './types.js';\nimport { SOURCE_PRECEDENCE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve signals from multiple sources using precedence.\n *\n * Per when multiple sources provide signals for the same purpose,\n * the highest-priority source with a definitive signal (allow or deny) wins.\n * If all sources return unspecified, the resolved signal is unspecified.\n *\n * @param entries - All signal entries from all sources\n * @returns Resolved entries (one per purpose, highest-priority source wins)\n */\nexport function resolveSignals(entries: ContentSignalEntry[]): ContentSignalEntry[] {\n // Group entries by purpose\n const byPurpose = new Map<ContentPurpose, ContentSignalEntry[]>();\n for (const entry of entries) {\n const list = byPurpose.get(entry.purpose) || [];\n list.push(entry);\n byPurpose.set(entry.purpose, list);\n }\n\n const resolved: ContentSignalEntry[] = [];\n\n for (const purposeEntries of byPurpose.values()) {\n // Sort by source precedence (lower index = higher priority)\n const sorted = [...purposeEntries].sort((a, b) => {\n const aIdx = SOURCE_PRECEDENCE.indexOf(a.source);\n const bIdx = SOURCE_PRECEDENCE.indexOf(b.source);\n return aIdx - bIdx;\n });\n\n // Find first definitive signal (allow or deny)\n let winner: ContentSignalEntry | null = null;\n for (const entry of sorted) {\n if (entry.decision === 'allow' || entry.decision === 'deny') {\n winner = entry;\n break;\n }\n }\n\n if (winner) {\n resolved.push(winner);\n } else {\n // All unspecified: use highest-priority source's entry\n resolved.push(sorted[0]);\n }\n }\n\n return resolved;\n}\n\n/**\n * Get the precedence index for a signal source.\n *\n * Lower number = higher priority. Returns -1 for unknown sources.\n */\nexport function getSourcePrecedence(source: SignalSource): number {\n return SOURCE_PRECEDENCE.indexOf(source);\n}\n\n/**\n * Check if source A has higher priority than source B.\n */\nexport function hasHigherPriority(a: SignalSource, b: SignalSource): boolean {\n const aIdx = SOURCE_PRECEDENCE.indexOf(a);\n const bIdx = SOURCE_PRECEDENCE.indexOf(b);\n return aIdx < bIdx;\n}\n\n/**\n * Get the effective decision for a specific purpose from resolved signals.\n *\n * @returns The signal decision, or 'unspecified' if no signal for the purpose\n */\nexport function getDecisionForPurpose(\n resolved: ContentSignalEntry[],\n purpose: ContentPurpose\n): SignalDecision {\n const entry = resolved.find((e) => e.purpose === purpose);\n return entry?.decision ?? 'unspecified';\n}\n","/**\n * Content Signal Observation Factory\n *\n * Creates ContentSignalObservation objects from parsed signals.\n */\n\nimport type {\n ContentSignalObservation,\n ContentSignalEntry,\n SignalSource,\n ContentPurpose,\n} from './types.js';\nimport { parseRobotsTxt } from './robots.js';\nimport { parseTdmrep } from './tdmrep.js';\nimport { parseContentUsage } from './content-usage.js';\nimport { resolveSignals } from './resolve.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Input for creating an observation */\nexport interface CreateObservationInput {\n /** Target URI the signals apply to */\n target_uri: string;\n /** Pre-fetched robots.txt content (optional) */\n robots_txt?: string;\n /** Pre-fetched tdmrep.json content (optional) */\n tdmrep_json?: string;\n /** Pre-fetched Content-Usage header value (optional) */\n content_usage?: string;\n /** Content digest for integrity binding (optional) */\n digest?: { alg: 'sha-256'; val: string };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a ContentSignalObservation from pre-fetched signal sources.\n *\n * All inputs are pre-fetched content (no network I/O).\n * Signals are parsed from each source and resolved using precedence.\n *\n * @param input - Pre-fetched signal sources\n * @returns ContentSignalObservation with resolved signals\n */\nexport function createObservation(input: CreateObservationInput): ContentSignalObservation {\n const allEntries: ContentSignalEntry[] = [];\n const sourcesChecked: SignalSource[] = [];\n\n // Parse each available source\n if (input.tdmrep_json !== undefined) {\n sourcesChecked.push('tdmrep-json');\n const entries = parseTdmrep(input.tdmrep_json);\n allEntries.push(...entries);\n }\n\n if (input.content_usage !== undefined) {\n sourcesChecked.push('content-usage-header');\n const result = parseContentUsage(input.content_usage);\n allEntries.push(...result.entries);\n }\n\n if (input.robots_txt !== undefined) {\n sourcesChecked.push('robots-txt');\n const entries = parseRobotsTxt(input.robots_txt);\n allEntries.push(...entries);\n }\n\n // Resolve signals using precedence\n const resolved = resolveSignals(allEntries);\n\n return {\n observed_at: new Date().toISOString(),\n target_uri: input.target_uri,\n signals: resolved,\n digest: input.digest,\n sources_checked: sourcesChecked,\n };\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @peac/mappings-content-signals
|
|
3
3
|
*
|
|
4
|
-
* Content use policy signal parsing for PEAC Protocol
|
|
4
|
+
* Content use policy signal parsing for PEAC Protocol.
|
|
5
5
|
*
|
|
6
6
|
* Parses content use signals from multiple sources and resolves them
|
|
7
7
|
* using priority precedence. Signals RECORD observations, never enforce.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - tdmrep.json (EU TDM Directive 2019/790, Art. 4)
|
|
12
12
|
* - Content-Usage header (AIPREF draft, RFC 9651)
|
|
13
13
|
*
|
|
14
|
-
* All parsers receive pre-fetched content (no network I/O
|
|
14
|
+
* All parsers receive pre-fetched content (no network I/O).
|
|
15
15
|
*/
|
|
16
16
|
export type { SignalSource, SignalDecision, ContentPurpose, ContentSignalEntry, ContentSignalObservation, RobotsTxtInput, TdmrepInput, ContentUsageInput, SfValueType, SfDictionaryMember, ContentUsageParseResult, } from './types.js';
|
|
17
17
|
export { AI_USER_AGENTS, SOURCE_PRECEDENCE, MAX_ROBOTS_TXT_SIZE, MAX_TDMREP_SIZE, MAX_HEADER_SIZE, } from './types.js';
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/robots.ts","../src/tdmrep.ts","../src/content-usage.ts","../src/resolve.ts","../src/observation.ts"],"names":["key"],"mappings":";AAiHO,IAAM,mBAAA,GAAsB;AAG5B,IAAM,eAAA,GAAkB;AAGxB,IAAM,eAAA,GAAkB;AAOxB,IAAM,cAAA,GAAmD;AAAA,EAC9D,MAAA,EAAQ,CAAC,aAAA,EAAe,cAAc,CAAA;AAAA,EACtC,cAAA,EAAgB,CAAC,cAAc,CAAA;AAAA,EAC/B,cAAA,EAAgB,CAAC,aAAA,EAAe,cAAc,CAAA;AAAA,EAC9C,SAAA,EAAW,CAAC,aAAa,CAAA;AAAA,EACzB,iBAAA,EAAmB,CAAC,aAAA,EAAe,eAAe,CAAA;AAAA,EAClD,KAAA,EAAO,CAAC,aAAa,CAAA;AAAA,EACrB,aAAA,EAAe,CAAC,WAAW,CAAA;AAAA,EAC3B,WAAA,EAAa,CAAC,aAAA,EAAe,cAAc,CAAA;AAAA,EAC3C,UAAA,EAAY,CAAC,aAAa;AAC5B;AASO,IAAM,iBAAA,GAA6C;AAAA,EACxD,aAAA;AAAA,EACA,sBAAA;AAAA,EACA;AACF;;;ACpHA,SAAS,iBAAiB,OAAA,EAA+B;AACvD,EAAA,MAAM,SAAuB,EAAC;AAC9B,EAAA,IAAI,YAAA,GAAkC,IAAA;AAEtC,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA;AACnC,EAAA,KAAA,MAAW,WAAW,KAAA,EAAO;AAC3B,IAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,EAAK;AAG1B,IAAA,IAAI,IAAA,KAAS,EAAA,IAAM,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AAEvC,MAAA,IAAI,SAAS,EAAA,IAAM,YAAA,IAAgB,YAAA,CAAa,UAAA,CAAW,SAAS,CAAA,EAAG;AACrE,QAAA,MAAA,CAAO,KAAK,YAAY,CAAA;AACxB,QAAA,YAAA,GAAe,IAAA;AAAA,MACjB;AACA,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AACjC,IAAA,IAAI,aAAa,EAAA,EAAI;AAErB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA,CAAE,IAAA,GAAO,WAAA,EAAY;AACzD,IAAA,MAAM,KAAA,GAAQ,IAAA,CACX,KAAA,CAAM,QAAA,GAAW,CAAC,CAAA,CAClB,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,CACZ,IAAA,EAAK;AAER,IAAA,IAAI,UAAU,YAAA,EAAc;AAC1B,MAAA,IAAI,CAAC,YAAA,EAAc;AACjB,QAAA,YAAA,GAAe,EAAE,YAAY,EAAC,EAAG,UAAU,EAAC,EAAG,KAAA,EAAO,EAAC,EAAE;AAAA,MAC3D;AACA,MAAA,YAAA,CAAa,UAAA,CAAW,IAAA,CAAK,KAAA,CAAM,WAAA,EAAa,CAAA;AAAA,IAClD,CAAA,MAAA,IAAW,UAAU,UAAA,EAAY;AAC/B,MAAA,IAAI,CAAC,YAAA,EAAc;AACjB,QAAA,YAAA,GAAe,EAAE,UAAA,EAAY,CAAC,GAAG,CAAA,EAAG,UAAU,EAAC,EAAG,KAAA,EAAO,EAAC,EAAE;AAAA,MAC9D;AACA,MAAA,YAAA,CAAa,QAAA,CAAS,KAAK,KAAK,CAAA;AAAA,IAClC,CAAA,MAAA,IAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,IAAI,CAAC,YAAA,EAAc;AACjB,QAAA,YAAA,GAAe,EAAE,UAAA,EAAY,CAAC,GAAG,CAAA,EAAG,UAAU,EAAC,EAAG,KAAA,EAAO,EAAC,EAAE;AAAA,MAC9D;AACA,MAAA,YAAA,CAAa,KAAA,CAAM,KAAK,KAAK,CAAA;AAAA,IAC/B;AAAA,EAEF;AAGA,EAAA,IAAI,YAAA,IAAgB,YAAA,CAAa,UAAA,CAAW,MAAA,GAAS,CAAA,EAAG;AACtD,IAAA,MAAA,CAAO,KAAK,YAAY,CAAA;AAAA,EAC1B;AAEA,EAAA,OAAO,MAAA;AACT;AAUA,SAAS,cAAc,KAAA,EAAmC;AAExD,EAAA,MAAM,iBAAA,GAAoB,MAAM,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,CAAC,CAAA;AAGnE,EAAA,IAAI,iBAAA,CAAkB,WAAW,CAAA,EAAG;AAClC,IAAA,OAAO,OAAA;AAAA,EACT;AAGA,EAAA,IAAI,kBAAkB,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,KAAM,GAAG,CAAA,EAAG;AAG5C,IAAA,IAAI,MAAM,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,KAAM,GAAG,CAAA,EAAG;AACtC,MAAA,OAAO,OAAA;AAAA,IACT;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAIA,EAAA,OAAO,aAAA;AACT;AAYO,SAAS,eAAe,OAAA,EAAuC;AACpE,EAAA,IAAI,OAAA,CAAQ,SAAS,mBAAA,EAAqB;AACxC,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,MAAA,GAAS,iBAAiB,OAAO,CAAA;AACvC,EAAA,MAAM,UAAgC,EAAC;AACvC,EAAA,MAAM,YAAA,uBAAmB,GAAA,EAAoB;AAG7C,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,KAAA,MAAW,EAAA,IAAM,MAAM,UAAA,EAAY;AACjC,MAAA,MAAM,QAAA,GAAW,eAAe,EAAE,CAAA;AAClC,MAAA,IAAI,CAAC,QAAA,EAAU;AAEf,MAAA,MAAM,QAAA,GAAW,cAAc,KAAK,CAAA;AACpC,MAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,QAAA,IAAI,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA,EAAG;AAC/B,QAAA,YAAA,CAAa,IAAI,OAAO,CAAA;AACxB,QAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,UACX,OAAA;AAAA,UACA,QAAA;AAAA,UACA,MAAA,EAAQ,YAAA;AAAA,UACR,SAAA,EAAW,eAAe,EAAE,CAAA;AAAA,SAC7B,CAAA;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,EAAA,MAAM,aAAA,GAAgB,OAAO,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,UAAA,CAAW,QAAA,CAAS,GAAG,CAAC,CAAA;AACnE,EAAA,IAAI,aAAA,EAAe;AACjB,IAAA,MAAM,QAAA,GAAW,cAAc,aAAa,CAAA;AAC5C,IAAA,MAAM,WAAA,GAAgC;AAAA,MACpC,aAAA;AAAA,MACA,cAAA;AAAA,MACA,WAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,KAAA,MAAW,WAAW,WAAA,EAAa;AACjC,MAAA,IAAI,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA,EAAG;AAC/B,MAAA,YAAA,CAAa,IAAI,OAAO,CAAA;AACxB,MAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,QACX,OAAA;AAAA,QACA,QAAA;AAAA,QACA,MAAA,EAAQ,YAAA;AAAA,QACR,SAAA,EAAW;AAAA,OACZ,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;ACpJO,SAAS,YAAY,OAAA,EAAuC;AACjE,EAAA,IAAI,OAAA,CAAQ,SAAS,eAAA,EAAiB;AACpC,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI;AACF,IAAA,IAAA,GAAO,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AAEN,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,IAAI,OAAO,SAAS,QAAA,IAAY,IAAA,KAAS,QAAQ,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACpE,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,WAAA,GAAc,KAAK,iBAAiB,CAAA;AAE1C,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI,QAAA;AAEJ,EAAA,IAAI,gBAAgB,CAAA,EAAG;AACrB,IAAA,QAAA,GAAW,MAAA;AACX,IAAA,QAAA,GAAW,oBAAA;AAAA,EACb,CAAA,MAAA,IAAW,gBAAgB,CAAA,EAAG;AAC5B,IAAA,QAAA,GAAW,OAAA;AACX,IAAA,QAAA,GAAW,oBAAA;AAAA,EACb,CAAA,MAAO;AAEL,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,OAAA,GAAgC;AAAA,IACpC;AAAA,MACE,OAAA,EAAS,KAAA;AAAA,MACT,QAAA;AAAA,MACA,MAAA,EAAQ,aAAA;AAAA,MACR,SAAA,EAAW;AAAA,KACb;AAAA,IACA;AAAA,MACE,OAAA,EAAS,aAAA;AAAA,MACT,QAAA;AAAA,MACA,MAAA,EAAQ,aAAA;AAAA,MACR,SAAA,EAAW;AAAA;AACb,GACF;AAGA,EAAA,IAAI,OAAO,IAAA,CAAK,YAAY,CAAA,KAAM,QAAA,EAAU;AAC1C,IAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,MAAA,KAAA,CAAM,YAAY,CAAA,EAAG,KAAA,CAAM,SAAS,CAAA,cAAA,EAAiB,IAAA,CAAK,YAAY,CAAC,CAAA,CAAA;AAAA,IACzE;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;AC9CA,IAAM,iBAAA,uBAAwB,GAAA,CAAI,CAAC,QAAQ,UAAA,EAAY,aAAA,EAAe,QAAQ,CAAC,CAAA;AAS/E,IAAM,cAAA,GAAiD;AAAA,EACrD,UAAA,EAAY,aAAA;AAAA,EACZ,aAAA,EAAe,eAAA;AAAA,EACf,MAAA,EAAQ;AACV,CAAA;AAMA,IAAM,aAAA,GAAoD;AAAA,EACxD,aAAA,EAAe,UAAA;AAAA,EACf,UAAA,EAAY,MAAA;AAAA,EACZ,MAAA,EAAQ,MAAA;AAAA,EACR,IAAA,EAAM;AACR,CAAA;AASA,SAAS,gBAAgB,OAAA,EAAwE;AAC/F,EAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,EAAG;AAC3B,IAAA,OAAO,EAAE,SAAA,EAAW,QAAA,EAAU,UAAA,EAAY,IAAA,EAAK;AAAA,EACjD;AACA,EAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,EAAG;AAC3B,IAAA,OAAO,EAAE,SAAA,EAAW,SAAA,EAAW,UAAA,EAAY,IAAA,EAAK;AAAA,EAClD;AACA,EAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,EAAG;AAC3B,IAAA,OAAO,EAAE,SAAA,EAAW,YAAA,EAAc,UAAA,EAAY,IAAA,EAAK;AAAA,EACrD;AACA,EAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,EAAG;AAC3B,IAAA,OAAO,EAAE,SAAA,EAAW,eAAA,EAAiB,UAAA,EAAY,IAAA,EAAK;AAAA,EACxD;AAEA,EAAA,MAAM,UAAA,GAAa,OAAA,KAAY,GAAA,IAAO,OAAA,KAAY,MAAM,OAAA,GAAU,IAAA;AAClE,EAAA,OAAO,EAAE,SAAA,EAAW,OAAA,EAAS,UAAA,EAAW;AAC1C;AASA,SAAS,kBAAkB,KAAA,EAAqC;AAC9D,EAAA,MAAM,UAAgC,EAAC;AACvC,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA;AAE7B,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,OAAA,GAAU,KAAK,IAAA,EAAK;AAC1B,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,MAAM,GAAA,GAAM,OAAA;AACZ,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,OAAA,CAAQ,GAAG,CAAA;AAEjC,IAAA,IAAI,UAAU,EAAA,EAAI;AAEhB,MAAA,MAAMA,OAAM,WAAA,CAAY,OAAO,CAAA,CAAE,IAAA,GAAO,WAAA,EAAY;AACpD,MAAA,IAAIA,IAAAA,EAAK;AACP,QAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,GAAA,EAAAA,IAAAA,EAAK,KAAK,SAAA,EAAW,SAAA,EAAW,UAAA,EAAY,IAAA,EAAM,CAAA;AAAA,MACnE;AACA,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,GAAA,GAAM,QAAQ,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,CAAE,IAAA,GAAO,WAAA,EAAY;AACvD,IAAA,IAAI,UAAU,OAAA,CAAQ,KAAA,CAAM,KAAA,GAAQ,CAAC,EAAE,IAAA,EAAK;AAG5C,IAAA,OAAA,GAAU,WAAA,CAAY,OAAO,CAAA,CAAE,IAAA,EAAK;AAEpC,IAAA,MAAM,EAAE,SAAA,EAAW,UAAA,EAAW,GAAI,gBAAgB,OAAO,CAAA;AACzD,IAAA,OAAA,CAAQ,KAAK,EAAE,GAAA,EAAK,GAAA,EAAK,SAAA,EAAW,YAAY,CAAA;AAAA,EAClD;AAEA,EAAA,OAAO,OAAA;AACT;AAMA,SAAS,YAAY,CAAA,EAAmB;AACtC,EAAA,MAAM,OAAA,GAAU,CAAA,CAAE,OAAA,CAAQ,GAAG,CAAA;AAC7B,EAAA,OAAO,YAAY,EAAA,GAAK,CAAA,GAAI,CAAA,CAAE,KAAA,CAAM,GAAG,OAAO,CAAA;AAChD;AAyBO,SAAS,kBAAkB,KAAA,EAAwC;AACxE,EAAA,MAAM,WAAA,GAAuC;AAAA,IAC3C,GAAA,EAAK,KAAA;AAAA,IACL,QAAQ,EAAC;AAAA,IACT,SAAS,EAAC;AAAA,IACV,YAAY;AAAC,GACf;AAEA,EAAA,IAAI,KAAA,CAAM,SAAS,eAAA,EAAiB;AAClC,IAAA,OAAO,WAAA;AAAA,EACT;AAGA,EAAA,MAAM,SAAA,GAAY,kBAAkB,KAAK,CAAA;AAKzC,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAuD;AAC5E,EAAA,MAAM,aAAmC,EAAC;AAE1C,EAAA,KAAA,MAAW,UAAU,SAAA,EAAW;AAC9B,IAAA,IAAI,CAAC,iBAAA,CAAkB,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,EAAG;AAEtC,MAAA,UAAA,CAAW,KAAK,MAAM,CAAA;AACtB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,QAAA;AACJ,IAAA,IAAI,MAAA,CAAO,eAAe,GAAA,EAAK;AAC7B,MAAA,QAAA,GAAW,OAAA;AAAA,IACb,CAAA,MAAA,IAAW,MAAA,CAAO,UAAA,KAAe,GAAA,EAAK;AACpC,MAAA,QAAA,GAAW,MAAA;AAAA,IACb,CAAA,MAAO;AACL,MAAA,QAAA,GAAW,aAAA;AAAA,IACb;AAGA,IAAA,QAAA,CAAS,GAAA,CAAI,OAAO,GAAA,EAAK,EAAE,UAAU,GAAA,EAAK,MAAA,CAAO,KAAK,CAAA;AAAA,EACxD;AAKA,EAAA,MAAM,aAAA,uBAAoB,GAAA,EAAuD;AACjF,EAAA,KAAA,MAAW,SAAA,IAAa,MAAA,CAAO,IAAA,CAAK,cAAc,CAAA,EAAG;AACnD,IAAA,MAAM,QAAA,GAAW,QAAA,CAAS,GAAA,CAAI,SAAS,CAAA;AACvC,IAAA,IAAI,QAAA,IAAY,QAAA,CAAS,QAAA,KAAa,aAAA,EAAe;AACnD,MAAA,aAAA,CAAc,GAAA,CAAI,WAAW,QAAQ,CAAA;AACrC,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,OAAA,GAA8B,cAAc,SAAS,CAAA;AACzD,IAAA,IAAI,SAAA;AACJ,IAAA,OAAO,OAAA,EAAS;AACd,MAAA,MAAM,UAAA,GAAa,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA;AACvC,MAAA,IAAI,UAAA,IAAc,UAAA,CAAW,QAAA,KAAa,aAAA,EAAe;AACvD,QAAA,SAAA,GAAY;AAAA,UACV,UAAU,UAAA,CAAW,QAAA;AAAA,UACrB,KAAK,CAAA,EAAG,SAAS,oBAAoB,OAAO,CAAA,CAAA,EAAI,WAAW,GAAG,CAAA,CAAA;AAAA,SAChE;AACA,QAAA;AAAA,MACF;AACA,MAAA,OAAA,GAAU,cAAc,OAAO,CAAA;AAAA,IACjC;AAEA,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,aAAA,CAAc,GAAA,CAAI,WAAW,SAAS,CAAA;AAAA,IACxC;AAAA,EAEF;AAGA,EAAA,MAAM,UAAgC,EAAC;AACvC,EAAA,KAAA,MAAW,CAAC,SAAA,EAAW,IAAI,CAAA,IAAK,aAAA,EAAe;AAC7C,IAAA,MAAM,OAAA,GAAU,eAAe,SAAS,CAAA;AACxC,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,MACX,OAAA;AAAA,MACA,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,MAAA,EAAQ,sBAAA;AAAA,MACR,WAAW,IAAA,CAAK;AAAA,KACjB,CAAA;AAAA,EACH;AAEA,EAAA,OAAO;AAAA,IACL,GAAA,EAAK,KAAA;AAAA,IACL,MAAA,EAAQ,SAAA;AAAA,IACR,OAAA;AAAA,IACA;AAAA,GACF;AACF;;;AC5OO,SAAS,eAAe,OAAA,EAAqD;AAElF,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAA0C;AAChE,EAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,IAAA,MAAM,OAAO,SAAA,CAAU,GAAA,CAAI,KAAA,CAAM,OAAO,KAAK,EAAC;AAC9C,IAAA,IAAA,CAAK,KAAK,KAAK,CAAA;AACf,IAAA,SAAA,CAAU,GAAA,CAAI,KAAA,CAAM,OAAA,EAAS,IAAI,CAAA;AAAA,EACnC;AAEA,EAAA,MAAM,WAAiC,EAAC;AAExC,EAAA,KAAA,MAAW,cAAA,IAAkB,SAAA,CAAU,MAAA,EAAO,EAAG;AAE/C,IAAA,MAAM,MAAA,GAAS,CAAC,GAAG,cAAc,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAChD,MAAA,MAAM,IAAA,GAAO,iBAAA,CAAkB,OAAA,CAAQ,CAAA,CAAE,MAAM,CAAA;AAC/C,MAAA,MAAM,IAAA,GAAO,iBAAA,CAAkB,OAAA,CAAQ,CAAA,CAAE,MAAM,CAAA;AAC/C,MAAA,OAAO,IAAA,GAAO,IAAA;AAAA,IAChB,CAAC,CAAA;AAGD,IAAA,IAAI,MAAA,GAAoC,IAAA;AACxC,IAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,MAAA,IAAI,KAAA,CAAM,QAAA,KAAa,OAAA,IAAW,KAAA,CAAM,aAAa,MAAA,EAAQ;AAC3D,QAAA,MAAA,GAAS,KAAA;AACT,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,QAAA,CAAS,KAAK,MAAM,CAAA;AAAA,IACtB,CAAA,MAAO;AAEL,MAAA,QAAA,CAAS,IAAA,CAAK,MAAA,CAAO,CAAC,CAAC,CAAA;AAAA,IACzB;AAAA,EACF;AAEA,EAAA,OAAO,QAAA;AACT;AAOO,SAAS,oBAAoB,MAAA,EAA8B;AAChE,EAAA,OAAO,iBAAA,CAAkB,QAAQ,MAAM,CAAA;AACzC;AAKO,SAAS,iBAAA,CAAkB,GAAiB,CAAA,EAA0B;AAC3E,EAAA,MAAM,IAAA,GAAO,iBAAA,CAAkB,OAAA,CAAQ,CAAC,CAAA;AACxC,EAAA,MAAM,IAAA,GAAO,iBAAA,CAAkB,OAAA,CAAQ,CAAC,CAAA;AACxC,EAAA,OAAO,IAAA,GAAO,IAAA;AAChB;AAOO,SAAS,qBAAA,CACd,UACA,OAAA,EACgB;AAChB,EAAA,MAAM,QAAQ,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,YAAY,OAAO,CAAA;AACxD,EAAA,OAAO,OAAO,QAAA,IAAY,aAAA;AAC5B;;;AC5CO,SAAS,kBAAkB,KAAA,EAAyD;AACzF,EAAA,MAAM,aAAmC,EAAC;AAC1C,EAAA,MAAM,iBAAiC,EAAC;AAGxC,EAAA,IAAI,KAAA,CAAM,gBAAgB,MAAA,EAAW;AACnC,IAAA,cAAA,CAAe,KAAK,aAAa,CAAA;AACjC,IAAA,MAAM,OAAA,GAAU,WAAA,CAAY,KAAA,CAAM,WAAW,CAAA;AAC7C,IAAA,UAAA,CAAW,IAAA,CAAK,GAAG,OAAO,CAAA;AAAA,EAC5B;AAEA,EAAA,IAAI,KAAA,CAAM,kBAAkB,MAAA,EAAW;AACrC,IAAA,cAAA,CAAe,KAAK,sBAAsB,CAAA;AAC1C,IAAA,MAAM,MAAA,GAAS,iBAAA,CAAkB,KAAA,CAAM,aAAa,CAAA;AACpD,IAAA,UAAA,CAAW,IAAA,CAAK,GAAG,MAAA,CAAO,OAAO,CAAA;AAAA,EACnC;AAEA,EAAA,IAAI,KAAA,CAAM,eAAe,MAAA,EAAW;AAClC,IAAA,cAAA,CAAe,KAAK,YAAY,CAAA;AAChC,IAAA,MAAM,OAAA,GAAU,cAAA,CAAe,KAAA,CAAM,UAAU,CAAA;AAC/C,IAAA,UAAA,CAAW,IAAA,CAAK,GAAG,OAAO,CAAA;AAAA,EAC5B;AAGA,EAAA,MAAM,QAAA,GAAW,eAAe,UAAU,CAAA;AAE1C,EAAA,OAAO;AAAA,IACL,WAAA,EAAA,iBAAa,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,IACpC,YAAY,KAAA,CAAM,UAAA;AAAA,IAClB,OAAA,EAAS,QAAA;AAAA,IACT,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,eAAA,EAAiB;AAAA,GACnB;AACF","file":"index.mjs","sourcesContent":["/**\n * Content Signal Types (DD-136, DD-137)\n *\n * Types for content use policy signal observation.\n * Signals RECORD observations, never enforce (DD-95 rail neutrality).\n */\n\n// ---------------------------------------------------------------------------\n// Core types\n// ---------------------------------------------------------------------------\n\n/**\n * Signal source identifier (DD-137 precedence order).\n *\n * Note: Content-Signal header is reserved for a future version when a parser\n * is implemented. Only sources with shipped parsers are included here.\n */\nexport type SignalSource = 'tdmrep-json' | 'content-usage-header' | 'robots-txt';\n\n/** Three-state signal decision (DD-136) */\nexport type SignalDecision = 'allow' | 'deny' | 'unspecified';\n\n/**\n * Canonical purpose token for content signals.\n *\n * Subset of PEAC CanonicalPurpose relevant to content use policy signals.\n */\nexport type ContentPurpose = 'ai-training' | 'ai-inference' | 'ai-search' | 'ai-generative' | 'tdm';\n\n/** Single content signal entry from a specific source */\nexport interface ContentSignalEntry {\n /** Purpose this signal applies to */\n purpose: ContentPurpose;\n /** Three-state decision */\n decision: SignalDecision;\n /** Which source produced this signal */\n source: SignalSource;\n /** Raw value from the source (for debugging) */\n raw_value?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Structured Fields types (RFC 9651)\n// ---------------------------------------------------------------------------\n\n/** SF value type classification per RFC 9651 */\nexport type SfValueType = 'token' | 'string' | 'boolean' | 'inner-list' | 'byte-sequence';\n\n/** Single parsed Structured Fields Dictionary member (RFC 9651) */\nexport interface SfDictionaryMember {\n /** Member key (lowercase, as parsed) */\n key: string;\n /** Raw member string from the header (key=value with parameters) */\n raw: string;\n /** SF value type classification */\n valueType: SfValueType;\n /** Token value if valueType is 'token', null otherwise */\n tokenValue: string | null;\n}\n\n/** Full parse result from Content-Usage header parsing */\nexport interface ContentUsageParseResult {\n /** Original raw header value */\n raw: string;\n /** All parsed SF Dictionary members (known and unknown) */\n parsed: SfDictionaryMember[];\n /** Mapped signal entries for recognized AIPREF vocabulary keys */\n entries: ContentSignalEntry[];\n /** Unrecognized dictionary members (forward-compatible pass-through) */\n extensions: SfDictionaryMember[];\n}\n\n/** Aggregated content signal observation */\nexport interface ContentSignalObservation {\n /** When the signals were observed (ISO 8601) */\n observed_at: string;\n /** URI the signals apply to */\n target_uri: string;\n /** Resolved signals (one per purpose, highest-priority source wins) */\n signals: ContentSignalEntry[];\n /** Content digest for integrity binding */\n digest?: { alg: 'sha-256'; val: string };\n /** Which sources were checked */\n sources_checked: SignalSource[];\n}\n\n// ---------------------------------------------------------------------------\n// Input types (pre-fetched content)\n// ---------------------------------------------------------------------------\n\n/** Pre-fetched robots.txt content */\nexport interface RobotsTxtInput {\n /** Raw text content of robots.txt */\n content: string;\n}\n\n/** Pre-fetched tdmrep.json content */\nexport interface TdmrepInput {\n /** Raw JSON string of tdmrep.json */\n content: string;\n}\n\n/** Pre-fetched Content-Usage header value */\nexport interface ContentUsageInput {\n /** Raw header value */\n value: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Maximum input size for robots.txt (500 KB) */\nexport const MAX_ROBOTS_TXT_SIZE = 512000;\n\n/** Maximum input size for tdmrep.json (64 KB) */\nexport const MAX_TDMREP_SIZE = 65536;\n\n/** Maximum header value size (8 KB) */\nexport const MAX_HEADER_SIZE = 8192;\n\n/**\n * AI-relevant user-agent strings for robots.txt parsing.\n *\n * Maps user-agent tokens to the content purposes they represent.\n */\nexport const AI_USER_AGENTS: Record<string, ContentPurpose[]> = {\n gptbot: ['ai-training', 'ai-inference'],\n 'chatgpt-user': ['ai-inference'],\n 'anthropic-ai': ['ai-training', 'ai-inference'],\n claudebot: ['ai-training'],\n 'google-extended': ['ai-training', 'ai-generative'],\n ccbot: ['ai-training'],\n perplexitybot: ['ai-search'],\n 'cohere-ai': ['ai-training', 'ai-inference'],\n bytespider: ['ai-training'],\n};\n\n/**\n * Signal source precedence (DD-137).\n * Lower index = higher priority.\n *\n * Note: Content-Signal header is reserved for a future version.\n * When implemented, it will slot between tdmrep-json and content-usage-header.\n */\nexport const SOURCE_PRECEDENCE: readonly SignalSource[] = [\n 'tdmrep-json',\n 'content-usage-header',\n 'robots-txt',\n] as const;\n","/**\n * robots.txt Parser (RFC 9309)\n *\n * Parses robots.txt content and extracts AI-relevant signals.\n * Receives pre-fetched text content (no network I/O, DD-55).\n */\n\nimport type { ContentSignalEntry, ContentPurpose, SignalDecision } from './types.js';\nimport { AI_USER_AGENTS, MAX_ROBOTS_TXT_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface RobotGroup {\n userAgents: string[];\n disallow: string[];\n allow: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Parser\n// ---------------------------------------------------------------------------\n\n/**\n * Parse robots.txt content into structured groups.\n *\n * Follows RFC 9309 (Robots Exclusion Protocol, Sep 2022):\n * - Groups are defined by User-agent lines\n * - Disallow/Allow directives apply to the preceding user-agent group\n * - Case-insensitive directive matching\n * - Lines starting with # are comments\n */\nfunction parseRobotGroups(content: string): RobotGroup[] {\n const groups: RobotGroup[] = [];\n let currentGroup: RobotGroup | null = null;\n\n const lines = content.split(/\\r?\\n/);\n for (const rawLine of lines) {\n const line = rawLine.trim();\n\n // Skip empty lines and comments\n if (line === '' || line.startsWith('#')) {\n // Empty line between groups ends the current group\n if (line === '' && currentGroup && currentGroup.userAgents.length > 0) {\n groups.push(currentGroup);\n currentGroup = null;\n }\n continue;\n }\n\n // Parse directive: \"Field: Value\"\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) continue;\n\n const field = line.slice(0, colonIdx).trim().toLowerCase();\n const value = line\n .slice(colonIdx + 1)\n .split('#')[0]\n .trim(); // Strip inline comments\n\n if (field === 'user-agent') {\n if (!currentGroup) {\n currentGroup = { userAgents: [], disallow: [], allow: [] };\n }\n currentGroup.userAgents.push(value.toLowerCase());\n } else if (field === 'disallow') {\n if (!currentGroup) {\n currentGroup = { userAgents: ['*'], disallow: [], allow: [] };\n }\n currentGroup.disallow.push(value);\n } else if (field === 'allow') {\n if (!currentGroup) {\n currentGroup = { userAgents: ['*'], disallow: [], allow: [] };\n }\n currentGroup.allow.push(value);\n }\n // Sitemap and other directives are ignored for signal purposes\n }\n\n // Push final group if any\n if (currentGroup && currentGroup.userAgents.length > 0) {\n groups.push(currentGroup);\n }\n\n return groups;\n}\n\n/**\n * Determine signal decision for a group.\n *\n * Per RFC 9309:\n * - Disallow: / means deny all\n * - Empty Disallow: means allow all\n * - More specific paths are not evaluated (we only check root-level signals)\n */\nfunction groupDecision(group: RobotGroup): SignalDecision {\n // Filter out empty Disallow values (empty string = allow per RFC 9309)\n const effectiveDisallow = group.disallow.filter((d) => d.length > 0);\n\n // No effective Disallow lines means allow\n if (effectiveDisallow.length === 0) {\n return 'allow';\n }\n\n // Check for blanket deny (Disallow: /)\n if (effectiveDisallow.some((d) => d === '/')) {\n // Check if there's a more specific Allow (e.g., Allow: /public/)\n // For signal purposes, Disallow: / is a deny unless Allow: / overrides\n if (group.allow.some((a) => a === '/')) {\n return 'allow';\n }\n return 'deny';\n }\n\n // Disallow present but not root-level: partial deny, treated as unspecified\n // for signal purposes (we don't resolve path-specific rules)\n return 'unspecified';\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse robots.txt content and extract AI-relevant signals.\n *\n * @param content - Raw text content of robots.txt (pre-fetched)\n * @returns Array of ContentSignalEntry for matched AI user-agents\n */\nexport function parseRobotsTxt(content: string): ContentSignalEntry[] {\n if (content.length > MAX_ROBOTS_TXT_SIZE) {\n return [];\n }\n\n const groups = parseRobotGroups(content);\n const entries: ContentSignalEntry[] = [];\n const seenPurposes = new Set<ContentPurpose>();\n\n // First pass: check specific AI user-agents\n for (const group of groups) {\n for (const ua of group.userAgents) {\n const purposes = AI_USER_AGENTS[ua];\n if (!purposes) continue;\n\n const decision = groupDecision(group);\n for (const purpose of purposes) {\n if (seenPurposes.has(purpose)) continue;\n seenPurposes.add(purpose);\n entries.push({\n purpose,\n decision,\n source: 'robots-txt',\n raw_value: `User-agent: ${ua}`,\n });\n }\n }\n }\n\n // Second pass: check wildcard * for remaining purposes\n const wildcardGroup = groups.find((g) => g.userAgents.includes('*'));\n if (wildcardGroup) {\n const decision = groupDecision(wildcardGroup);\n const allPurposes: ContentPurpose[] = [\n 'ai-training',\n 'ai-inference',\n 'ai-search',\n 'ai-generative',\n ];\n for (const purpose of allPurposes) {\n if (seenPurposes.has(purpose)) continue;\n seenPurposes.add(purpose);\n entries.push({\n purpose,\n decision,\n source: 'robots-txt',\n raw_value: 'User-agent: *',\n });\n }\n }\n\n return entries;\n}\n","/**\n * tdmrep.json Parser (EU TDM Directive 2019/790, Art. 4)\n *\n * Parses tdmrep.json content for EU Text and Data Mining reservation signals.\n * Receives pre-fetched JSON content (no network I/O, DD-55).\n */\n\nimport type { ContentSignalEntry, SignalDecision } from './types.js';\nimport { MAX_TDMREP_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface TdmrepData {\n 'tdm-reservation'?: number;\n 'tdm-policy'?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse tdmrep.json content and extract TDM reservation signals.\n *\n * EU Directive 2019/790, Article 4:\n * - tdm-reservation: 0 = no reservation (allow TDM), 1 = reserved (deny TDM)\n * - tdm-policy: URL to machine-readable license terms\n *\n * @param content - Raw JSON string of tdmrep.json (pre-fetched)\n * @returns Array of ContentSignalEntry for TDM purposes\n */\nexport function parseTdmrep(content: string): ContentSignalEntry[] {\n if (content.length > MAX_TDMREP_SIZE) {\n return [];\n }\n\n let data: TdmrepData;\n try {\n data = JSON.parse(content) as TdmrepData;\n } catch {\n // Malformed JSON: return unspecified\n return [];\n }\n\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n return [];\n }\n\n const reservation = data['tdm-reservation'];\n\n let decision: SignalDecision;\n let rawValue: string;\n\n if (reservation === 1) {\n decision = 'deny';\n rawValue = 'tdm-reservation: 1';\n } else if (reservation === 0) {\n decision = 'allow';\n rawValue = 'tdm-reservation: 0';\n } else {\n // Absent or invalid value\n return [];\n }\n\n const entries: ContentSignalEntry[] = [\n {\n purpose: 'tdm',\n decision,\n source: 'tdmrep-json',\n raw_value: rawValue,\n },\n {\n purpose: 'ai-training',\n decision,\n source: 'tdmrep-json',\n raw_value: rawValue,\n },\n ];\n\n // If a tdm-policy URL is present, include it in the raw_value\n if (typeof data['tdm-policy'] === 'string') {\n for (const entry of entries) {\n entry.raw_value = `${entry.raw_value}, tdm-policy: ${data['tdm-policy']}`;\n }\n }\n\n return entries;\n}\n","/**\n * Content-Usage Header Parser (AIPREF attach draft, draft-ietf-aipref-attach-04)\n *\n * Parses Content-Usage HTTP header values as Structured Fields Dictionaries\n * per RFC 9651. Maps AIPREF vocabulary keys (draft-ietf-aipref-vocab-03)\n * to PEAC ContentPurpose values.\n *\n * Scope: HTTP header parsing only. Does NOT parse robots.txt directives\n * or any other signal source. Receives pre-fetched header value (no network\n * I/O, DD-55).\n *\n * AIPREF vocabulary keys (draft-ietf-aipref-vocab-03, Table 1):\n * - bots: Automated processing (parent of train-ai and search)\n * - train-ai: AI training (parent of train-genai)\n * - train-genai: Generative AI training\n * - search: Search applications\n *\n * Values are SF Tokens: y = allow, n = disallow, anything else = unknown.\n * Bare keys (Boolean true per SF rules) are NOT y and produce unknown.\n *\n * Hierarchy propagation (Section 5.2 of vocab-03):\n * bots -> train-ai -> train-genai\n * bots -> search\n * When a specific key has no explicit preference, inherit from parent.\n */\n\nimport type {\n ContentSignalEntry,\n ContentPurpose,\n SignalDecision,\n SfDictionaryMember,\n SfValueType,\n ContentUsageParseResult,\n} from './types.js';\nimport { MAX_HEADER_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// AIPREF vocabulary\n// ---------------------------------------------------------------------------\n\n/**\n * All recognized AIPREF vocabulary keys (draft-ietf-aipref-vocab-03, Table 1).\n * Used for parsing; includes parent-only keys that do not produce output entries.\n */\nconst AIPREF_KNOWN_KEYS = new Set(['bots', 'train-ai', 'train-genai', 'search']);\n\n/**\n * AIPREF leaf/child vocabulary keys mapped to PEAC ContentPurpose.\n * Per draft-ietf-aipref-vocab-03, Table 1.\n *\n * Note: `bots` is a parent-only key used solely for hierarchy propagation\n * (Section 5.2). It does not produce its own output entry.\n */\nconst AIPREF_KEY_MAP: Record<string, ContentPurpose> = {\n 'train-ai': 'ai-training',\n 'train-genai': 'ai-generative',\n search: 'ai-search',\n};\n\n/**\n * Hierarchy for AIPREF propagation (Section 5.2 of vocab-03).\n * Maps each key to its parent key. Root keys have no parent.\n */\nconst AIPREF_PARENT: Record<string, string | undefined> = {\n 'train-genai': 'train-ai',\n 'train-ai': 'bots',\n search: 'bots',\n bots: undefined,\n};\n\n// ---------------------------------------------------------------------------\n// Structured Fields Dictionary Parser (RFC 9651 subset)\n// ---------------------------------------------------------------------------\n\n/**\n * Classify SF value type from the raw value portion of a member.\n */\nfunction classifySfValue(valPart: string): { valueType: SfValueType; tokenValue: string | null } {\n if (valPart.startsWith('\"')) {\n return { valueType: 'string', tokenValue: null };\n }\n if (valPart.startsWith('?')) {\n return { valueType: 'boolean', tokenValue: null };\n }\n if (valPart.startsWith('(')) {\n return { valueType: 'inner-list', tokenValue: null };\n }\n if (valPart.startsWith(':')) {\n return { valueType: 'byte-sequence', tokenValue: null };\n }\n // Token value (alphanumeric + limited special chars per RFC 9651)\n const tokenValue = valPart === 'y' || valPart === 'n' ? valPart : null;\n return { valueType: 'token', tokenValue };\n}\n\n/**\n * Parse an SF Dictionary header value (RFC 9651, Section 4.2.2).\n *\n * This is a minimal parser sufficient for AIPREF Content-Usage headers.\n * Handles: Token values, String values, Boolean bare items,\n * parameters (stripped). Does NOT handle Inner Lists.\n */\nfunction parseSfDictionary(input: string): SfDictionaryMember[] {\n const members: SfDictionaryMember[] = [];\n const parts = input.split(',');\n\n for (const part of parts) {\n const trimmed = part.trim();\n if (!trimmed) continue;\n\n const raw = trimmed;\n const eqIdx = trimmed.indexOf('=');\n\n if (eqIdx === -1) {\n // Bare key: per SF rules, this is Boolean true, not Token y/n\n const key = stripParams(trimmed).trim().toLowerCase();\n if (key) {\n members.push({ key, raw, valueType: 'boolean', tokenValue: null });\n }\n continue;\n }\n\n const key = trimmed.slice(0, eqIdx).trim().toLowerCase();\n let valPart = trimmed.slice(eqIdx + 1).trim();\n\n // Strip parameters from value (;key=value portions)\n valPart = stripParams(valPart).trim();\n\n const { valueType, tokenValue } = classifySfValue(valPart);\n members.push({ key, raw, valueType, tokenValue });\n }\n\n return members;\n}\n\n/**\n * Strip SF parameters from a value or bare key.\n * Parameters start with ';' and are key=value or key pairs.\n */\nfunction stripParams(s: string): string {\n const semiIdx = s.indexOf(';');\n return semiIdx === -1 ? s : s.slice(0, semiIdx);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse Content-Usage header value and extract signal entries.\n *\n * Implements the AIPREF attach draft (draft-ietf-aipref-attach-04) with\n * vocabulary from draft-ietf-aipref-vocab-03. Header is parsed as an\n * SF Dictionary (RFC 9651). Values must be Tokens: y = allow, n = disallow.\n *\n * Returns a structured result preserving all parse pipeline stages:\n * - `raw`: original header string\n * - `parsed`: all SF Dictionary members (known and unknown)\n * - `entries`: mapped signal entries for recognized AIPREF keys\n * - `extensions`: unrecognized dictionary members (forward-compatible)\n *\n * Scope: HTTP Content-Usage header only. Does not handle robots.txt\n * or any other signal source.\n *\n * @param value - Raw Content-Usage header value (pre-fetched)\n * @returns Structured parse result with entries and extensions\n */\nexport function parseContentUsage(value: string): ContentUsageParseResult {\n const emptyResult: ContentUsageParseResult = {\n raw: value,\n parsed: [],\n entries: [],\n extensions: [],\n };\n\n if (value.length > MAX_HEADER_SIZE) {\n return emptyResult;\n }\n\n // Step 1: Parse SF Dictionary\n const sfMembers = parseSfDictionary(value);\n\n // Step 2: Build raw preference map for known AIPREF keys\n // Track all known AIPREF keys (including parent-only keys like bots) for inheritance.\n // Separate unknown keys into extensions (forward-compatible pass-through).\n const rawPrefs = new Map<string, { decision: SignalDecision; raw: string }>();\n const extensions: SfDictionaryMember[] = [];\n\n for (const member of sfMembers) {\n if (!AIPREF_KNOWN_KEYS.has(member.key)) {\n // Unknown key: store as extension (never drop)\n extensions.push(member);\n continue;\n }\n\n let decision: SignalDecision;\n if (member.tokenValue === 'y') {\n decision = 'allow';\n } else if (member.tokenValue === 'n') {\n decision = 'deny';\n } else {\n decision = 'unspecified'; // Non-Token, unknown Token, or bare key\n }\n\n // Last value wins (SF Dictionary duplicate key rule)\n rawPrefs.set(member.key, { decision, raw: member.raw });\n }\n\n // Step 3: Apply hierarchy propagation (Section 5.2 of vocab-03)\n // For each leaf AIPREF key, if preference is missing or unspecified,\n // inherit from parent.\n const resolvedPrefs = new Map<string, { decision: SignalDecision; raw: string }>();\n for (const aiprefKey of Object.keys(AIPREF_KEY_MAP)) {\n const explicit = rawPrefs.get(aiprefKey);\n if (explicit && explicit.decision !== 'unspecified') {\n resolvedPrefs.set(aiprefKey, explicit);\n continue;\n }\n\n // Walk up hierarchy for inheritance\n let current: string | undefined = AIPREF_PARENT[aiprefKey];\n let inherited: { decision: SignalDecision; raw: string } | undefined;\n while (current) {\n const parentPref = rawPrefs.get(current);\n if (parentPref && parentPref.decision !== 'unspecified') {\n inherited = {\n decision: parentPref.decision,\n raw: `${aiprefKey} (inherited from ${current}=${parentPref.raw})`,\n };\n break;\n }\n current = AIPREF_PARENT[current];\n }\n\n if (inherited) {\n resolvedPrefs.set(aiprefKey, inherited);\n }\n // If no inheritance found, key stays absent (unspecified)\n }\n\n // Step 4: Convert to ContentSignalEntry\n const entries: ContentSignalEntry[] = [];\n for (const [aiprefKey, pref] of resolvedPrefs) {\n const purpose = AIPREF_KEY_MAP[aiprefKey];\n if (!purpose) continue;\n\n entries.push({\n purpose,\n decision: pref.decision,\n source: 'content-usage-header',\n raw_value: pref.raw,\n });\n }\n\n return {\n raw: value,\n parsed: sfMembers,\n entries,\n extensions,\n };\n}\n","/**\n * Signal Priority Resolution (DD-137)\n *\n * Resolves signals from multiple sources using precedence rules.\n * tdmrep.json > Content-Usage > robots.txt\n */\n\nimport type { ContentSignalEntry, ContentPurpose, SignalDecision, SignalSource } from './types.js';\nimport { SOURCE_PRECEDENCE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve signals from multiple sources using DD-137 precedence.\n *\n * Per DD-137, when multiple sources provide signals for the same purpose,\n * the highest-priority source with a definitive signal (allow or deny) wins.\n * If all sources return unspecified, the resolved signal is unspecified.\n *\n * @param entries - All signal entries from all sources\n * @returns Resolved entries (one per purpose, highest-priority source wins)\n */\nexport function resolveSignals(entries: ContentSignalEntry[]): ContentSignalEntry[] {\n // Group entries by purpose\n const byPurpose = new Map<ContentPurpose, ContentSignalEntry[]>();\n for (const entry of entries) {\n const list = byPurpose.get(entry.purpose) || [];\n list.push(entry);\n byPurpose.set(entry.purpose, list);\n }\n\n const resolved: ContentSignalEntry[] = [];\n\n for (const purposeEntries of byPurpose.values()) {\n // Sort by source precedence (lower index = higher priority)\n const sorted = [...purposeEntries].sort((a, b) => {\n const aIdx = SOURCE_PRECEDENCE.indexOf(a.source);\n const bIdx = SOURCE_PRECEDENCE.indexOf(b.source);\n return aIdx - bIdx;\n });\n\n // Find first definitive signal (allow or deny)\n let winner: ContentSignalEntry | null = null;\n for (const entry of sorted) {\n if (entry.decision === 'allow' || entry.decision === 'deny') {\n winner = entry;\n break;\n }\n }\n\n if (winner) {\n resolved.push(winner);\n } else {\n // All unspecified: use highest-priority source's entry\n resolved.push(sorted[0]);\n }\n }\n\n return resolved;\n}\n\n/**\n * Get the precedence index for a signal source.\n *\n * Lower number = higher priority. Returns -1 for unknown sources.\n */\nexport function getSourcePrecedence(source: SignalSource): number {\n return SOURCE_PRECEDENCE.indexOf(source);\n}\n\n/**\n * Check if source A has higher priority than source B.\n */\nexport function hasHigherPriority(a: SignalSource, b: SignalSource): boolean {\n const aIdx = SOURCE_PRECEDENCE.indexOf(a);\n const bIdx = SOURCE_PRECEDENCE.indexOf(b);\n return aIdx < bIdx;\n}\n\n/**\n * Get the effective decision for a specific purpose from resolved signals.\n *\n * @returns The signal decision, or 'unspecified' if no signal for the purpose\n */\nexport function getDecisionForPurpose(\n resolved: ContentSignalEntry[],\n purpose: ContentPurpose\n): SignalDecision {\n const entry = resolved.find((e) => e.purpose === purpose);\n return entry?.decision ?? 'unspecified';\n}\n","/**\n * Content Signal Observation Factory\n *\n * Creates ContentSignalObservation objects from parsed signals.\n */\n\nimport type {\n ContentSignalObservation,\n ContentSignalEntry,\n SignalSource,\n ContentPurpose,\n} from './types.js';\nimport { parseRobotsTxt } from './robots.js';\nimport { parseTdmrep } from './tdmrep.js';\nimport { parseContentUsage } from './content-usage.js';\nimport { resolveSignals } from './resolve.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Input for creating an observation */\nexport interface CreateObservationInput {\n /** Target URI the signals apply to */\n target_uri: string;\n /** Pre-fetched robots.txt content (optional) */\n robots_txt?: string;\n /** Pre-fetched tdmrep.json content (optional) */\n tdmrep_json?: string;\n /** Pre-fetched Content-Usage header value (optional) */\n content_usage?: string;\n /** Content digest for integrity binding (optional) */\n digest?: { alg: 'sha-256'; val: string };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a ContentSignalObservation from pre-fetched signal sources.\n *\n * All inputs are pre-fetched content (no network I/O, DD-55).\n * Signals are parsed from each source and resolved using DD-137 precedence.\n *\n * @param input - Pre-fetched signal sources\n * @returns ContentSignalObservation with resolved signals\n */\nexport function createObservation(input: CreateObservationInput): ContentSignalObservation {\n const allEntries: ContentSignalEntry[] = [];\n const sourcesChecked: SignalSource[] = [];\n\n // Parse each available source\n if (input.tdmrep_json !== undefined) {\n sourcesChecked.push('tdmrep-json');\n const entries = parseTdmrep(input.tdmrep_json);\n allEntries.push(...entries);\n }\n\n if (input.content_usage !== undefined) {\n sourcesChecked.push('content-usage-header');\n const result = parseContentUsage(input.content_usage);\n allEntries.push(...result.entries);\n }\n\n if (input.robots_txt !== undefined) {\n sourcesChecked.push('robots-txt');\n const entries = parseRobotsTxt(input.robots_txt);\n allEntries.push(...entries);\n }\n\n // Resolve signals using DD-137 precedence\n const resolved = resolveSignals(allEntries);\n\n return {\n observed_at: new Date().toISOString(),\n target_uri: input.target_uri,\n signals: resolved,\n digest: input.digest,\n sources_checked: sourcesChecked,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/robots.ts","../src/tdmrep.ts","../src/content-usage.ts","../src/resolve.ts","../src/observation.ts"],"names":["key"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Content Signal Types\n *\n * Types for content use policy signal observation.\n * Signals RECORD observations, never enforce (rail neutrality).\n */\n\n// ---------------------------------------------------------------------------\n// Core types\n// ---------------------------------------------------------------------------\n\n/**\n * Signal source identifier (precedence order).\n *\n * Note: Content-Signal header is reserved for a future version when a parser\n * is implemented. Only sources with shipped parsers are included here.\n */\nexport type SignalSource = 'tdmrep-json' | 'content-usage-header' | 'robots-txt';\n\n/** Three-state signal decision */\nexport type SignalDecision = 'allow' | 'deny' | 'unspecified';\n\n/**\n * Canonical purpose token for content signals.\n *\n * Subset of PEAC CanonicalPurpose relevant to content use policy signals.\n */\nexport type ContentPurpose = 'ai-training' | 'ai-inference' | 'ai-search' | 'ai-generative' | 'tdm';\n\n/** Single content signal entry from a specific source */\nexport interface ContentSignalEntry {\n /** Purpose this signal applies to */\n purpose: ContentPurpose;\n /** Three-state decision */\n decision: SignalDecision;\n /** Which source produced this signal */\n source: SignalSource;\n /** Raw value from the source (for debugging) */\n raw_value?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Structured Fields types (RFC 9651)\n// ---------------------------------------------------------------------------\n\n/** SF value type classification per RFC 9651 */\nexport type SfValueType = 'token' | 'string' | 'boolean' | 'inner-list' | 'byte-sequence';\n\n/** Single parsed Structured Fields Dictionary member (RFC 9651) */\nexport interface SfDictionaryMember {\n /** Member key (lowercase, as parsed) */\n key: string;\n /** Raw member string from the header (key=value with parameters) */\n raw: string;\n /** SF value type classification */\n valueType: SfValueType;\n /** Token value if valueType is 'token', null otherwise */\n tokenValue: string | null;\n}\n\n/** Full parse result from Content-Usage header parsing */\nexport interface ContentUsageParseResult {\n /** Original raw header value */\n raw: string;\n /** All parsed SF Dictionary members (known and unknown) */\n parsed: SfDictionaryMember[];\n /** Mapped signal entries for recognized AIPREF vocabulary keys */\n entries: ContentSignalEntry[];\n /** Unrecognized dictionary members (forward-compatible pass-through) */\n extensions: SfDictionaryMember[];\n}\n\n/** Aggregated content signal observation */\nexport interface ContentSignalObservation {\n /** When the signals were observed (ISO 8601) */\n observed_at: string;\n /** URI the signals apply to */\n target_uri: string;\n /** Resolved signals (one per purpose, highest-priority source wins) */\n signals: ContentSignalEntry[];\n /** Content digest for integrity binding */\n digest?: { alg: 'sha-256'; val: string };\n /** Which sources were checked */\n sources_checked: SignalSource[];\n}\n\n// ---------------------------------------------------------------------------\n// Input types (pre-fetched content)\n// ---------------------------------------------------------------------------\n\n/** Pre-fetched robots.txt content */\nexport interface RobotsTxtInput {\n /** Raw text content of robots.txt */\n content: string;\n}\n\n/** Pre-fetched tdmrep.json content */\nexport interface TdmrepInput {\n /** Raw JSON string of tdmrep.json */\n content: string;\n}\n\n/** Pre-fetched Content-Usage header value */\nexport interface ContentUsageInput {\n /** Raw header value */\n value: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Maximum input size for robots.txt (500 KB) */\nexport const MAX_ROBOTS_TXT_SIZE = 512000;\n\n/** Maximum input size for tdmrep.json (64 KB) */\nexport const MAX_TDMREP_SIZE = 65536;\n\n/** Maximum header value size (8 KB) */\nexport const MAX_HEADER_SIZE = 8192;\n\n/**\n * AI-relevant user-agent strings for robots.txt parsing.\n *\n * Maps user-agent tokens to the content purposes they represent.\n */\nexport const AI_USER_AGENTS: Record<string, ContentPurpose[]> = {\n gptbot: ['ai-training', 'ai-inference'],\n 'chatgpt-user': ['ai-inference'],\n 'anthropic-ai': ['ai-training', 'ai-inference'],\n claudebot: ['ai-training'],\n 'google-extended': ['ai-training', 'ai-generative'],\n ccbot: ['ai-training'],\n perplexitybot: ['ai-search'],\n 'cohere-ai': ['ai-training', 'ai-inference'],\n bytespider: ['ai-training'],\n};\n\n/**\n * Signal source precedence.\n * Lower index = higher priority.\n *\n * Note: Content-Signal header is reserved for a future version.\n * When implemented, it will slot between tdmrep-json and content-usage-header.\n */\nexport const SOURCE_PRECEDENCE: readonly SignalSource[] = [\n 'tdmrep-json',\n 'content-usage-header',\n 'robots-txt',\n] as const;\n","/**\n * robots.txt Parser (RFC 9309)\n *\n * Parses robots.txt content and extracts AI-relevant signals.\n * Receives pre-fetched text content (no network I/O).\n */\n\nimport type { ContentSignalEntry, ContentPurpose, SignalDecision } from './types.js';\nimport { AI_USER_AGENTS, MAX_ROBOTS_TXT_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface RobotGroup {\n userAgents: string[];\n disallow: string[];\n allow: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Parser\n// ---------------------------------------------------------------------------\n\n/**\n * Parse robots.txt content into structured groups.\n *\n * Follows RFC 9309 (Robots Exclusion Protocol, Sep 2022):\n * - Groups are defined by User-agent lines\n * - Disallow/Allow directives apply to the preceding user-agent group\n * - Case-insensitive directive matching\n * - Lines starting with # are comments\n */\nfunction parseRobotGroups(content: string): RobotGroup[] {\n const groups: RobotGroup[] = [];\n let currentGroup: RobotGroup | null = null;\n\n const lines = content.split(/\\r?\\n/);\n for (const rawLine of lines) {\n const line = rawLine.trim();\n\n // Skip empty lines and comments\n if (line === '' || line.startsWith('#')) {\n // Empty line between groups ends the current group\n if (line === '' && currentGroup && currentGroup.userAgents.length > 0) {\n groups.push(currentGroup);\n currentGroup = null;\n }\n continue;\n }\n\n // Parse directive: \"Field: Value\"\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) continue;\n\n const field = line.slice(0, colonIdx).trim().toLowerCase();\n const value = line\n .slice(colonIdx + 1)\n .split('#')[0]\n .trim(); // Strip inline comments\n\n if (field === 'user-agent') {\n if (!currentGroup) {\n currentGroup = { userAgents: [], disallow: [], allow: [] };\n }\n currentGroup.userAgents.push(value.toLowerCase());\n } else if (field === 'disallow') {\n if (!currentGroup) {\n currentGroup = { userAgents: ['*'], disallow: [], allow: [] };\n }\n currentGroup.disallow.push(value);\n } else if (field === 'allow') {\n if (!currentGroup) {\n currentGroup = { userAgents: ['*'], disallow: [], allow: [] };\n }\n currentGroup.allow.push(value);\n }\n // Sitemap and other directives are ignored for signal purposes\n }\n\n // Push final group if any\n if (currentGroup && currentGroup.userAgents.length > 0) {\n groups.push(currentGroup);\n }\n\n return groups;\n}\n\n/**\n * Determine signal decision for a group.\n *\n * Per RFC 9309:\n * - Disallow: / means deny all\n * - Empty Disallow: means allow all\n * - More specific paths are not evaluated (we only check root-level signals)\n */\nfunction groupDecision(group: RobotGroup): SignalDecision {\n // Filter out empty Disallow values (empty string = allow per RFC 9309)\n const effectiveDisallow = group.disallow.filter((d) => d.length > 0);\n\n // No effective Disallow lines means allow\n if (effectiveDisallow.length === 0) {\n return 'allow';\n }\n\n // Check for blanket deny (Disallow: /)\n if (effectiveDisallow.some((d) => d === '/')) {\n // Check if there's a more specific Allow (e.g., Allow: /public/)\n // For signal purposes, Disallow: / is a deny unless Allow: / overrides\n if (group.allow.some((a) => a === '/')) {\n return 'allow';\n }\n return 'deny';\n }\n\n // Disallow present but not root-level: partial deny, treated as unspecified\n // for signal purposes (we don't resolve path-specific rules)\n return 'unspecified';\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse robots.txt content and extract AI-relevant signals.\n *\n * @param content - Raw text content of robots.txt (pre-fetched)\n * @returns Array of ContentSignalEntry for matched AI user-agents\n */\nexport function parseRobotsTxt(content: string): ContentSignalEntry[] {\n if (content.length > MAX_ROBOTS_TXT_SIZE) {\n return [];\n }\n\n const groups = parseRobotGroups(content);\n const entries: ContentSignalEntry[] = [];\n const seenPurposes = new Set<ContentPurpose>();\n\n // First pass: check specific AI user-agents\n for (const group of groups) {\n for (const ua of group.userAgents) {\n const purposes = AI_USER_AGENTS[ua];\n if (!purposes) continue;\n\n const decision = groupDecision(group);\n for (const purpose of purposes) {\n if (seenPurposes.has(purpose)) continue;\n seenPurposes.add(purpose);\n entries.push({\n purpose,\n decision,\n source: 'robots-txt',\n raw_value: `User-agent: ${ua}`,\n });\n }\n }\n }\n\n // Second pass: check wildcard * for remaining purposes\n const wildcardGroup = groups.find((g) => g.userAgents.includes('*'));\n if (wildcardGroup) {\n const decision = groupDecision(wildcardGroup);\n const allPurposes: ContentPurpose[] = [\n 'ai-training',\n 'ai-inference',\n 'ai-search',\n 'ai-generative',\n ];\n for (const purpose of allPurposes) {\n if (seenPurposes.has(purpose)) continue;\n seenPurposes.add(purpose);\n entries.push({\n purpose,\n decision,\n source: 'robots-txt',\n raw_value: 'User-agent: *',\n });\n }\n }\n\n return entries;\n}\n","/**\n * tdmrep.json Parser (EU TDM Directive 2019/790, Art. 4)\n *\n * Parses tdmrep.json content for EU Text and Data Mining reservation signals.\n * Receives pre-fetched JSON content (no network I/O).\n */\n\nimport type { ContentSignalEntry, SignalDecision } from './types.js';\nimport { MAX_TDMREP_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface TdmrepData {\n 'tdm-reservation'?: number;\n 'tdm-policy'?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse tdmrep.json content and extract TDM reservation signals.\n *\n * EU Directive 2019/790, Article 4:\n * - tdm-reservation: 0 = no reservation (allow TDM), 1 = reserved (deny TDM)\n * - tdm-policy: URL to machine-readable license terms\n *\n * @param content - Raw JSON string of tdmrep.json (pre-fetched)\n * @returns Array of ContentSignalEntry for TDM purposes\n */\nexport function parseTdmrep(content: string): ContentSignalEntry[] {\n if (content.length > MAX_TDMREP_SIZE) {\n return [];\n }\n\n let data: TdmrepData;\n try {\n data = JSON.parse(content) as TdmrepData;\n } catch {\n // Malformed JSON: return unspecified\n return [];\n }\n\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n return [];\n }\n\n const reservation = data['tdm-reservation'];\n\n let decision: SignalDecision;\n let rawValue: string;\n\n if (reservation === 1) {\n decision = 'deny';\n rawValue = 'tdm-reservation: 1';\n } else if (reservation === 0) {\n decision = 'allow';\n rawValue = 'tdm-reservation: 0';\n } else {\n // Absent or invalid value\n return [];\n }\n\n const entries: ContentSignalEntry[] = [\n {\n purpose: 'tdm',\n decision,\n source: 'tdmrep-json',\n raw_value: rawValue,\n },\n {\n purpose: 'ai-training',\n decision,\n source: 'tdmrep-json',\n raw_value: rawValue,\n },\n ];\n\n // If a tdm-policy URL is present, include it in the raw_value\n if (typeof data['tdm-policy'] === 'string') {\n for (const entry of entries) {\n entry.raw_value = `${entry.raw_value}, tdm-policy: ${data['tdm-policy']}`;\n }\n }\n\n return entries;\n}\n","/**\n * Content-Usage Header Parser (AIPREF attach draft, draft-ietf-aipref-attach-04)\n *\n * Parses Content-Usage HTTP header values as Structured Fields Dictionaries\n * per RFC 9651. Maps AIPREF vocabulary keys (draft-ietf-aipref-vocab-05)\n * to PEAC ContentPurpose values.\n *\n * Scope: HTTP header parsing only. Does NOT parse robots.txt directives\n * or any other signal source. Receives pre-fetched header value (no network\n * I/O).\n *\n * AIPREF vocabulary keys (draft-ietf-aipref-vocab-05, Table 1):\n * - bots: Automated processing (parent of train-ai and search)\n * - train-ai: AI training (parent of train-genai)\n * - train-genai: Generative AI training\n * - search: Search applications\n *\n * Values are SF Tokens: y = allow, n = disallow, anything else = unknown.\n * Bare keys (Boolean true per SF rules) are NOT y and produce unknown.\n *\n * Hierarchy propagation (Section 5.2 of vocab-05):\n * bots -> train-ai -> train-genai\n * bots -> search\n * When a specific key has no explicit preference, inherit from parent.\n */\n\nimport type {\n ContentSignalEntry,\n ContentPurpose,\n SignalDecision,\n SfDictionaryMember,\n SfValueType,\n ContentUsageParseResult,\n} from './types.js';\nimport { MAX_HEADER_SIZE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// AIPREF vocabulary\n// ---------------------------------------------------------------------------\n\n/**\n * All recognized AIPREF vocabulary keys (draft-ietf-aipref-vocab-05, Table 1).\n * Used for parsing; includes parent-only keys that do not produce output entries.\n */\nconst AIPREF_KNOWN_KEYS = new Set(['bots', 'train-ai', 'train-genai', 'search']);\n\n/**\n * AIPREF leaf/child vocabulary keys mapped to PEAC ContentPurpose.\n * Per draft-ietf-aipref-vocab-05, Table 1.\n *\n * Note: `bots` is a parent-only key used solely for hierarchy propagation\n * (Section 5.2). It does not produce its own output entry.\n */\nconst AIPREF_KEY_MAP: Record<string, ContentPurpose> = {\n 'train-ai': 'ai-training',\n 'train-genai': 'ai-generative',\n search: 'ai-search',\n};\n\n/**\n * Hierarchy for AIPREF propagation (Section 5.2 of vocab-05).\n * Maps each key to its parent key. Root keys have no parent.\n */\nconst AIPREF_PARENT: Record<string, string | undefined> = {\n 'train-genai': 'train-ai',\n 'train-ai': 'bots',\n search: 'bots',\n bots: undefined,\n};\n\n// ---------------------------------------------------------------------------\n// Structured Fields Dictionary Parser (RFC 9651 subset)\n// ---------------------------------------------------------------------------\n\n/**\n * Classify SF value type from the raw value portion of a member.\n */\nfunction classifySfValue(valPart: string): { valueType: SfValueType; tokenValue: string | null } {\n if (valPart.startsWith('\"')) {\n return { valueType: 'string', tokenValue: null };\n }\n if (valPart.startsWith('?')) {\n return { valueType: 'boolean', tokenValue: null };\n }\n if (valPart.startsWith('(')) {\n return { valueType: 'inner-list', tokenValue: null };\n }\n if (valPart.startsWith(':')) {\n return { valueType: 'byte-sequence', tokenValue: null };\n }\n // Token value (alphanumeric + limited special chars per RFC 9651)\n const tokenValue = valPart === 'y' || valPart === 'n' ? valPart : null;\n return { valueType: 'token', tokenValue };\n}\n\n/**\n * Parse an SF Dictionary header value (RFC 9651, Section 4.2.2).\n *\n * This is a minimal parser sufficient for AIPREF Content-Usage headers.\n * Handles: Token values, String values, Boolean bare items,\n * parameters (stripped). Does NOT handle Inner Lists.\n */\nfunction parseSfDictionary(input: string): SfDictionaryMember[] {\n const members: SfDictionaryMember[] = [];\n const parts = input.split(',');\n\n for (const part of parts) {\n const trimmed = part.trim();\n if (!trimmed) continue;\n\n const raw = trimmed;\n const eqIdx = trimmed.indexOf('=');\n\n if (eqIdx === -1) {\n // Bare key: per SF rules, this is Boolean true, not Token y/n\n const key = stripParams(trimmed).trim().toLowerCase();\n if (key) {\n members.push({ key, raw, valueType: 'boolean', tokenValue: null });\n }\n continue;\n }\n\n const key = trimmed.slice(0, eqIdx).trim().toLowerCase();\n let valPart = trimmed.slice(eqIdx + 1).trim();\n\n // Strip parameters from value (;key=value portions)\n valPart = stripParams(valPart).trim();\n\n const { valueType, tokenValue } = classifySfValue(valPart);\n members.push({ key, raw, valueType, tokenValue });\n }\n\n return members;\n}\n\n/**\n * Strip SF parameters from a value or bare key.\n * Parameters start with ';' and are key=value or key pairs.\n */\nfunction stripParams(s: string): string {\n const semiIdx = s.indexOf(';');\n return semiIdx === -1 ? s : s.slice(0, semiIdx);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Parse Content-Usage header value and extract signal entries.\n *\n * Implements the AIPREF attach draft (draft-ietf-aipref-attach-04) with\n * vocabulary from draft-ietf-aipref-vocab-05. Header is parsed as an\n * SF Dictionary (RFC 9651). Values must be Tokens: y = allow, n = disallow.\n *\n * Returns a structured result preserving all parse pipeline stages:\n * - `raw`: original header string\n * - `parsed`: all SF Dictionary members (known and unknown)\n * - `entries`: mapped signal entries for recognized AIPREF keys\n * - `extensions`: unrecognized dictionary members (forward-compatible)\n *\n * Scope: HTTP Content-Usage header only. Does not handle robots.txt\n * or any other signal source.\n *\n * @param value - Raw Content-Usage header value (pre-fetched)\n * @returns Structured parse result with entries and extensions\n */\nexport function parseContentUsage(value: string): ContentUsageParseResult {\n const emptyResult: ContentUsageParseResult = {\n raw: value,\n parsed: [],\n entries: [],\n extensions: [],\n };\n\n if (value.length > MAX_HEADER_SIZE) {\n return emptyResult;\n }\n\n // Step 1: Parse SF Dictionary\n const sfMembers = parseSfDictionary(value);\n\n // Step 2: Build raw preference map for known AIPREF keys\n // Track all known AIPREF keys (including parent-only keys like bots) for inheritance.\n // Separate unknown keys into extensions (forward-compatible pass-through).\n const rawPrefs = new Map<string, { decision: SignalDecision; raw: string }>();\n const extensions: SfDictionaryMember[] = [];\n\n for (const member of sfMembers) {\n if (!AIPREF_KNOWN_KEYS.has(member.key)) {\n // Unknown key: store as extension (never drop)\n extensions.push(member);\n continue;\n }\n\n let decision: SignalDecision;\n if (member.tokenValue === 'y') {\n decision = 'allow';\n } else if (member.tokenValue === 'n') {\n decision = 'deny';\n } else {\n decision = 'unspecified'; // Non-Token, unknown Token, or bare key\n }\n\n // Last value wins (SF Dictionary duplicate key rule)\n rawPrefs.set(member.key, { decision, raw: member.raw });\n }\n\n // Step 3: Apply hierarchy propagation (Section 5.2 of vocab-05)\n // For each leaf AIPREF key, if preference is missing or unspecified,\n // inherit from parent.\n const resolvedPrefs = new Map<string, { decision: SignalDecision; raw: string }>();\n for (const aiprefKey of Object.keys(AIPREF_KEY_MAP)) {\n const explicit = rawPrefs.get(aiprefKey);\n if (explicit && explicit.decision !== 'unspecified') {\n resolvedPrefs.set(aiprefKey, explicit);\n continue;\n }\n\n // Walk up hierarchy for inheritance\n let current: string | undefined = AIPREF_PARENT[aiprefKey];\n let inherited: { decision: SignalDecision; raw: string } | undefined;\n while (current) {\n const parentPref = rawPrefs.get(current);\n if (parentPref && parentPref.decision !== 'unspecified') {\n inherited = {\n decision: parentPref.decision,\n raw: `${aiprefKey} (inherited from ${current}=${parentPref.raw})`,\n };\n break;\n }\n current = AIPREF_PARENT[current];\n }\n\n if (inherited) {\n resolvedPrefs.set(aiprefKey, inherited);\n }\n // If no inheritance found, key stays absent (unspecified)\n }\n\n // Step 4: Convert to ContentSignalEntry\n const entries: ContentSignalEntry[] = [];\n for (const [aiprefKey, pref] of resolvedPrefs) {\n const purpose = AIPREF_KEY_MAP[aiprefKey];\n if (!purpose) continue;\n\n entries.push({\n purpose,\n decision: pref.decision,\n source: 'content-usage-header',\n raw_value: pref.raw,\n });\n }\n\n return {\n raw: value,\n parsed: sfMembers,\n entries,\n extensions,\n };\n}\n","/**\n * Signal Priority Resolution\n *\n * Resolves signals from multiple sources using precedence rules.\n * tdmrep.json > Content-Usage > robots.txt\n */\n\nimport type { ContentSignalEntry, ContentPurpose, SignalDecision, SignalSource } from './types.js';\nimport { SOURCE_PRECEDENCE } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve signals from multiple sources using precedence.\n *\n * Per when multiple sources provide signals for the same purpose,\n * the highest-priority source with a definitive signal (allow or deny) wins.\n * If all sources return unspecified, the resolved signal is unspecified.\n *\n * @param entries - All signal entries from all sources\n * @returns Resolved entries (one per purpose, highest-priority source wins)\n */\nexport function resolveSignals(entries: ContentSignalEntry[]): ContentSignalEntry[] {\n // Group entries by purpose\n const byPurpose = new Map<ContentPurpose, ContentSignalEntry[]>();\n for (const entry of entries) {\n const list = byPurpose.get(entry.purpose) || [];\n list.push(entry);\n byPurpose.set(entry.purpose, list);\n }\n\n const resolved: ContentSignalEntry[] = [];\n\n for (const purposeEntries of byPurpose.values()) {\n // Sort by source precedence (lower index = higher priority)\n const sorted = [...purposeEntries].sort((a, b) => {\n const aIdx = SOURCE_PRECEDENCE.indexOf(a.source);\n const bIdx = SOURCE_PRECEDENCE.indexOf(b.source);\n return aIdx - bIdx;\n });\n\n // Find first definitive signal (allow or deny)\n let winner: ContentSignalEntry | null = null;\n for (const entry of sorted) {\n if (entry.decision === 'allow' || entry.decision === 'deny') {\n winner = entry;\n break;\n }\n }\n\n if (winner) {\n resolved.push(winner);\n } else {\n // All unspecified: use highest-priority source's entry\n resolved.push(sorted[0]);\n }\n }\n\n return resolved;\n}\n\n/**\n * Get the precedence index for a signal source.\n *\n * Lower number = higher priority. Returns -1 for unknown sources.\n */\nexport function getSourcePrecedence(source: SignalSource): number {\n return SOURCE_PRECEDENCE.indexOf(source);\n}\n\n/**\n * Check if source A has higher priority than source B.\n */\nexport function hasHigherPriority(a: SignalSource, b: SignalSource): boolean {\n const aIdx = SOURCE_PRECEDENCE.indexOf(a);\n const bIdx = SOURCE_PRECEDENCE.indexOf(b);\n return aIdx < bIdx;\n}\n\n/**\n * Get the effective decision for a specific purpose from resolved signals.\n *\n * @returns The signal decision, or 'unspecified' if no signal for the purpose\n */\nexport function getDecisionForPurpose(\n resolved: ContentSignalEntry[],\n purpose: ContentPurpose\n): SignalDecision {\n const entry = resolved.find((e) => e.purpose === purpose);\n return entry?.decision ?? 'unspecified';\n}\n","/**\n * Content Signal Observation Factory\n *\n * Creates ContentSignalObservation objects from parsed signals.\n */\n\nimport type {\n ContentSignalObservation,\n ContentSignalEntry,\n SignalSource,\n ContentPurpose,\n} from './types.js';\nimport { parseRobotsTxt } from './robots.js';\nimport { parseTdmrep } from './tdmrep.js';\nimport { parseContentUsage } from './content-usage.js';\nimport { resolveSignals } from './resolve.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Input for creating an observation */\nexport interface CreateObservationInput {\n /** Target URI the signals apply to */\n target_uri: string;\n /** Pre-fetched robots.txt content (optional) */\n robots_txt?: string;\n /** Pre-fetched tdmrep.json content (optional) */\n tdmrep_json?: string;\n /** Pre-fetched Content-Usage header value (optional) */\n content_usage?: string;\n /** Content digest for integrity binding (optional) */\n digest?: { alg: 'sha-256'; val: string };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a ContentSignalObservation from pre-fetched signal sources.\n *\n * All inputs are pre-fetched content (no network I/O).\n * Signals are parsed from each source and resolved using precedence.\n *\n * @param input - Pre-fetched signal sources\n * @returns ContentSignalObservation with resolved signals\n */\nexport function createObservation(input: CreateObservationInput): ContentSignalObservation {\n const allEntries: ContentSignalEntry[] = [];\n const sourcesChecked: SignalSource[] = [];\n\n // Parse each available source\n if (input.tdmrep_json !== undefined) {\n sourcesChecked.push('tdmrep-json');\n const entries = parseTdmrep(input.tdmrep_json);\n allEntries.push(...entries);\n }\n\n if (input.content_usage !== undefined) {\n sourcesChecked.push('content-usage-header');\n const result = parseContentUsage(input.content_usage);\n allEntries.push(...result.entries);\n }\n\n if (input.robots_txt !== undefined) {\n sourcesChecked.push('robots-txt');\n const entries = parseRobotsTxt(input.robots_txt);\n allEntries.push(...entries);\n }\n\n // Resolve signals using precedence\n const resolved = resolveSignals(allEntries);\n\n return {\n observed_at: new Date().toISOString(),\n target_uri: input.target_uri,\n signals: resolved,\n digest: input.digest,\n sources_checked: sourcesChecked,\n };\n}\n"]}
|
package/dist/observation.d.ts
CHANGED
|
@@ -23,8 +23,8 @@ export interface CreateObservationInput {
|
|
|
23
23
|
/**
|
|
24
24
|
* Create a ContentSignalObservation from pre-fetched signal sources.
|
|
25
25
|
*
|
|
26
|
-
* All inputs are pre-fetched content (no network I/O
|
|
27
|
-
* Signals are parsed from each source and resolved using
|
|
26
|
+
* All inputs are pre-fetched content (no network I/O).
|
|
27
|
+
* Signals are parsed from each source and resolved using precedence.
|
|
28
28
|
*
|
|
29
29
|
* @param input - Pre-fetched signal sources
|
|
30
30
|
* @returns ContentSignalObservation with resolved signals
|
package/dist/resolve.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Signal Priority Resolution
|
|
2
|
+
* Signal Priority Resolution
|
|
3
3
|
*
|
|
4
4
|
* Resolves signals from multiple sources using precedence rules.
|
|
5
5
|
* tdmrep.json > Content-Usage > robots.txt
|
|
6
6
|
*/
|
|
7
7
|
import type { ContentSignalEntry, ContentPurpose, SignalDecision, SignalSource } from './types.js';
|
|
8
8
|
/**
|
|
9
|
-
* Resolve signals from multiple sources using
|
|
9
|
+
* Resolve signals from multiple sources using precedence.
|
|
10
10
|
*
|
|
11
|
-
* Per
|
|
11
|
+
* Per when multiple sources provide signals for the same purpose,
|
|
12
12
|
* the highest-priority source with a definitive signal (allow or deny) wins.
|
|
13
13
|
* If all sources return unspecified, the resolved signal is unspecified.
|
|
14
14
|
*
|
package/dist/robots.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* robots.txt Parser (RFC 9309)
|
|
3
3
|
*
|
|
4
4
|
* Parses robots.txt content and extracts AI-relevant signals.
|
|
5
|
-
* Receives pre-fetched text content (no network I/O
|
|
5
|
+
* Receives pre-fetched text content (no network I/O).
|
|
6
6
|
*/
|
|
7
7
|
import type { ContentSignalEntry } from './types.js';
|
|
8
8
|
/**
|
package/dist/tdmrep.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* tdmrep.json Parser (EU TDM Directive 2019/790, Art. 4)
|
|
3
3
|
*
|
|
4
4
|
* Parses tdmrep.json content for EU Text and Data Mining reservation signals.
|
|
5
|
-
* Receives pre-fetched JSON content (no network I/O
|
|
5
|
+
* Receives pre-fetched JSON content (no network I/O).
|
|
6
6
|
*/
|
|
7
7
|
import type { ContentSignalEntry } from './types.js';
|
|
8
8
|
/**
|
package/dist/types.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Content Signal Types
|
|
2
|
+
* Content Signal Types
|
|
3
3
|
*
|
|
4
4
|
* Types for content use policy signal observation.
|
|
5
|
-
* Signals RECORD observations, never enforce (
|
|
5
|
+
* Signals RECORD observations, never enforce (rail neutrality).
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
* Signal source identifier (
|
|
8
|
+
* Signal source identifier (precedence order).
|
|
9
9
|
*
|
|
10
10
|
* Note: Content-Signal header is reserved for a future version when a parser
|
|
11
11
|
* is implemented. Only sources with shipped parsers are included here.
|
|
12
12
|
*/
|
|
13
13
|
export type SignalSource = 'tdmrep-json' | 'content-usage-header' | 'robots-txt';
|
|
14
|
-
/** Three-state signal decision
|
|
14
|
+
/** Three-state signal decision */
|
|
15
15
|
export type SignalDecision = 'allow' | 'deny' | 'unspecified';
|
|
16
16
|
/**
|
|
17
17
|
* Canonical purpose token for content signals.
|
|
@@ -98,7 +98,7 @@ export declare const MAX_HEADER_SIZE = 8192;
|
|
|
98
98
|
*/
|
|
99
99
|
export declare const AI_USER_AGENTS: Record<string, ContentPurpose[]>;
|
|
100
100
|
/**
|
|
101
|
-
* Signal source precedence
|
|
101
|
+
* Signal source precedence.
|
|
102
102
|
* Lower index = higher priority.
|
|
103
103
|
*
|
|
104
104
|
* Note: Content-Signal header is reserved for a future version.
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,sBAAsB,GAAG,YAAY,CAAC;AAEjF,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,sBAAsB,GAAG,YAAY,CAAC;AAEjF,kCAAkC;AAClC,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,GAAG,aAAa,CAAC;AAE9D;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,cAAc,GAAG,WAAW,GAAG,eAAe,GAAG,KAAK,CAAC;AAEpG,yDAAyD;AACzD,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,OAAO,EAAE,cAAc,CAAC;IACxB,2BAA2B;IAC3B,QAAQ,EAAE,cAAc,CAAC;IACzB,wCAAwC;IACxC,MAAM,EAAE,YAAY,CAAC;IACrB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,gDAAgD;AAChD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,eAAe,CAAC;AAE1F,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,SAAS,EAAE,WAAW,CAAC;IACvB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,0DAA0D;AAC1D,MAAM,WAAW,uBAAuB;IACtC,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,kEAAkE;IAClE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,wEAAwE;IACxE,UAAU,EAAE,kBAAkB,EAAE,CAAC;CAClC;AAED,4CAA4C;AAC5C,MAAM,WAAW,wBAAwB;IACvC,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,2CAA2C;IAC3C,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,SAAS,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,iCAAiC;IACjC,eAAe,EAAE,YAAY,EAAE,CAAC;CACjC;AAMD,qCAAqC;AACrC,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AAMD,iDAAiD;AACjD,eAAO,MAAM,mBAAmB,SAAS,CAAC;AAE1C,iDAAiD;AACjD,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,uCAAuC;AACvC,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,CAU3D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,YAAY,EAI3C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peac/mappings-content-signals",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "Content use policy signal parsing for PEAC (robots.txt, tdmrep.json, Content-Usage)",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@peac/kernel": "0.12.
|
|
27
|
-
"@peac/schema": "0.12.
|
|
26
|
+
"@peac/kernel": "0.12.3",
|
|
27
|
+
"@peac/schema": "0.12.3"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^22.19.11",
|