@ansvar/us-regulations-mcp 1.0.0 → 1.2.2
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 +422 -79
- package/data/regulations.db +0 -0
- package/data/seed/colorado-cpa.json +97 -0
- package/data/seed/ffiec.json +103 -0
- package/data/seed/mappings/ccpa-nist-csf.json +11 -1
- package/data/seed/mappings/hipaa-nist-800-53.json +10 -1
- package/data/seed/nydfs.json +122 -0
- package/data/seed/sox.json +109 -0
- package/dist/index.js +1 -1
- package/dist/ingest/adapters/colorado-public.d.ts +25 -0
- package/dist/ingest/adapters/colorado-public.d.ts.map +1 -0
- package/dist/ingest/adapters/colorado-public.js +76 -0
- package/dist/ingest/adapters/colorado-public.js.map +1 -0
- package/dist/ingest/adapters/connecticut-cga.d.ts +22 -0
- package/dist/ingest/adapters/connecticut-cga.d.ts.map +1 -0
- package/dist/ingest/adapters/connecticut-cga.js +116 -0
- package/dist/ingest/adapters/connecticut-cga.js.map +1 -0
- package/dist/ingest/adapters/ecfr.d.ts +46 -4
- package/dist/ingest/adapters/ecfr.d.ts.map +1 -1
- package/dist/ingest/adapters/ecfr.js +131 -16
- package/dist/ingest/adapters/ecfr.js.map +1 -1
- package/dist/ingest/adapters/ffiec.d.ts +42 -0
- package/dist/ingest/adapters/ffiec.d.ts.map +1 -0
- package/dist/ingest/adapters/ffiec.js +68 -0
- package/dist/ingest/adapters/ffiec.js.map +1 -0
- package/dist/ingest/adapters/nydfs.d.ts +42 -0
- package/dist/ingest/adapters/nydfs.d.ts.map +1 -0
- package/dist/ingest/adapters/nydfs.js +68 -0
- package/dist/ingest/adapters/nydfs.js.map +1 -0
- package/dist/ingest/adapters/regulations-gov.d.ts +11 -12
- package/dist/ingest/adapters/regulations-gov.d.ts.map +1 -1
- package/dist/ingest/adapters/regulations-gov.js +46 -43
- package/dist/ingest/adapters/regulations-gov.js.map +1 -1
- package/dist/ingest/adapters/utah-xcode.d.ts +19 -0
- package/dist/ingest/adapters/utah-xcode.d.ts.map +1 -0
- package/dist/ingest/adapters/utah-xcode.js +112 -0
- package/dist/ingest/adapters/utah-xcode.js.map +1 -0
- package/dist/ingest/adapters/virginia-law.d.ts +21 -0
- package/dist/ingest/adapters/virginia-law.d.ts.map +1 -0
- package/dist/ingest/adapters/virginia-law.js +111 -0
- package/dist/ingest/adapters/virginia-law.js.map +1 -0
- package/package.json +27 -5
- package/scripts/build-db.ts +50 -32
- package/scripts/check-updates.ts +184 -0
- package/scripts/ingest.ts +72 -25
- package/src/index.ts +1 -1
- package/src/ingest/adapters/colorado-public.ts +96 -0
- package/src/ingest/adapters/connecticut-cga.ts +150 -0
- package/src/ingest/adapters/ecfr.ts +158 -17
- package/src/ingest/adapters/ffiec.ts +77 -0
- package/src/ingest/adapters/nydfs.ts +77 -0
- package/src/ingest/adapters/regulations-gov.ts +48 -47
- package/src/ingest/adapters/utah-xcode.ts +143 -0
- package/src/ingest/adapters/virginia-law.ts +140 -0
- package/scripts/quality-test.ts +0 -346
- package/scripts/test-mcp-tools.ts +0 -187
- package/scripts/test-remaining-tools.ts +0 -107
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connecticut General Assembly Adapter
|
|
3
|
+
*
|
|
4
|
+
* Fetches Connecticut CTDPA from Connecticut General Assembly website.
|
|
5
|
+
* Source: Conn. Gen. Stat. § 42-515 to 42-524
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This adapter parses a SINGLE page containing ALL sections,
|
|
8
|
+
* unlike Virginia/Colorado which fetch individual section pages.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
SourceAdapter,
|
|
13
|
+
RegulationMetadata,
|
|
14
|
+
Section,
|
|
15
|
+
Definition,
|
|
16
|
+
UpdateStatus,
|
|
17
|
+
} from '../framework.js';
|
|
18
|
+
import * as cheerio from 'cheerio';
|
|
19
|
+
import https from 'https';
|
|
20
|
+
|
|
21
|
+
class ScrapingError extends Error {
|
|
22
|
+
constructor(message: string) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'ScrapingError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class ConnecticutCGAAdapter implements SourceAdapter {
|
|
29
|
+
private readonly regulationId = 'CONNECTICUT_CTDPA';
|
|
30
|
+
private readonly chapterUrl =
|
|
31
|
+
'https://www.cga.ct.gov/current/pub/chap_743jj.htm';
|
|
32
|
+
|
|
33
|
+
async fetchMetadata(): Promise<RegulationMetadata> {
|
|
34
|
+
return {
|
|
35
|
+
id: 'CONNECTICUT_CTDPA',
|
|
36
|
+
full_name: 'Connecticut Data Privacy Act',
|
|
37
|
+
citation: 'Conn. Gen. Stat. §42-515 to 42-524',
|
|
38
|
+
effective_date: '2023-07-01',
|
|
39
|
+
last_amended: '2023-07-01',
|
|
40
|
+
source_url: this.chapterUrl,
|
|
41
|
+
jurisdiction: 'connecticut',
|
|
42
|
+
regulation_type: 'statute',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async *fetchSections(): AsyncGenerator<Section[]> {
|
|
47
|
+
console.log('Fetching Connecticut CTDPA from single chapter page...');
|
|
48
|
+
|
|
49
|
+
// Connecticut's certificate has SSL issues, use https module directly
|
|
50
|
+
const html = await this.fetchWithHttps(this.chapterUrl);
|
|
51
|
+
const $ = cheerio.load(html);
|
|
52
|
+
|
|
53
|
+
const sections = this.parseSections($);
|
|
54
|
+
|
|
55
|
+
if (sections.length < 8) {
|
|
56
|
+
throw new ScrapingError(
|
|
57
|
+
`Expected at least 8 sections, got ${sections.length}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(` ✅ Parsed ${sections.length} sections from chapter page`);
|
|
62
|
+
yield sections;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async fetchWithHttps(url: string): Promise<string> {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
https.get(url, { rejectUnauthorized: false }, (res) => {
|
|
68
|
+
if (res.statusCode !== 200) {
|
|
69
|
+
reject(new ScrapingError(`HTTP ${res.statusCode}`));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let data = '';
|
|
74
|
+
res.on('data', (chunk) => data += chunk);
|
|
75
|
+
res.on('end', () => resolve(data));
|
|
76
|
+
res.on('error', reject);
|
|
77
|
+
}).on('error', reject);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private parseSections($: cheerio.Root): Section[] {
|
|
82
|
+
const sections: Section[] = [];
|
|
83
|
+
|
|
84
|
+
// Find all paragraphs containing section headers (span.catchln)
|
|
85
|
+
$('span.catchln[id^="sec_42-"]').each((_idx, elem) => {
|
|
86
|
+
const $span = $(elem);
|
|
87
|
+
const id = $span.attr('id');
|
|
88
|
+
|
|
89
|
+
if (!id) return;
|
|
90
|
+
|
|
91
|
+
// Extract section number from id: "sec_42-515" -> "515"
|
|
92
|
+
const idMatch = id.match(/sec_42-(\d+)/);
|
|
93
|
+
if (!idMatch) return;
|
|
94
|
+
|
|
95
|
+
const sectionNum = idMatch[1];
|
|
96
|
+
|
|
97
|
+
// Extract title from span text: "Sec. 42-515. Definitions." -> "Definitions"
|
|
98
|
+
const spanText = $span.text().trim();
|
|
99
|
+
const titleMatch = spanText.match(/^Sec\.\s+42-\d+\.\s+(.+?)\.?\s*$/);
|
|
100
|
+
const title = titleMatch ? titleMatch[1].trim() : `Section 42-${sectionNum}`;
|
|
101
|
+
|
|
102
|
+
// Get the parent paragraph and all following paragraphs until next section
|
|
103
|
+
const $startPara = $span.parent();
|
|
104
|
+
let fullText = $startPara.text().trim();
|
|
105
|
+
|
|
106
|
+
// Collect following paragraphs until we hit another section marker
|
|
107
|
+
let $next = $startPara.next('p');
|
|
108
|
+
while ($next.length > 0) {
|
|
109
|
+
// Stop if this paragraph contains another section marker
|
|
110
|
+
if ($next.find('span.catchln[id^="sec_42-"]').length > 0) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const nextText = $next.text().trim();
|
|
115
|
+
if (nextText.length > 0) {
|
|
116
|
+
fullText += '\n\n' + nextText;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
$next = $next.next('p');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Only include sections in the 42-515 to 42-526 range (CTDPA)
|
|
123
|
+
const num = parseInt(sectionNum);
|
|
124
|
+
if (num >= 515 && num <= 526) {
|
|
125
|
+
sections.push({
|
|
126
|
+
sectionNumber: `42-${sectionNum}`,
|
|
127
|
+
title: title,
|
|
128
|
+
text: fullText,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
console.log(` Found § 42-${sectionNum}: ${title}`);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return sections;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async extractDefinitions(): Promise<Definition[]> {
|
|
139
|
+
// Definitions are in section 42-515, could be extracted if needed
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async checkForUpdates(lastFetched: Date): Promise<UpdateStatus> {
|
|
144
|
+
return { hasChanges: false };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function createConnecticutAdapter(): SourceAdapter {
|
|
149
|
+
return new ConnecticutCGAAdapter();
|
|
150
|
+
}
|
|
@@ -17,6 +17,17 @@ import {
|
|
|
17
17
|
} from '../framework.js';
|
|
18
18
|
import { XMLParser } from 'fast-xml-parser';
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Configuration for regulation metadata fallback
|
|
22
|
+
*/
|
|
23
|
+
export interface RegulationConfig {
|
|
24
|
+
full_name: string;
|
|
25
|
+
citation: string;
|
|
26
|
+
effective_date: string;
|
|
27
|
+
jurisdiction: 'federal' | 'state';
|
|
28
|
+
regulation_type: 'rule' | 'statute';
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
/**
|
|
21
32
|
* Adapter for fetching HIPAA from eCFR API
|
|
22
33
|
*/
|
|
@@ -24,31 +35,85 @@ export class EcfrAdapter implements SourceAdapter {
|
|
|
24
35
|
private readonly regulationId: string;
|
|
25
36
|
private readonly cfr_title: number;
|
|
26
37
|
private readonly cfr_parts: number[];
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
private readonly fallbackMetadata: RegulationConfig;
|
|
39
|
+
|
|
40
|
+
constructor(
|
|
41
|
+
regulationId: string,
|
|
42
|
+
cfr_title: number,
|
|
43
|
+
cfr_parts: number[],
|
|
44
|
+
fallbackMetadata: RegulationConfig
|
|
45
|
+
) {
|
|
29
46
|
this.regulationId = regulationId;
|
|
30
47
|
this.cfr_title = cfr_title;
|
|
31
48
|
this.cfr_parts = cfr_parts;
|
|
49
|
+
this.fallbackMetadata = fallbackMetadata;
|
|
32
50
|
}
|
|
33
51
|
|
|
34
52
|
/**
|
|
35
|
-
* Fetch
|
|
53
|
+
* Fetch metadata from eCFR API
|
|
54
|
+
* Returns effective_date and last_amended from API with fallback metadata for other fields
|
|
55
|
+
*/
|
|
56
|
+
private async fetchMetadataFromAPI(): Promise<RegulationMetadata> {
|
|
57
|
+
try {
|
|
58
|
+
// Get latest available date for this CFR title
|
|
59
|
+
const latestDate = await this.getLatestDate();
|
|
60
|
+
|
|
61
|
+
// Fetch structure JSON (lighter than full XML)
|
|
62
|
+
const url = `https://www.ecfr.gov/api/versioner/v1/structure/${latestDate}/title-${this.cfr_title}.json`;
|
|
63
|
+
const response = await fetch(url);
|
|
64
|
+
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(`eCFR API returned ${response.status} for ${url}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
|
|
71
|
+
// Extract dates from API response
|
|
72
|
+
// Note: eCFR structure API may not have these exact fields, using latest date as proxy
|
|
73
|
+
const effectiveDate = data.effective_date || latestDate;
|
|
74
|
+
const lastAmended = data.last_amended || latestDate;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
id: this.regulationId,
|
|
78
|
+
full_name: this.fallbackMetadata.full_name,
|
|
79
|
+
citation: this.fallbackMetadata.citation,
|
|
80
|
+
effective_date: effectiveDate,
|
|
81
|
+
last_amended: lastAmended,
|
|
82
|
+
source_url: `https://www.ecfr.gov/current/title-${this.cfr_title}`,
|
|
83
|
+
jurisdiction: this.fallbackMetadata.jurisdiction,
|
|
84
|
+
regulation_type: this.fallbackMetadata.regulation_type
|
|
85
|
+
};
|
|
86
|
+
} catch (error) {
|
|
87
|
+
// Re-throw to be caught by fetchMetadata
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fetch regulation metadata
|
|
36
94
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
95
|
+
* Hybrid approach:
|
|
96
|
+
* 1. Try to fetch effective_date and last_amended from eCFR API
|
|
97
|
+
* 2. Fall back to hardcoded metadata if API unavailable
|
|
39
98
|
*/
|
|
40
99
|
async fetchMetadata(): Promise<RegulationMetadata> {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
100
|
+
try {
|
|
101
|
+
return await this.fetchMetadataFromAPI();
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.warn(`⚠️ eCFR API unavailable for ${this.regulationId}, using fallback metadata`);
|
|
104
|
+
console.warn(` Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
id: this.regulationId,
|
|
108
|
+
full_name: this.fallbackMetadata.full_name,
|
|
109
|
+
citation: this.fallbackMetadata.citation,
|
|
110
|
+
effective_date: this.fallbackMetadata.effective_date,
|
|
111
|
+
last_amended: this.fallbackMetadata.effective_date, // Use effective_date as fallback
|
|
112
|
+
source_url: `https://www.ecfr.gov/current/title-${this.cfr_title}`,
|
|
113
|
+
jurisdiction: this.fallbackMetadata.jurisdiction,
|
|
114
|
+
regulation_type: this.fallbackMetadata.regulation_type
|
|
115
|
+
};
|
|
116
|
+
}
|
|
52
117
|
}
|
|
53
118
|
|
|
54
119
|
/**
|
|
@@ -399,5 +464,81 @@ export class EcfrAdapter implements SourceAdapter {
|
|
|
399
464
|
* Factory function to create HIPAA adapter
|
|
400
465
|
*/
|
|
401
466
|
export function createHipaaAdapter(): EcfrAdapter {
|
|
402
|
-
return new EcfrAdapter('HIPAA', 45, [160, 162, 164]
|
|
467
|
+
return new EcfrAdapter('HIPAA', 45, [160, 162, 164], {
|
|
468
|
+
full_name: 'Health Insurance Portability and Accountability Act',
|
|
469
|
+
citation: '45 CFR Parts 160, 162, 164',
|
|
470
|
+
effective_date: '2003-04-14',
|
|
471
|
+
jurisdiction: 'federal',
|
|
472
|
+
regulation_type: 'rule'
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Factory function to create GLBA adapter
|
|
478
|
+
* Gramm-Leach-Bliley Act - Safeguards Rule
|
|
479
|
+
*/
|
|
480
|
+
export function createGlbaAdapter(): EcfrAdapter {
|
|
481
|
+
return new EcfrAdapter('GLBA', 16, [314], {
|
|
482
|
+
full_name: 'Gramm-Leach-Bliley Act - Safeguards Rule',
|
|
483
|
+
citation: '16 CFR Part 314',
|
|
484
|
+
effective_date: '2003-05-23',
|
|
485
|
+
jurisdiction: 'federal',
|
|
486
|
+
regulation_type: 'rule'
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Factory function to create FERPA adapter
|
|
492
|
+
* Family Educational Rights and Privacy Act
|
|
493
|
+
*/
|
|
494
|
+
export function createFerpaAdapter(): EcfrAdapter {
|
|
495
|
+
return new EcfrAdapter('FERPA', 34, [99], {
|
|
496
|
+
full_name: 'Family Educational Rights and Privacy Act',
|
|
497
|
+
citation: '34 CFR Part 99',
|
|
498
|
+
effective_date: '2009-01-03',
|
|
499
|
+
jurisdiction: 'federal',
|
|
500
|
+
regulation_type: 'rule'
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Factory function to create COPPA adapter
|
|
506
|
+
* Children's Online Privacy Protection Act Rule
|
|
507
|
+
*/
|
|
508
|
+
export function createCoppaAdapter(): EcfrAdapter {
|
|
509
|
+
return new EcfrAdapter('COPPA', 16, [312], {
|
|
510
|
+
full_name: "Children's Online Privacy Protection Act Rule",
|
|
511
|
+
citation: '16 CFR Part 312',
|
|
512
|
+
effective_date: '2013-07-01',
|
|
513
|
+
jurisdiction: 'federal',
|
|
514
|
+
regulation_type: 'rule'
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Factory function to create FDA 21 CFR Part 11 adapter
|
|
520
|
+
* Electronic Records and Electronic Signatures
|
|
521
|
+
*/
|
|
522
|
+
export function createFdaAdapter(): EcfrAdapter {
|
|
523
|
+
return new EcfrAdapter('FDA_CFR_11', 21, [11], {
|
|
524
|
+
full_name: 'FDA Electronic Records and Electronic Signatures',
|
|
525
|
+
citation: '21 CFR Part 11',
|
|
526
|
+
effective_date: '1997-08-20',
|
|
527
|
+
jurisdiction: 'federal',
|
|
528
|
+
regulation_type: 'rule'
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Factory function to create EPA RMP adapter
|
|
534
|
+
* Risk Management Plan Rule
|
|
535
|
+
*/
|
|
536
|
+
export function createEpaRmpAdapter(): EcfrAdapter {
|
|
537
|
+
return new EcfrAdapter('EPA_RMP', 40, [68], {
|
|
538
|
+
full_name: 'EPA Risk Management Plan Rule',
|
|
539
|
+
citation: '40 CFR Part 68',
|
|
540
|
+
effective_date: '2017-01-13',
|
|
541
|
+
jurisdiction: 'federal',
|
|
542
|
+
regulation_type: 'rule'
|
|
543
|
+
});
|
|
403
544
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFIEC Adapter - Seed-based for v1.1
|
|
3
|
+
* TODO: Implement PDF scraping for automated updates
|
|
4
|
+
*
|
|
5
|
+
* Source: FFIEC IT Examination Handbook
|
|
6
|
+
* URL: https://www.ffiec.gov/examination.htm
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { SourceAdapter, RegulationMetadata, Section, Definition, UpdateStatus } from '../framework.js';
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Seed-based adapter for FFIEC Guidelines
|
|
19
|
+
*
|
|
20
|
+
* Future enhancement: Implement PDF scraping from ffiec.gov
|
|
21
|
+
* The IT Examination Handbook is published as a collection of PDF booklets
|
|
22
|
+
*/
|
|
23
|
+
export class FfiecAdapter implements SourceAdapter {
|
|
24
|
+
private seedPath: string;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
this.seedPath = join(__dirname, '../../../data/seed/ffiec.json');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Fetch regulation metadata from seed file
|
|
32
|
+
*/
|
|
33
|
+
async fetchMetadata(): Promise<RegulationMetadata> {
|
|
34
|
+
const seed = JSON.parse(readFileSync(this.seedPath, 'utf-8'));
|
|
35
|
+
return {
|
|
36
|
+
...seed.regulation,
|
|
37
|
+
source_url: 'https://www.ffiec.gov/examination.htm',
|
|
38
|
+
last_amended: seed.regulation.effective_date
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Fetch sections from seed file
|
|
44
|
+
* Returns all sections in a single batch
|
|
45
|
+
*/
|
|
46
|
+
async *fetchSections(): AsyncGenerator<Section[]> {
|
|
47
|
+
const seed = JSON.parse(readFileSync(this.seedPath, 'utf-8'));
|
|
48
|
+
yield seed.sections;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extract definitions (not implemented for seed-based adapter)
|
|
53
|
+
* Future: Extract from PDF glossary sections
|
|
54
|
+
*/
|
|
55
|
+
async extractDefinitions(): Promise<Definition[]> {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check for updates (not implemented for seed-based adapter)
|
|
61
|
+
* Future: Check PDF publication dates on ffiec.gov
|
|
62
|
+
*/
|
|
63
|
+
async checkForUpdates(lastFetched: Date): Promise<UpdateStatus> {
|
|
64
|
+
return {
|
|
65
|
+
hasChanges: false,
|
|
66
|
+
lastModified: new Date(),
|
|
67
|
+
changes: []
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Factory function to create FFIEC adapter
|
|
74
|
+
*/
|
|
75
|
+
export function createFfiecAdapter(): FfiecAdapter {
|
|
76
|
+
return new FfiecAdapter();
|
|
77
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NYDFS Adapter - Seed-based for v1.1
|
|
3
|
+
* TODO: Implement HTML scraping for automated updates
|
|
4
|
+
*
|
|
5
|
+
* Source: NY DFS Cybersecurity Regulation (23 NYCRR 500)
|
|
6
|
+
* URL: https://www.dfs.ny.gov/industry_guidance/cybersecurity
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { SourceAdapter, RegulationMetadata, Section, Definition, UpdateStatus } from '../framework.js';
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Seed-based adapter for NYDFS 23 NYCRR 500
|
|
19
|
+
*
|
|
20
|
+
* Future enhancement: Implement HTML scraping from NY DFS website
|
|
21
|
+
* The regulation is published online at dfs.ny.gov
|
|
22
|
+
*/
|
|
23
|
+
export class NydfsAdapter implements SourceAdapter {
|
|
24
|
+
private seedPath: string;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
this.seedPath = join(__dirname, '../../../data/seed/nydfs.json');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Fetch regulation metadata from seed file
|
|
32
|
+
*/
|
|
33
|
+
async fetchMetadata(): Promise<RegulationMetadata> {
|
|
34
|
+
const seed = JSON.parse(readFileSync(this.seedPath, 'utf-8'));
|
|
35
|
+
return {
|
|
36
|
+
...seed.regulation,
|
|
37
|
+
source_url: 'https://www.dfs.ny.gov/industry_guidance/cybersecurity',
|
|
38
|
+
last_amended: seed.regulation.effective_date
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Fetch sections from seed file
|
|
44
|
+
* Returns all sections in a single batch
|
|
45
|
+
*/
|
|
46
|
+
async *fetchSections(): AsyncGenerator<Section[]> {
|
|
47
|
+
const seed = JSON.parse(readFileSync(this.seedPath, 'utf-8'));
|
|
48
|
+
yield seed.sections;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extract definitions (not implemented for seed-based adapter)
|
|
53
|
+
* Future: Extract from 500.01 Definitions section
|
|
54
|
+
*/
|
|
55
|
+
async extractDefinitions(): Promise<Definition[]> {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check for updates (not implemented for seed-based adapter)
|
|
61
|
+
* Future: Check last-modified headers from NY DFS website
|
|
62
|
+
*/
|
|
63
|
+
async checkForUpdates(lastFetched: Date): Promise<UpdateStatus> {
|
|
64
|
+
return {
|
|
65
|
+
hasChanges: false,
|
|
66
|
+
lastModified: new Date(),
|
|
67
|
+
changes: []
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Factory function to create NYDFS adapter
|
|
74
|
+
*/
|
|
75
|
+
export function createNydfsAdapter(): NydfsAdapter {
|
|
76
|
+
return new NydfsAdapter();
|
|
77
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SOX Adapter
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Provides Sarbanes-Oxley Act compliance content including:
|
|
5
|
+
* - SOX statute sections (15 U.S.C., 18 U.S.C.)
|
|
6
|
+
* - SEC implementing regulations (17 CFR)
|
|
7
|
+
* - PCAOB auditing standards
|
|
8
|
+
* - IT General Controls guidance
|
|
6
9
|
*
|
|
7
10
|
* PRODUCTION IMPLEMENTATION
|
|
8
|
-
* Uses
|
|
11
|
+
* Uses comprehensive seed data verified against official sources
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
import {
|
|
@@ -15,22 +18,25 @@ import {
|
|
|
15
18
|
Definition,
|
|
16
19
|
UpdateStatus,
|
|
17
20
|
} from '../framework.js';
|
|
18
|
-
import
|
|
19
|
-
import
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
import { fileURLToPath } from 'url';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = path.dirname(__filename);
|
|
20
27
|
|
|
21
28
|
/**
|
|
22
|
-
* Adapter for
|
|
29
|
+
* Adapter for SOX compliance content
|
|
23
30
|
*
|
|
24
|
-
*
|
|
31
|
+
* Provides comprehensive SOX coverage including statute, SEC rules, and PCAOB standards
|
|
25
32
|
*/
|
|
26
33
|
export class SoxAdapter implements SourceAdapter {
|
|
27
34
|
private readonly regulationId: string;
|
|
28
|
-
private readonly
|
|
35
|
+
private readonly seedPath: string;
|
|
29
36
|
|
|
30
37
|
constructor(regulationId: string) {
|
|
31
38
|
this.regulationId = regulationId;
|
|
32
|
-
|
|
33
|
-
this.ecfrAdapter = new EcfrAdapter('SOX-SEC', 17, [229, 240]);
|
|
39
|
+
this.seedPath = path.join(__dirname, '../../../data/seed/sox.json');
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
/**
|
|
@@ -39,68 +45,63 @@ export class SoxAdapter implements SourceAdapter {
|
|
|
39
45
|
async fetchMetadata(): Promise<RegulationMetadata> {
|
|
40
46
|
return {
|
|
41
47
|
id: this.regulationId,
|
|
42
|
-
full_name: 'Sarbanes-Oxley Act
|
|
43
|
-
citation: '
|
|
44
|
-
effective_date: '
|
|
45
|
-
last_amended:
|
|
46
|
-
source_url: 'https://www.
|
|
48
|
+
full_name: 'Sarbanes-Oxley Act of 2002',
|
|
49
|
+
citation: 'Pub.L. 107-204, 15 U.S.C. §§ 7201-7266, 17 CFR Parts 229, 240',
|
|
50
|
+
effective_date: '2002-07-30',
|
|
51
|
+
last_amended: '2023-01-01',
|
|
52
|
+
source_url: 'https://www.sec.gov/spotlight/sarbanes-oxley.htm',
|
|
47
53
|
jurisdiction: 'federal',
|
|
48
|
-
regulation_type: '
|
|
54
|
+
regulation_type: 'statute',
|
|
49
55
|
};
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
/**
|
|
53
|
-
* Fetch all SOX
|
|
59
|
+
* Fetch all SOX sections from seed data
|
|
54
60
|
*
|
|
55
|
-
*
|
|
61
|
+
* Includes statute sections, SEC implementing rules, and PCAOB standards
|
|
56
62
|
*/
|
|
57
63
|
async *fetchSections(): AsyncGenerator<Section[]> {
|
|
58
|
-
console.log('
|
|
64
|
+
console.log('Loading SOX sections from seed data...');
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const seedData = JSON.parse(fs.readFileSync(this.seedPath, 'utf-8'));
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// - 17 CFR 240.15d-15 (Controls and procedures)
|
|
64
|
-
// - 17 CFR 240.13a-14 (Certifications)
|
|
65
|
-
// - 17 CFR 240.15d-14 (Certifications)
|
|
69
|
+
if (!seedData.sections || !Array.isArray(seedData.sections)) {
|
|
70
|
+
throw new Error('Invalid seed data format');
|
|
71
|
+
}
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
];
|
|
73
|
+
const sections: Section[] = seedData.sections.map((s: any) => ({
|
|
74
|
+
sectionNumber: s.sectionNumber,
|
|
75
|
+
title: s.title,
|
|
76
|
+
text: s.text,
|
|
77
|
+
chapter: s.chapter || 'Sarbanes-Oxley Act',
|
|
78
|
+
}));
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
for await (const sectionBatch of this.ecfrAdapter.fetchSections()) {
|
|
77
|
-
// Filter to SOX-relevant sections
|
|
78
|
-
const filtered = sectionBatch.filter(section =>
|
|
79
|
-
relevantSections.some(relevant => section.sectionNumber.includes(relevant))
|
|
80
|
-
);
|
|
80
|
+
console.log(` Loaded ${sections.length} SOX sections from seed data`);
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
yield sections;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Failed to load SOX seed data:', error);
|
|
85
|
+
throw error;
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
/**
|
|
89
90
|
* Check for updates since last fetch
|
|
90
|
-
*
|
|
91
|
-
* Delegates to eCFR adapter for update checking
|
|
92
91
|
*/
|
|
93
92
|
async checkForUpdates(lastFetched: Date): Promise<UpdateStatus> {
|
|
94
|
-
|
|
93
|
+
// SOX statute is stable; SEC rules update periodically
|
|
94
|
+
return {
|
|
95
|
+
hasChanges: false,
|
|
96
|
+
changes: ['SOX uses verified seed data. Check SEC.gov for regulatory updates.']
|
|
97
|
+
};
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
/**
|
|
98
101
|
* Extract definitions from SOX sections
|
|
99
|
-
*
|
|
100
|
-
* Future enhancement: Parse definitions from SEC regulations
|
|
101
102
|
*/
|
|
102
103
|
async extractDefinitions(): Promise<Definition[]> {
|
|
103
|
-
return
|
|
104
|
+
return [];
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
|