@ansvar/us-regulations-mcp 1.0.0 → 1.2.1

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.
Files changed (57) hide show
  1. package/README.md +422 -79
  2. package/data/regulations.db +0 -0
  3. package/data/seed/colorado-cpa.json +97 -0
  4. package/data/seed/ffiec.json +103 -0
  5. package/data/seed/mappings/ccpa-nist-csf.json +11 -1
  6. package/data/seed/mappings/hipaa-nist-800-53.json +10 -1
  7. package/data/seed/nydfs.json +122 -0
  8. package/data/seed/sox.json +109 -0
  9. package/dist/index.js +1 -1
  10. package/dist/ingest/adapters/colorado-public.d.ts +25 -0
  11. package/dist/ingest/adapters/colorado-public.d.ts.map +1 -0
  12. package/dist/ingest/adapters/colorado-public.js +76 -0
  13. package/dist/ingest/adapters/colorado-public.js.map +1 -0
  14. package/dist/ingest/adapters/connecticut-cga.d.ts +22 -0
  15. package/dist/ingest/adapters/connecticut-cga.d.ts.map +1 -0
  16. package/dist/ingest/adapters/connecticut-cga.js +116 -0
  17. package/dist/ingest/adapters/connecticut-cga.js.map +1 -0
  18. package/dist/ingest/adapters/ecfr.d.ts +46 -4
  19. package/dist/ingest/adapters/ecfr.d.ts.map +1 -1
  20. package/dist/ingest/adapters/ecfr.js +131 -16
  21. package/dist/ingest/adapters/ecfr.js.map +1 -1
  22. package/dist/ingest/adapters/ffiec.d.ts +42 -0
  23. package/dist/ingest/adapters/ffiec.d.ts.map +1 -0
  24. package/dist/ingest/adapters/ffiec.js +68 -0
  25. package/dist/ingest/adapters/ffiec.js.map +1 -0
  26. package/dist/ingest/adapters/nydfs.d.ts +42 -0
  27. package/dist/ingest/adapters/nydfs.d.ts.map +1 -0
  28. package/dist/ingest/adapters/nydfs.js +68 -0
  29. package/dist/ingest/adapters/nydfs.js.map +1 -0
  30. package/dist/ingest/adapters/regulations-gov.d.ts +11 -12
  31. package/dist/ingest/adapters/regulations-gov.d.ts.map +1 -1
  32. package/dist/ingest/adapters/regulations-gov.js +46 -43
  33. package/dist/ingest/adapters/regulations-gov.js.map +1 -1
  34. package/dist/ingest/adapters/utah-xcode.d.ts +19 -0
  35. package/dist/ingest/adapters/utah-xcode.d.ts.map +1 -0
  36. package/dist/ingest/adapters/utah-xcode.js +112 -0
  37. package/dist/ingest/adapters/utah-xcode.js.map +1 -0
  38. package/dist/ingest/adapters/virginia-law.d.ts +21 -0
  39. package/dist/ingest/adapters/virginia-law.d.ts.map +1 -0
  40. package/dist/ingest/adapters/virginia-law.js +111 -0
  41. package/dist/ingest/adapters/virginia-law.js.map +1 -0
  42. package/package.json +26 -4
  43. package/scripts/build-db.ts +50 -32
  44. package/scripts/check-updates.ts +184 -0
  45. package/scripts/ingest.ts +72 -25
  46. package/src/index.ts +1 -1
  47. package/src/ingest/adapters/colorado-public.ts +96 -0
  48. package/src/ingest/adapters/connecticut-cga.ts +150 -0
  49. package/src/ingest/adapters/ecfr.ts +158 -17
  50. package/src/ingest/adapters/ffiec.ts +77 -0
  51. package/src/ingest/adapters/nydfs.ts +77 -0
  52. package/src/ingest/adapters/regulations-gov.ts +48 -47
  53. package/src/ingest/adapters/utah-xcode.ts +143 -0
  54. package/src/ingest/adapters/virginia-law.ts +140 -0
  55. package/scripts/quality-test.ts +0 -346
  56. package/scripts/test-mcp-tools.ts +0 -187
  57. 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
- constructor(regulationId: string, cfr_title: number, cfr_parts: number[]) {
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 HIPAA metadata
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
- * PLACEHOLDER: Returns hardcoded HIPAA metadata
38
- * TODO: Integrate with eCFR API to fetch live metadata
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
- // Placeholder metadata for HIPAA
42
- return {
43
- id: this.regulationId,
44
- full_name: 'Health Insurance Portability and Accountability Act',
45
- citation: '45 CFR Parts 160, 162, 164',
46
- effective_date: '2003-04-14',
47
- last_amended: '2013-01-25',
48
- source_url: 'https://www.ecfr.gov/current/title-45',
49
- jurisdiction: 'federal',
50
- regulation_type: 'rule',
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
- * Fetches SOX regulations from eCFR (for SEC implementing rules).
5
- * Source: 17 CFR Part 229 (Regulation S-K, Item 308) and Part 240 (Exchange Act Rules)
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 eCFR API for SEC regulations implementing Sarbanes-Oxley Section 404
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 { XMLParser } from 'fast-xml-parser';
19
- import { EcfrAdapter } from './ecfr.js';
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 fetching SOX regulations from eCFR
29
+ * Adapter for SOX compliance content
23
30
  *
24
- * Uses eCFR API for SEC regulations implementing Sarbanes-Oxley
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 ecfrAdapter: EcfrAdapter;
35
+ private readonly seedPath: string;
29
36
 
30
37
  constructor(regulationId: string) {
31
38
  this.regulationId = regulationId;
32
- // Use eCFR adapter for Title 17 (SEC regulations)
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 - SEC Implementing Regulations',
43
- citation: '17 CFR Parts 229, 240 (Regulation S-K Item 308, Exchange Act Rules)',
44
- effective_date: '2003-06-05',
45
- last_amended: new Date().toISOString().split('T')[0],
46
- source_url: 'https://www.ecfr.gov/current/title-17',
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: 'rule',
54
+ regulation_type: 'statute',
49
55
  };
50
56
  }
51
57
 
52
58
  /**
53
- * Fetch all SOX-related sections from eCFR
59
+ * Fetch all SOX sections from seed data
54
60
  *
55
- * Fetches 17 CFR Parts 229 and 240, filtering to SOX-relevant sections
61
+ * Includes statute sections, SEC implementing rules, and PCAOB standards
56
62
  */
57
63
  async *fetchSections(): AsyncGenerator<Section[]> {
58
- console.log('Fetching SOX sections from eCFR (Title 17)...');
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
- // Key SOX-related sections:
61
- // - 17 CFR 229.308 (Item 308: Internal control over financial reporting)
62
- // - 17 CFR 240.13a-15 (Controls and procedures)
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
- const relevantSections = [
68
- '229.308',
69
- '240.13a-15',
70
- '240.15d-15',
71
- '240.13a-14',
72
- '240.15d-14',
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
- // Fetch from eCFR adapter
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
- if (filtered.length > 0) {
83
- yield filtered;
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
- return this.ecfrAdapter.checkForUpdates(lastFetched);
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 this.ecfrAdapter.extractDefinitions();
104
+ return [];
104
105
  }
105
106
  }
106
107