@consenttheater/playbill 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,52 @@
1
+ {
2
+ "category": "tag_manager",
3
+ "description": "Tag management systems — container scripts that load other trackers",
4
+ "stats": { "cookies": 10, "domains": 33, "companies": 25 },
5
+ "cookies": {
6
+ "_dc_gtm_*": { "company": "Google", "service": "Google Tag Manager", "category": "tag_manager", "description": "Throttles request rate for Google Tag Manager container loading", "severity": "medium", "pattern": true, "lifetime": "1 minute", "docs_url": "https://developers.google.com/tag-platform/tag-manager/web" },
7
+ "_zaius_*": { "company": "Optimizely (Zaius)", "service": "Zaius CDP", "category": "tag_manager", "description": "Zaius/Optimizely Data Platform cookie for B2C customer data collection and tag management", "severity": "high", "pattern": true, "lifetime": "1 year", "docs_url": "https://www.optimizely.com/legal/privacy-policy/" },
8
+ "ensighten_*": { "company": "Ensighten", "service": "Ensighten Manage", "category": "tag_manager", "description": "Ensighten tag management cookie for controlling tag firing rules and visitor segmentation", "severity": "medium", "pattern": true, "lifetime": "1 year", "docs_url": "https://www.ensighten.com/privacy-policy/" },
9
+ "lytics_*": { "company": "Lytics", "service": "Lytics CDP", "category": "tag_manager", "description": "Lytics CDP tag management cookie for real-time audience decisioning and tag orchestration", "severity": "high", "pattern": true, "lifetime": "1 year", "docs_url": "https://www.lytics.com/privacy/" },
10
+ "mp_*_api_host_override": { "company": "mParticle", "service": "mParticle SDK", "category": "tag_manager", "description": "mParticle API host override cookie for routing SDK calls through custom proxy endpoints", "severity": "medium", "pattern": true, "lifetime": "session", "docs_url": "https://docs.mparticle.com/developers/sdk/web/configuration/" },
11
+ "rl_*": { "company": "RudderStack", "service": "RudderStack SDK", "category": "tag_manager", "description": "RudderStack open-source CDP cookie storing anonymous ID, user traits, and session data for tag routing", "severity": "high", "pattern": true, "lifetime": "1 year", "docs_url": "https://www.rudderstack.com/privacy-policy/" },
12
+ "tdjs_*": { "company": "Treasure Data", "service": "Treasure Data JS SDK", "category": "tag_manager", "description": "Treasure Data CDP JavaScript SDK cookie for event collection and visitor identification", "severity": "high", "pattern": true, "lifetime": "1 year", "docs_url": "https://www.treasuredata.com/privacy/" },
13
+ "tt_tealium_ct": { "company": "Tealium", "service": "Tealium iQ", "category": "tag_manager", "description": "Tealium connection type cookie for tag management load rules", "severity": "medium", "lifetime": "session", "docs_url": "https://docs.tealium.com/platforms/javascript/cookie-definitions/" },
14
+ "utag_main": { "company": "Tealium", "service": "Tealium iQ", "category": "tag_manager", "description": "Primary Tealium visitor session cookie containing session ID and visit count", "severity": "high", "lifetime": "1 year", "docs_url": "https://docs.tealium.com/platforms/javascript/cookie-definitions/" },
15
+ "utag_main_*": { "company": "Tealium", "service": "Tealium iQ", "category": "tag_manager", "description": "Extended Tealium session data used for tag firing conditions and visitor stitching", "severity": "high", "pattern": true, "lifetime": "1 year", "docs_url": "https://docs.tealium.com/platforms/javascript/cookie-definitions/" }
16
+ },
17
+ "domains": {
18
+ "api.lytics.io": { "company": "Lytics", "service": "Lytics API", "category": "tag_manager", "severity": "high", "docs_url": "https://www.lytics.com/privacy/" },
19
+ "assets.adobedtm.com": { "company": "Adobe", "service": "Adobe Launch (DTM)", "category": "tag_manager", "severity": "medium", "docs_url": "https://experienceleague.adobe.com/docs/experience-platform/tags/home.html" },
20
+ "cdn.actioniq.com": { "company": "ActionIQ", "service": "ActionIQ CDP Tag", "category": "tag_manager", "severity": "high", "docs_url": "https://www.actioniq.com/privacy-policy/" },
21
+ "cdn.amperity.com": { "company": "Amperity", "service": "Amperity CDP Tag", "category": "tag_manager", "severity": "high", "docs_url": "https://amperity.com/privacy/" },
22
+ "cdn.blueshift.com": { "company": "Blueshift", "service": "Blueshift CDP Tag", "category": "tag_manager", "severity": "high", "docs_url": "https://blueshift.com/privacy-policy/" },
23
+ "cdn.census.com": { "company": "Census", "service": "Census Reverse ETL CDN", "category": "tag_manager", "severity": "medium", "docs_url": "https://www.getcensus.com/privacy" },
24
+ "cdn.commandersact.com/js": { "company": "Commanders Act", "service": "Commanders Act TagCommander", "category": "tag_manager", "severity": "medium", "docs_url": "https://www.commandersact.com/en/privacy/" },
25
+ "cdn.exponea.com": { "company": "Bloomreach (Exponea)", "service": "Bloomreach CDP Tag", "category": "tag_manager", "severity": "high", "docs_url": "https://www.bloomreach.com/en/legal/privacy-policy" },
26
+ "cdn.freshpaint.io": { "company": "Freshpaint", "service": "Freshpaint Tag CDN", "category": "tag_manager", "severity": "high", "note": "Healthcare-compliant analytics proxy that replaces direct third-party tag loading", "docs_url": "https://www.freshpaint.io/privacy-policy" },
27
+ "cdn.hightouch.io": { "company": "Hightouch", "service": "Hightouch Events CDN", "category": "tag_manager", "severity": "medium", "docs_url": "https://hightouch.com/privacy-policy/" },
28
+ "cdn.lytics.io": { "company": "Lytics", "service": "Lytics CDP Tag", "category": "tag_manager", "severity": "high", "docs_url": "https://www.lytics.com/privacy/" },
29
+ "cdn.mparticle.com": { "company": "mParticle", "service": "mParticle SDK CDN", "category": "tag_manager", "severity": "high", "docs_url": "https://www.mparticle.com/privacy/" },
30
+ "cdn.ometria.com": { "company": "Ometria", "service": "Ometria CDP Tag", "category": "tag_manager", "severity": "high", "docs_url": "https://ometria.com/privacy-policy/" },
31
+ "cdn.piwikpro.com": { "company": "Piwik PRO", "service": "Piwik PRO Tag Manager", "category": "tag_manager", "severity": "medium", "docs_url": "https://piwik.pro/privacy-policy/" },
32
+ "cdn.qubit.com": { "company": "Coveo/Qubit", "service": "Qubit Tag Manager", "category": "tag_manager", "severity": "medium", "docs_url": "https://www.coveo.com/en/trust-center/privacy/privacy-notice" },
33
+ "cdn.rudderlabs.com": { "company": "RudderStack", "service": "RudderStack SDK CDN", "category": "tag_manager", "severity": "medium", "docs_url": "https://www.rudderstack.com/privacy-policy/" },
34
+ "cdn.segment.com": { "company": "Twilio", "service": "Segment Tag Manager", "category": "tag_manager", "severity": "high", "docs_url": "https://segment.com/legal/privacy/" },
35
+ "cdn.signal.co": { "company": "Signal (BrightTag)", "service": "Signal Tag Manager", "category": "tag_manager", "severity": "medium", "docs_url": "https://www.signal.co/privacy-policy/" },
36
+ "cdn.simondata.com": { "company": "Simon Data", "service": "Simon Data CDP Tag", "category": "tag_manager", "severity": "high", "docs_url": "https://www.simondata.com/privacy-policy/" },
37
+ "cdn.tagcommander.com": { "company": "Commanders Act", "service": "TagCommander", "category": "tag_manager", "severity": "medium", "docs_url": "https://www.commandersact.com/en/privacy/" },
38
+ "cdn.treasuredata.com": { "company": "Treasure Data", "service": "Treasure Data CDP Tag", "category": "tag_manager", "severity": "high", "docs_url": "https://www.treasuredata.com/privacy/" },
39
+ "cdn.zaius.com": { "company": "Optimizely (Zaius)", "service": "Zaius CDP Tag", "category": "tag_manager", "severity": "high", "docs_url": "https://www.optimizely.com/legal/privacy-policy/" },
40
+ "collect.tealiumiq.com": { "company": "Tealium", "service": "Tealium iQ", "category": "tag_manager", "severity": "high", "docs_url": "https://docs.tealium.com/platforms/javascript/" },
41
+ "ensighten.com": { "company": "Ensighten", "service": "Ensighten Tag Manager", "category": "tag_manager", "severity": "medium", "docs_url": "https://www.ensighten.com/privacy-policy/" },
42
+ "googletagmanager.com": { "company": "Google", "service": "Google Tag Manager", "category": "tag_manager", "severity": "medium", "note": "Container script host; severity depends on tags loaded inside the container", "docs_url": "https://developers.google.com/tag-platform/tag-manager" },
43
+ "identity.mparticle.com": { "company": "mParticle", "service": "mParticle Identity API", "category": "tag_manager", "severity": "high", "docs_url": "https://www.mparticle.com/privacy/" },
44
+ "js.hs-scripts.com": { "company": "HubSpot", "service": "HubSpot Tracking Code (Tag)", "category": "tag_manager", "severity": "high", "docs_url": "https://knowledge.hubspot.com/reports/what-cookies-does-hubspot-set-in-a-visitor-s-browser" },
45
+ "jssdks.mparticle.com": { "company": "mParticle", "service": "mParticle JS SDK", "category": "tag_manager", "severity": "high", "docs_url": "https://www.mparticle.com/privacy/" },
46
+ "matomo.cloud": { "company": "Matomo", "service": "Matomo Tag Manager", "category": "tag_manager", "severity": "low", "docs_url": "https://matomo.org/docs/tag-manager/" },
47
+ "nexus.ensighten.com": { "company": "Ensighten", "service": "Ensighten Nexus", "category": "tag_manager", "severity": "medium", "docs_url": "https://www.ensighten.com/privacy-policy/" },
48
+ "tagmanager.google.com": { "company": "Google", "service": "Google Tag Manager", "category": "tag_manager", "severity": "medium", "docs_url": "https://developers.google.com/tag-platform/tag-manager" },
49
+ "tags.tiqcdn.com": { "company": "Tealium", "service": "Tealium iQ", "category": "tag_manager", "severity": "medium", "docs_url": "https://docs.tealium.com/platforms/javascript/" },
50
+ "www.googletagmanager.com": { "company": "Google", "service": "Google Tag Manager", "category": "tag_manager", "severity": "medium", "note": "Container script host; severity depends on tags loaded inside the container", "docs_url": "https://developers.google.com/tag-platform/tag-manager" }
51
+ }
52
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @consenttheater/playbill
3
+ *
4
+ * The world's largest open-source GDPR tracker knowledge base.
5
+ * Every cookie, every domain, every actor on the web stage —
6
+ * identified, categorized, and scored.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { loadPlaybill, matchCookie, computeScore } from '@consenttheater/playbill';
11
+ *
12
+ * const playbill = await loadPlaybill('core');
13
+ * const actor = matchCookie(playbill, '_ga');
14
+ * ```
15
+ */
16
+ export type { Playbill, CookieActor, DomainActor, CookieMatch, DomainMatch, Tier, Severity, Category, BandKey, Band, Violation, ScoreResult } from './types.js';
17
+ export { matchCookie, matchDomain, isSameOrSubdomain, listCompanies, listCategories } from './matcher.js';
18
+ export { computeScore, bandForScore, SEVERITY_WEIGHTS, BANDS } from './scorer.js';
19
+ export type { ScoreInput, ObservedItem, ObservedBanner } from './scorer.js';
20
+ import type { Playbill, Tier } from './types.js';
21
+ /**
22
+ * Load a playbill tier by merging actor category files and filtering by severity.
23
+ *
24
+ * @param tier - 'mini' (~50 top companies, critical+high only),
25
+ * 'core' (all critical+high+medium),
26
+ * 'full' (everything)
27
+ */
28
+ export declare function loadPlaybill(tier?: Tier): Playbill;
29
+ /**
30
+ * Load only specific actor categories (e.g. just advertising + analytics).
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const db = loadActors(['advertising', 'analytics']);
35
+ * ```
36
+ */
37
+ export declare function loadActors(categories: string[]): Playbill;
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAChK,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAClF,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAA4B,MAAM,YAAY,CAAC;AAgF3E;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,GAAE,IAAa,GAAG,QAAQ,CAoB1D;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,QAAQ,CAgBzD"}
package/dist/index.js ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @consenttheater/playbill
3
+ *
4
+ * The world's largest open-source GDPR tracker knowledge base.
5
+ * Every cookie, every domain, every actor on the web stage —
6
+ * identified, categorized, and scored.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { loadPlaybill, matchCookie, computeScore } from '@consenttheater/playbill';
11
+ *
12
+ * const playbill = await loadPlaybill('core');
13
+ * const actor = matchCookie(playbill, '_ga');
14
+ * ```
15
+ */
16
+ export { matchCookie, matchDomain, isSameOrSubdomain, listCompanies, listCategories } from './matcher.js';
17
+ export { computeScore, bandForScore, SEVERITY_WEIGHTS, BANDS } from './scorer.js';
18
+ // All actor category files
19
+ import advertising from './actors/advertising.json' with { type: 'json' };
20
+ import analytics from './actors/analytics.json' with { type: 'json' };
21
+ import consent from './actors/consent.json' with { type: 'json' };
22
+ import dataLeak from './actors/data_leak.json' with { type: 'json' };
23
+ import fingerprinting from './actors/fingerprinting.json' with { type: 'json' };
24
+ import functional from './actors/functional.json' with { type: 'json' };
25
+ import marketing from './actors/marketing.json' with { type: 'json' };
26
+ import security from './actors/security.json' with { type: 'json' };
27
+ import sessionRecording from './actors/session_recording.json' with { type: 'json' };
28
+ import social from './actors/social.json' with { type: 'json' };
29
+ import tagManager from './actors/tag_manager.json' with { type: 'json' };
30
+ const ALL_ACTORS = [
31
+ advertising, analytics, consent, dataLeak, fingerprinting,
32
+ functional, marketing, security, sessionRecording, social, tagManager
33
+ ];
34
+ const TOP_N_COMPANIES = 50;
35
+ function mergeActors(actors) {
36
+ const cookies = {};
37
+ const domains = {};
38
+ for (const actor of actors) {
39
+ Object.assign(cookies, actor.cookies);
40
+ Object.assign(domains, actor.domains);
41
+ }
42
+ return { cookies, domains };
43
+ }
44
+ function getTopCompanies(cookies, domains, n) {
45
+ const counts = {};
46
+ for (const e of [...Object.values(cookies), ...Object.values(domains)]) {
47
+ counts[e.company] = (counts[e.company] || 0) + 1;
48
+ }
49
+ return new Set(Object.entries(counts)
50
+ .sort((a, b) => b[1] - a[1])
51
+ .slice(0, n)
52
+ .map(([company]) => company));
53
+ }
54
+ function uniqueCompanyCount(cookies, domains) {
55
+ const set = new Set();
56
+ for (const e of [...Object.values(cookies), ...Object.values(domains)])
57
+ set.add(e.company);
58
+ return set.size;
59
+ }
60
+ function filterEntries(entries, filterFn, topCompanies) {
61
+ const result = {};
62
+ for (const [key, entry] of Object.entries(entries)) {
63
+ if (filterFn(entry, topCompanies))
64
+ result[key] = entry;
65
+ }
66
+ return result;
67
+ }
68
+ const TIER_FILTERS = {
69
+ mini: (entry, topCompanies) => (entry.severity === 'critical' || entry.severity === 'high') &&
70
+ topCompanies.has(entry.company),
71
+ core: (entry) => entry.severity === 'critical' || entry.severity === 'high' || entry.severity === 'medium',
72
+ full: () => true
73
+ };
74
+ /**
75
+ * Load a playbill tier by merging actor category files and filtering by severity.
76
+ *
77
+ * @param tier - 'mini' (~50 top companies, critical+high only),
78
+ * 'core' (all critical+high+medium),
79
+ * 'full' (everything)
80
+ */
81
+ export function loadPlaybill(tier = 'full') {
82
+ const { cookies: allCookies, domains: allDomains } = mergeActors(ALL_ACTORS);
83
+ const topCompanies = getTopCompanies(allCookies, allDomains, TOP_N_COMPANIES);
84
+ const filter = TIER_FILTERS[tier];
85
+ const cookies = filterEntries(allCookies, filter, topCompanies);
86
+ const domains = filterEntries(allDomains, filter, topCompanies);
87
+ return {
88
+ version: 2,
89
+ tier,
90
+ generated: new Date().toISOString(),
91
+ stats: {
92
+ cookies: Object.keys(cookies).length,
93
+ domains: Object.keys(domains).length,
94
+ companies: uniqueCompanyCount(cookies, domains)
95
+ },
96
+ cookies,
97
+ domains
98
+ };
99
+ }
100
+ /**
101
+ * Load only specific actor categories (e.g. just advertising + analytics).
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * const db = loadActors(['advertising', 'analytics']);
106
+ * ```
107
+ */
108
+ export function loadActors(categories) {
109
+ const selected = ALL_ACTORS.filter(a => categories.includes(a.category));
110
+ const { cookies, domains } = mergeActors(selected);
111
+ return {
112
+ version: 2,
113
+ tier: 'full',
114
+ generated: new Date().toISOString(),
115
+ stats: {
116
+ cookies: Object.keys(cookies).length,
117
+ domains: Object.keys(domains).length,
118
+ companies: uniqueCompanyCount(cookies, domains)
119
+ },
120
+ cookies,
121
+ domains
122
+ };
123
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @consenttheater/playbill — Matcher
3
+ *
4
+ * Pure functions to identify actors (trackers) from the playbill by cookie name
5
+ * or domain hostname. No side effects, no browser API access.
6
+ */
7
+ import type { Playbill, CookieMatch, DomainMatch } from './types.js';
8
+ /**
9
+ * Match a cookie name against the playbill. Checks exact names first,
10
+ * then pattern entries (e.g. `_ga_*` matches `_ga_ABC123`).
11
+ */
12
+ export declare function matchCookie(playbill: Playbill | null | undefined, cookieName: string): CookieMatch | null;
13
+ /**
14
+ * Match a hostname against the playbill. Checks exact domain first,
15
+ * then subdomain matches (e.g. `www.google-analytics.com` matches `google-analytics.com`).
16
+ */
17
+ export declare function matchDomain(playbill: Playbill | null | undefined, hostname: string): DomainMatch | null;
18
+ /**
19
+ * Check if two hostnames are the same domain or subdomains of each other.
20
+ */
21
+ export declare function isSameOrSubdomain(hostname: string, baseHost: string): boolean;
22
+ /**
23
+ * List all unique companies in the playbill.
24
+ */
25
+ export declare function listCompanies(playbill: Playbill): string[];
26
+ /**
27
+ * List all unique categories in the playbill.
28
+ */
29
+ export declare function listCategories(playbill: Playbill): string[];
30
+ //# sourceMappingURL=matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAezG;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAavG;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK7E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAK1D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAK3D"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Match a cookie name against the playbill. Checks exact names first,
3
+ * then pattern entries (e.g. `_ga_*` matches `_ga_ABC123`).
4
+ */
5
+ export function matchCookie(playbill, cookieName) {
6
+ if (!playbill?.cookies || !cookieName)
7
+ return null;
8
+ const exact = playbill.cookies[cookieName];
9
+ if (exact)
10
+ return { ...exact, name: cookieName };
11
+ for (const key of Object.keys(playbill.cookies)) {
12
+ const entry = playbill.cookies[key];
13
+ if (!entry.pattern || !key.includes('*'))
14
+ continue;
15
+ const prefix = key.replace(/\*.*$/, '');
16
+ if (prefix && cookieName.startsWith(prefix)) {
17
+ return { ...entry, name: cookieName, matchedPattern: key };
18
+ }
19
+ }
20
+ return null;
21
+ }
22
+ /**
23
+ * Match a hostname against the playbill. Checks exact domain first,
24
+ * then subdomain matches (e.g. `www.google-analytics.com` matches `google-analytics.com`).
25
+ */
26
+ export function matchDomain(playbill, hostname) {
27
+ if (!playbill?.domains || !hostname)
28
+ return null;
29
+ const host = hostname.toLowerCase();
30
+ const exact = playbill.domains[host];
31
+ if (exact)
32
+ return { ...exact, hostname: host };
33
+ for (const key of Object.keys(playbill.domains)) {
34
+ if (host === key || host.endsWith('.' + key)) {
35
+ return { ...playbill.domains[key], hostname: host, matchedDomain: key };
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ /**
41
+ * Check if two hostnames are the same domain or subdomains of each other.
42
+ */
43
+ export function isSameOrSubdomain(hostname, baseHost) {
44
+ if (!hostname || !baseHost)
45
+ return false;
46
+ const h = hostname.toLowerCase();
47
+ const b = baseHost.toLowerCase();
48
+ return h === b || h.endsWith('.' + b) || b.endsWith('.' + h);
49
+ }
50
+ /**
51
+ * List all unique companies in the playbill.
52
+ */
53
+ export function listCompanies(playbill) {
54
+ const set = new Set();
55
+ for (const entry of Object.values(playbill.cookies))
56
+ set.add(entry.company);
57
+ for (const entry of Object.values(playbill.domains))
58
+ set.add(entry.company);
59
+ return [...set].sort();
60
+ }
61
+ /**
62
+ * List all unique categories in the playbill.
63
+ */
64
+ export function listCategories(playbill) {
65
+ const set = new Set();
66
+ for (const entry of Object.values(playbill.cookies))
67
+ set.add(entry.category);
68
+ for (const entry of Object.values(playbill.domains))
69
+ set.add(entry.category);
70
+ return [...set].sort();
71
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @consenttheater/playbill — Scorer
3
+ *
4
+ * Risk scoring — pure functions. Given observations (cookies, requests, banner),
5
+ * produce a compliance score with risk band and violation list.
6
+ *
7
+ * Bands: >= 90 Compliant, 70-89 At Risk, 40-69 Non-Compliant, < 40 Violating
8
+ * Weights: critical -25, high -15, medium -10, low -5
9
+ */
10
+ import type { Severity, Band, BandKey, ScoreResult } from './types.js';
11
+ export declare const SEVERITY_WEIGHTS: Record<Severity, number>;
12
+ export declare const BANDS: readonly {
13
+ min: number;
14
+ key: BandKey;
15
+ label: string;
16
+ }[];
17
+ export declare function bandForScore(score: number): Band;
18
+ export interface ObservedItem {
19
+ name?: string;
20
+ hostname?: string;
21
+ company?: string;
22
+ service?: string;
23
+ severity: Severity;
24
+ category?: string;
25
+ note?: string;
26
+ }
27
+ export interface ObservedBanner {
28
+ detected: boolean;
29
+ hasAcceptButton?: boolean;
30
+ hasRejectButton?: boolean;
31
+ hasManageButton?: boolean;
32
+ buttonCount?: number;
33
+ textPreview?: string;
34
+ }
35
+ export interface ScoreInput {
36
+ preConsentCookies?: ObservedItem[];
37
+ preConsentRequests?: ObservedItem[];
38
+ dataLeakRequests?: ObservedItem[];
39
+ banner?: ObservedBanner | null;
40
+ }
41
+ export declare function computeScore(input: ScoreInput): ScoreResult;
42
+ //# sourceMappingURL=scorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scorer.d.ts","sourceRoot":"","sources":["../src/scorer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAa,MAAM,YAAY,CAAC;AAElF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAKrD,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAKxE,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAKhD;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,iBAAiB,CAAC,EAAE,YAAY,EAAE,CAAC;IACnC,kBAAkB,CAAC,EAAE,YAAY,EAAE,CAAC;IACpC,gBAAgB,CAAC,EAAE,YAAY,EAAE,CAAC;IAClC,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;CAChC;AAUD,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAwE3D"}
package/dist/scorer.js ADDED
@@ -0,0 +1,97 @@
1
+ export const SEVERITY_WEIGHTS = {
2
+ critical: 25,
3
+ high: 15,
4
+ medium: 10,
5
+ low: 5
6
+ };
7
+ export const BANDS = [
8
+ { min: 90, key: 'compliant', label: 'Compliant' },
9
+ { min: 70, key: 'at_risk', label: 'At Risk' },
10
+ { min: 40, key: 'non_compliant', label: 'Non-Compliant' },
11
+ { min: 0, key: 'violating', label: 'Violating' }
12
+ ];
13
+ export function bandForScore(score) {
14
+ for (const b of BANDS) {
15
+ if (score >= b.min)
16
+ return { key: b.key, label: b.label };
17
+ }
18
+ return { key: 'violating', label: 'Violating' };
19
+ }
20
+ function bucketBySeverity(items) {
21
+ const out = { critical: [], high: [], medium: [], low: [] };
22
+ for (const it of items || []) {
23
+ if (out[it.severity])
24
+ out[it.severity].push(it);
25
+ }
26
+ return out;
27
+ }
28
+ export function computeScore(input) {
29
+ const violations = [];
30
+ const cookies = bucketBySeverity(input.preConsentCookies);
31
+ const reqs = bucketBySeverity(input.preConsentRequests);
32
+ if (cookies.critical.length) {
33
+ violations.push({
34
+ type: 'critical_cookies_before_consent', severity: 'critical', count: cookies.critical.length,
35
+ description: `${cookies.critical.length} advertising/tracking cookie(s) set before consent`,
36
+ items: cookies.critical.map(c => ({ name: c.name, company: c.company }))
37
+ });
38
+ }
39
+ if (cookies.high.length) {
40
+ violations.push({
41
+ type: 'high_cookies_before_consent', severity: 'high', count: cookies.high.length,
42
+ description: `${cookies.high.length} analytics cookie(s) set before consent`,
43
+ items: cookies.high.map(c => ({ name: c.name, company: c.company }))
44
+ });
45
+ }
46
+ if (cookies.medium.length) {
47
+ violations.push({
48
+ type: 'medium_cookies_before_consent', severity: 'medium', count: cookies.medium.length,
49
+ description: `${cookies.medium.length} tracking cookie(s) set before consent`,
50
+ items: cookies.medium.map(c => ({ name: c.name, company: c.company }))
51
+ });
52
+ }
53
+ if (reqs.critical.length) {
54
+ violations.push({
55
+ type: 'critical_requests_before_consent', severity: 'critical', count: reqs.critical.length,
56
+ description: `${reqs.critical.length} advertising request(s) fired before consent`,
57
+ items: reqs.critical.map(r => ({ hostname: r.hostname, company: r.company }))
58
+ });
59
+ }
60
+ if (reqs.high.length) {
61
+ violations.push({
62
+ type: 'high_requests_before_consent', severity: 'high', count: reqs.high.length,
63
+ description: `${reqs.high.length} analytics request(s) fired before consent`,
64
+ items: reqs.high.map(r => ({ hostname: r.hostname, company: r.company }))
65
+ });
66
+ }
67
+ const leaks = (input.dataLeakRequests || []).filter(r => r.category === 'data_leak');
68
+ if (leaks.length) {
69
+ violations.push({
70
+ type: 'data_leaks', severity: 'medium', count: leaks.length,
71
+ description: `${leaks.length} data-leak request(s) (IP exposure to third parties)`,
72
+ items: leaks.map(r => ({ hostname: r.hostname, company: r.company, note: r.note }))
73
+ });
74
+ }
75
+ const banner = input.banner;
76
+ if (banner?.detected) {
77
+ if (banner.hasAcceptButton && !banner.hasRejectButton) {
78
+ violations.push({
79
+ type: 'banner_missing_reject', severity: 'high', count: 1,
80
+ description: 'Consent banner has Accept but no Reject/Decline option'
81
+ });
82
+ }
83
+ }
84
+ else if (banner === null || (banner && !banner.detected)) {
85
+ if ((input.preConsentCookies || []).length || (input.preConsentRequests || []).length) {
86
+ violations.push({
87
+ type: 'no_banner_with_trackers', severity: 'high', count: 1,
88
+ description: 'No consent banner detected but trackers are present'
89
+ });
90
+ }
91
+ }
92
+ let score = 100;
93
+ for (const v of violations)
94
+ score -= (SEVERITY_WEIGHTS[v.severity] || 0);
95
+ score = Math.max(0, Math.min(100, score));
96
+ return { score, band: bandForScore(score), violations };
97
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @consenttheater/playbill — Type definitions
3
+ *
4
+ * The Playbill is a theater program that lists every actor (tracker) and their role
5
+ * (category/severity) on the web stage (website). These types define the shape of
6
+ * that program.
7
+ */
8
+ export type Severity = 'critical' | 'high' | 'medium' | 'low';
9
+ export type Category = 'advertising' | 'analytics' | 'marketing' | 'functional' | 'tag_manager' | 'data_leak' | 'social' | 'session_recording' | 'security' | 'consent' | 'fingerprinting';
10
+ export type Tier = 'mini' | 'core' | 'full';
11
+ /** A cookie actor in the playbill — a known tracking cookie signature. */
12
+ export interface CookieActor {
13
+ company: string;
14
+ service: string;
15
+ category: Category;
16
+ description?: string;
17
+ severity: Severity;
18
+ pattern?: boolean;
19
+ note?: string;
20
+ lifetime?: string;
21
+ docs_url?: string;
22
+ }
23
+ /** A domain actor in the playbill — a known tracking domain. */
24
+ export interface DomainActor {
25
+ company: string;
26
+ service: string;
27
+ category: Category;
28
+ severity: Severity;
29
+ note?: string;
30
+ docs_url?: string;
31
+ }
32
+ /** The Playbill — the complete cast of known trackers. */
33
+ export interface Playbill {
34
+ version: number;
35
+ tier: Tier;
36
+ generated: string;
37
+ stats: {
38
+ cookies: number;
39
+ domains: number;
40
+ companies: number;
41
+ };
42
+ cookies: Record<string, CookieActor>;
43
+ domains: Record<string, DomainActor>;
44
+ }
45
+ /** Result of matching a cookie name against the playbill. */
46
+ export interface CookieMatch extends CookieActor {
47
+ name: string;
48
+ matchedPattern?: string;
49
+ }
50
+ /** Result of matching a domain against the playbill. */
51
+ export interface DomainMatch extends DomainActor {
52
+ hostname: string;
53
+ matchedDomain?: string;
54
+ }
55
+ /** Risk band derived from the compliance score. */
56
+ export type BandKey = 'compliant' | 'at_risk' | 'non_compliant' | 'violating';
57
+ export interface Band {
58
+ key: BandKey;
59
+ label: string;
60
+ }
61
+ export interface Violation {
62
+ type: string;
63
+ severity: Severity;
64
+ count: number;
65
+ description: string;
66
+ items?: Array<{
67
+ name?: string;
68
+ hostname?: string;
69
+ company?: string;
70
+ note?: string;
71
+ }>;
72
+ }
73
+ export interface ScoreResult {
74
+ score: number;
75
+ band: Band;
76
+ violations: Violation[];
77
+ }
78
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE9D,MAAM,MAAM,QAAQ,GAChB,aAAa,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,GACxD,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,mBAAmB,GAC5D,UAAU,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAE9C,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,0DAA0D;AAC1D,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,IAAI,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACtC;AAED,6DAA6D;AAC7D,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wDAAwD;AACxD,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,mDAAmD;AACnD,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,SAAS,GAAG,eAAe,GAAG,WAAW,CAAC;AAE9E,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtF;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB"}
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @consenttheater/playbill — Type definitions
3
+ *
4
+ * The Playbill is a theater program that lists every actor (tracker) and their role
5
+ * (category/severity) on the web stage (website). These types define the shape of
6
+ * that program.
7
+ */
8
+ export {};