@ai-pip/csl 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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +349 -0
  3. package/index.ts +273 -0
  4. package/layers/csl/adapters/index.ts +9 -0
  5. package/layers/csl/adapters/input/DOMAdapter.ts +236 -0
  6. package/layers/csl/adapters/input/UIAdapter.ts +0 -0
  7. package/layers/csl/adapters/output/ConsoleLogger.ts +34 -0
  8. package/layers/csl/adapters/output/CryptoHashGenerator.ts +29 -0
  9. package/layers/csl/adapters/output/FilePolicyRepository.ts +0 -0
  10. package/layers/csl/adapters/output/InMemoryPolicyRepository.ts +135 -0
  11. package/layers/csl/adapters/output/SystemTimestampProvider.ts +9 -0
  12. package/layers/csl/domain/entities/CSLResult.ts +309 -0
  13. package/layers/csl/domain/entities/Segment.ts +338 -0
  14. package/layers/csl/domain/entities/index.ts +2 -0
  15. package/layers/csl/domain/exceptions/ClassificationError.ts +26 -0
  16. package/layers/csl/domain/exceptions/SegmentationError.ts +30 -0
  17. package/layers/csl/domain/exceptions/index.ts +2 -0
  18. package/layers/csl/domain/index.ts +4 -0
  19. package/layers/csl/domain/services/AnomalyService.ts +255 -0
  20. package/layers/csl/domain/services/LineageService.ts +224 -0
  21. package/layers/csl/domain/services/NormalizationService.ts +392 -0
  22. package/layers/csl/domain/services/OriginClassificationService.ts +69 -0
  23. package/layers/csl/domain/services/PiDetectionService.ts +475 -0
  24. package/layers/csl/domain/services/PolicyService.ts +296 -0
  25. package/layers/csl/domain/services/SegmentClassificationService.ts +105 -0
  26. package/layers/csl/domain/services/SerializationService.ts +229 -0
  27. package/layers/csl/domain/services/index.ts +7 -0
  28. package/layers/csl/domain/value-objects/AnomalyScore.ts +23 -0
  29. package/layers/csl/domain/value-objects/ContentHash.ts +54 -0
  30. package/layers/csl/domain/value-objects/LineageEntry.ts +42 -0
  31. package/layers/csl/domain/value-objects/Origin-map.ts +67 -0
  32. package/layers/csl/domain/value-objects/Origin.ts +99 -0
  33. package/layers/csl/domain/value-objects/Pattern.ts +221 -0
  34. package/layers/csl/domain/value-objects/PiDetection.ts +140 -0
  35. package/layers/csl/domain/value-objects/PiDetectionResult.ts +275 -0
  36. package/layers/csl/domain/value-objects/PolicyRule.ts +151 -0
  37. package/layers/csl/domain/value-objects/TrustLevel.ts +34 -0
  38. package/layers/csl/domain/value-objects/index.ts +10 -0
  39. package/layers/csl/index.ts +3 -0
  40. package/layers/csl/ports/index.ts +10 -0
  41. package/layers/csl/ports/input/ClassificationPort.ts +76 -0
  42. package/layers/csl/ports/input/SegmentationPort.ts +81 -0
  43. package/layers/csl/ports/output/DOMAdapter.ts +14 -0
  44. package/layers/csl/ports/output/HashGenerator.ts +18 -0
  45. package/layers/csl/ports/output/Logger.ts +17 -0
  46. package/layers/csl/ports/output/PolicyRepository.ts +29 -0
  47. package/layers/csl/ports/output/SegmentClassified.ts +8 -0
  48. package/layers/csl/ports/output/TimeStampProvider.ts +5 -0
  49. package/layers/csl/services/CSLService.ts +393 -0
  50. package/layers/csl/services/index.ts +1 -0
  51. package/layers/csl/types/entities-types.ts +37 -0
  52. package/layers/csl/types/index.ts +4 -0
  53. package/layers/csl/types/pi-types.ts +111 -0
  54. package/layers/csl/types/port-output-types.ts +17 -0
  55. package/layers/csl/types/value-objects-types.ts +213 -0
  56. package/layers/csl/utils/colors.ts +25 -0
  57. package/layers/csl/utils/pattern-helpers.ts +174 -0
  58. package/package.json +50 -0
@@ -0,0 +1,54 @@
1
+ import type { CSLHashAlgorithm } from "../../types";
2
+
3
+ /**
4
+ * ContentHash value object represents a cryptographic hash of content
5
+ *
6
+ * @property value - The hash value as a hexadecimal string
7
+ * @property algorithm - The hash algorithm used (sha256 or sha512)
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const hash = new ContentHash('a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3', 'sha256')
12
+ * ```
13
+ */
14
+ export class ContentHash {
15
+ readonly value: string
16
+ readonly algorithm: CSLHashAlgorithm
17
+
18
+ constructor(value: string, algorithm: CSLHashAlgorithm = 'sha256') {
19
+ if (!value || typeof value !== 'string') {
20
+ throw new Error('ContentHash value must be a non-empty string')
21
+ }
22
+
23
+ if (!['sha256', 'sha512'].includes(algorithm)) {
24
+ throw new Error(`Invalid HashAlgorithm: ${algorithm}. Must be 'sha256' or 'sha512'`)
25
+ }
26
+
27
+ const hexPattern = /^[a-f0-9]+$/i
28
+ if (!hexPattern.test(value)) {
29
+ throw new Error('ContentHash value must be a valid hexadecimal string')
30
+ }
31
+
32
+ const expectedLength = algorithm === 'sha256' ? 64 : 128
33
+ if (value.length !== expectedLength) {
34
+ throw new Error(`ContentHash value length must be ${expectedLength} characters for ${algorithm}`)
35
+ }
36
+
37
+ this.value = value.toLowerCase()
38
+ this.algorithm = algorithm
39
+ }
40
+
41
+ /**
42
+ * Returns true if the hash uses SHA-256 algorithm
43
+ */
44
+ isSha256(): boolean {
45
+ return this.algorithm === 'sha256'
46
+ }
47
+
48
+ /**
49
+ * Returns true if the hash uses SHA-512 algorithm
50
+ */
51
+ isSha512(): boolean {
52
+ return this.algorithm === 'sha512'
53
+ }
54
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * LineageEntry value object represents a single entry in the lineage chain of a content segment
3
+ *
4
+ * @property step - Description of the processing step
5
+ * @property timestamp - Unix timestamp in milliseconds
6
+ * @property notes - Optional additional notes about this step
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const entry = new LineageEntry('normalization', Date.now(), 'Unicode normalization applied')
11
+ * ```
12
+ */
13
+ export class LineageEntry {
14
+ readonly step: string
15
+ readonly timestamp: number
16
+ readonly notes?: string
17
+
18
+ constructor(step: string, timestamp: number, notes?: string) {
19
+ if (!step || typeof step !== 'string' || step.trim().length === 0) {
20
+ throw new Error('LineageEntry step must be a non-empty string')
21
+ }
22
+
23
+ if (typeof timestamp !== 'number' || timestamp < 0 || !Number.isFinite(timestamp)) {
24
+ throw new Error('LineageEntry timestamp must be a valid positive number')
25
+ }
26
+
27
+ if (notes !== undefined && (typeof notes !== 'string' || notes.trim().length === 0)) {
28
+ throw new Error('LineageEntry notes must be a non-empty string if provided')
29
+ }
30
+
31
+ this.step = step.trim()
32
+ this.timestamp = timestamp
33
+ this.notes = notes?.trim() ?? ''
34
+ }
35
+
36
+ /**
37
+ * Returns true if this entry has notes
38
+ */
39
+ hasNotes(): boolean {
40
+ return this.notes !== undefined && this.notes.length > 0
41
+ }
42
+ }
@@ -0,0 +1,67 @@
1
+ import { OriginType, TrustLevelType } from "../../types";
2
+
3
+
4
+
5
+ /**
6
+ * originMap is the deterministic mapping from OriginType to TrustLevelType.
7
+ *
8
+ * @remarks
9
+ * This map defines the **deterministic classification rules** for content segments.
10
+ * The mapping is based solely on the origin type, not on content analysis.
11
+ *
12
+ * **Key Principles:**
13
+ * - 100% deterministic: same origin → same trust level, always
14
+ * - No heuristics or content analysis
15
+ * - All OriginType values must be present in this map
16
+ * - Future content analysis can be added as a separate layer
17
+ *
18
+ * **Mapping Rules:**
19
+ * - USER → UC (always untrusted for security)
20
+ * - DOM_VISIBLE → STC (user can verify)
21
+ * - DOM_HIDDEN → UC (potential attack vector)
22
+ * - DOM_ATTRIBUTE → STC (visible in source)
23
+ * - SCRIPT_INJECTED → UC (can be manipulated)
24
+ * - NETWORK_FETCHED → UC (external source)
25
+ * - SYSTEM_GENERATED → TC (system controls it)
26
+ * - UNKNOWN → UC (unknown is untrusted by default)
27
+ */
28
+ export const originMap = new Map<OriginType, TrustLevelType>([
29
+ // User origins - always untrusted (security by default)
30
+ [OriginType.USER, TrustLevelType.UC],
31
+
32
+ // DOM origins - trust based on visibility
33
+ [OriginType.DOM_VISIBLE, TrustLevelType.STC],
34
+ [OriginType.DOM_HIDDEN, TrustLevelType.UC],
35
+ [OriginType.DOM_ATTRIBUTE, TrustLevelType.STC],
36
+
37
+ // External origins - always untrusted
38
+ [OriginType.SCRIPT_INJECTED, TrustLevelType.UC],
39
+ [OriginType.NETWORK_FETCHED, TrustLevelType.UC],
40
+
41
+ // System origins - trusted (system controls)
42
+ [OriginType.SYSTEM_GENERATED, TrustLevelType.TC],
43
+
44
+ // Unknown - untrusted by default (fail-secure)
45
+ [OriginType.UNKNOWN, TrustLevelType.UC],
46
+ ]);
47
+
48
+ /**
49
+ * Validates that all OriginType values are mapped in originMap.
50
+ *
51
+ * @remarks
52
+ * This function ensures completeness of the mapping at runtime.
53
+ * Throws an error if any OriginType is missing from the map.
54
+ *
55
+ * @throws {Error} If any OriginType is not present in originMap
56
+ */
57
+ export function validateOriginMap(): void {
58
+ const allOriginTypes = Object.values(OriginType);
59
+ const missingTypes = allOriginTypes.filter(type => !originMap.has(type));
60
+
61
+ if (missingTypes.length > 0) {
62
+ throw new Error(
63
+ `Missing origin mappings: ${missingTypes.join(', ')}. ` +
64
+ `All OriginType values must be mapped in originMap.`
65
+ );
66
+ }
67
+ }
@@ -0,0 +1,99 @@
1
+ import { OriginType } from '../../types'
2
+
3
+
4
+ /**
5
+ * Origin value object represents the deterministic source of a content segment.
6
+ *
7
+ * @remarks
8
+ * Origin is a value object that encapsulates the source type of content.
9
+ * The origin type determines the trust level through deterministic classification.
10
+ *
11
+ * **Deterministic Classification:**
12
+ * - Classification is based solely on the origin type
13
+ * - No content analysis or heuristics
14
+ * - 100% reproducible: same origin → same trust level
15
+ *
16
+ * @property type - The origin type (USER, DOM_VISIBLE, DOM_HIDDEN, etc.)
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * // Create origin for user input
21
+ * const userOrigin = new Origin(OriginType.USER)
22
+ *
23
+ * // Check origin characteristics
24
+ * if (userOrigin.isUser()) {
25
+ * // Handle user input with extra security
26
+ * }
27
+ *
28
+ * // Create origin for system content
29
+ * const systemOrigin = new Origin(OriginType.SYSTEM_GENERATED)
30
+ * if (systemOrigin.isSystem()) {
31
+ * // Handle system content with trust
32
+ * }
33
+ * ```
34
+ */
35
+
36
+
37
+ export class Origin {
38
+ readonly type: OriginType;
39
+
40
+ constructor(type: OriginType){
41
+ if(!Object.values(OriginType).includes(type)){
42
+ throw new Error(`Invalid Origin type: ${type}`)
43
+ }
44
+ this.type = type
45
+ }
46
+
47
+ /**
48
+ * Checks if the origin is from DOM (visible, hidden, or attribute)
49
+ */
50
+ isDom(): boolean {
51
+ return this.type === OriginType.DOM_HIDDEN ||
52
+ this.type === OriginType.DOM_VISIBLE ||
53
+ this.type === OriginType.DOM_ATTRIBUTE
54
+ }
55
+
56
+ /**
57
+ * Checks if the origin is direct user input
58
+ */
59
+ isUser(): boolean {
60
+ return this.type === OriginType.USER
61
+ }
62
+
63
+ /**
64
+ * Checks if the origin is system-generated content
65
+ */
66
+ isSystem(): boolean {
67
+ return this.type === OriginType.SYSTEM_GENERATED
68
+ }
69
+
70
+ /**
71
+ * Checks if the origin is script-injected content
72
+ */
73
+ isInjected(): boolean {
74
+ return this.type === OriginType.SCRIPT_INJECTED
75
+ }
76
+
77
+ /**
78
+ * Checks if the origin is unknown
79
+ */
80
+ isUnknown(): boolean {
81
+ return this.type === OriginType.UNKNOWN
82
+ }
83
+
84
+ /**
85
+ * Checks if the origin is network-fetched content
86
+ */
87
+ isNetworkFetched(): boolean {
88
+ return this.type === OriginType.NETWORK_FETCHED
89
+ }
90
+
91
+ /**
92
+ * Checks if the origin is from an external source (network or injected)
93
+ */
94
+ isExternal(): boolean {
95
+ return this.type === OriginType.NETWORK_FETCHED ||
96
+ this.type === OriginType.SCRIPT_INJECTED
97
+ }
98
+
99
+ }
@@ -0,0 +1,221 @@
1
+ import type { RiskScore } from '../../types'
2
+ import { PatternHelpers } from '../../utils/pattern-helpers'
3
+
4
+ /**
5
+ * Pattern value object represents a detection pattern for prompt injection attacks
6
+ *
7
+ * @remarks
8
+ * Patterns define how to detect specific types of prompt injection attempts.
9
+ * They can be created from regular expression strings or RegExp objects.
10
+ * The pattern is normalized to a RegExp internally for consistent matching.
11
+ *
12
+ * **Key Features:**
13
+ * - Normalizes regex strings to RegExp objects
14
+ * - Maintains immutability through Object.freeze
15
+ * - Provides methods for pattern matching and match information extraction
16
+ * - Validates all inputs to ensure pattern integrity
17
+ *
18
+ * **Usage:**
19
+ * Patterns are typically used by PiDetectionService to scan content for malicious patterns.
20
+ * Each pattern has a base confidence score that can be adjusted based on context.
21
+ *
22
+ * @property pattern_type - Type of pattern (e.g., "role_swapping", "instruction_override")
23
+ * @property regex - Compiled regular expression for pattern matching (always RegExp after construction)
24
+ * @property base_confidence - Base confidence score for this pattern (0-1)
25
+ * @property description - Optional description of what this pattern detects
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // Create pattern from string
30
+ * const pattern = new Pattern(
31
+ * 'role_swapping',
32
+ * 'you are no longer',
33
+ * 0.9,
34
+ * 'Detects attempts to change AI role'
35
+ * )
36
+ *
37
+ * // Create pattern from RegExp
38
+ * const pattern2 = new Pattern(
39
+ * 'instruction_override',
40
+ * /ignore\s+(all\s+)?(previous|prior)\s+instructions?/i,
41
+ * 0.95
42
+ * )
43
+ *
44
+ * // Check if pattern matches content
45
+ * if (pattern.matches(content)) {
46
+ * const match = pattern.findMatch(content)
47
+ * if (match) {
48
+ * // Use match information to create PiDetection
49
+ * }
50
+ * }
51
+ * ```
52
+ */
53
+ export class Pattern {
54
+ readonly pattern_type: string
55
+ readonly regex: RegExp
56
+ readonly base_confidence: RiskScore
57
+ readonly description: string
58
+
59
+ constructor(
60
+ pattern_type: string,
61
+ regex: string | RegExp,
62
+ base_confidence: RiskScore,
63
+ description?: string
64
+ ) {
65
+ // Validate all inputs using helper functions
66
+ PatternHelpers.validatePatternType(pattern_type)
67
+ PatternHelpers.validateRegex(regex)
68
+ PatternHelpers.validateBaseConfidence(base_confidence)
69
+ PatternHelpers.validateDescription(description)
70
+
71
+ this.pattern_type = pattern_type.trim()
72
+
73
+ // Validate and process regex
74
+ const regexSource = typeof regex === 'string' ? regex : regex.source
75
+ PatternHelpers.validateRegexLength(regexSource)
76
+ PatternHelpers.checkForReDoSPatterns(regexSource, this.pattern_type)
77
+
78
+ // Normalize regex to RegExp for consistency and immutability
79
+ this.regex = typeof regex === 'string'
80
+ ? PatternHelpers.compileRegexString(regex)
81
+ : PatternHelpers.cloneRegExp(regex)
82
+
83
+ this.base_confidence = base_confidence
84
+ this.description = description?.trim() ?? ''
85
+
86
+ // Freeze the object for immutability
87
+ Object.freeze(this)
88
+ }
89
+
90
+ /**
91
+ * Checks if the pattern matches the given content
92
+ *
93
+ * @param content - The content string to test against the pattern
94
+ * @returns true if the pattern matches, false otherwise
95
+ *
96
+ * @throws {TypeError} If content is not a valid non-empty string
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const pattern = new Pattern('role_swapping', 'you are no longer', 0.9)
101
+ * if (pattern.matches('Hello, you are no longer an AI')) {
102
+ * // Pattern detected
103
+ * }
104
+ * ```
105
+ */
106
+ matches(content: string): boolean {
107
+ PatternHelpers.validateContent(content, 'matches')
108
+ PatternHelpers.validateContentLength(content, 'matches')
109
+ return this.regex.test(content)
110
+ }
111
+
112
+ /**
113
+ * Finds the first match in content and returns match information
114
+ *
115
+ * @remarks
116
+ * This method is useful when you need to create a PiDetection from a pattern match.
117
+ * It returns the exact matched text and its position in the content.
118
+ *
119
+ * @param content - The content string to search for matches
120
+ * @returns Match information with matched text and position, or null if no match found
121
+ *
122
+ * @throws {TypeError} If content is not a valid non-empty string
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * const pattern = new Pattern('role_swapping', 'you are no longer', 0.9)
127
+ * const match = pattern.findMatch('Hello, you are no longer an AI assistant')
128
+ *
129
+ * if (match) {
130
+ * // match.matched = 'you are no longer'
131
+ * // match.position = { start: 7, end: 25 }
132
+ * const detection = new PiDetection(
133
+ * pattern.pattern_type,
134
+ * match.matched,
135
+ * match.position,
136
+ * pattern.base_confidence
137
+ * )
138
+ * }
139
+ * ```
140
+ */
141
+ findMatch(content: string): { matched: string; position: { start: number; end: number } } | null {
142
+ PatternHelpers.validateContent(content, 'findMatch')
143
+ PatternHelpers.validateContentLength(content, 'findMatch')
144
+
145
+ const match = this.regex.exec(content)
146
+ if (!match) {
147
+ return null
148
+ }
149
+
150
+ return PatternHelpers.createMatchResult(match)
151
+ }
152
+
153
+ /**
154
+ * Finds all matches in content and returns match information for each
155
+ *
156
+ * @remarks
157
+ * This method finds all non-overlapping matches of the pattern in the content.
158
+ * Useful when a pattern might appear multiple times in the same content.
159
+ *
160
+ * @param content - The content string to search for matches
161
+ * @returns Array of match information, empty array if no matches found
162
+ *
163
+ * @throws {TypeError} If content is not a valid non-empty string
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * const pattern = new Pattern('instruction_override', 'ignore', 0.9)
168
+ * const matches = pattern.findAllMatches('ignore this and ignore that')
169
+ * // Returns: [
170
+ * // { matched: 'ignore', position: { start: 0, end: 6 } },
171
+ * // { matched: 'ignore', position: { start: 20, end: 26 } }
172
+ * // ]
173
+ * ```
174
+ */
175
+ findAllMatches(content: string): Array<{ matched: string; position: { start: number; end: number } }> {
176
+ PatternHelpers.validateContent(content, 'findAllMatches')
177
+ PatternHelpers.validateContentLength(content, 'findAllMatches')
178
+
179
+ const matches: Array<{ matched: string; position: { start: number; end: number } }> = []
180
+ const globalRegex = new RegExp(this.regex.source, this.regex.flags + 'g')
181
+ let match: RegExpExecArray | null
182
+ let iterations = 0
183
+
184
+ while ((match = globalRegex.exec(content)) !== null) {
185
+ iterations++
186
+
187
+ if (PatternHelpers.checkMatchLimits(matches.length, iterations)) {
188
+ break
189
+ }
190
+
191
+ matches.push(PatternHelpers.createMatchResult(match))
192
+ PatternHelpers.handleEmptyStringMatch(globalRegex, match)
193
+ }
194
+
195
+ return matches
196
+ }
197
+
198
+ /**
199
+ * Returns the base confidence score for this pattern
200
+ *
201
+ * @returns The base confidence score (0-1)
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * const pattern = new Pattern('role_swapping', 'you are no longer', 0.9)
206
+ * const confidence = pattern.getConfidence() // Returns: 0.9
207
+ * ```
208
+ */
209
+ getConfidence(): RiskScore {
210
+ return this.base_confidence
211
+ }
212
+
213
+ /**
214
+ * Returns true if this pattern has a description
215
+ *
216
+ * @returns true if description is not empty, false otherwise
217
+ */
218
+ hasDescription(): boolean {
219
+ return this.description.length > 0
220
+ }
221
+ }
@@ -0,0 +1,140 @@
1
+ import type { RiskScore } from '../../types'
2
+
3
+ /**
4
+ * Position represents the location of a detected pattern within content
5
+ *
6
+ * @property start - Start position (inclusive, 0-based index)
7
+ * @property end - End position (exclusive, 0-based index)
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const position = { start: 12, end: 35 }
12
+ * // This means the pattern spans from character 12 to 34 (inclusive)
13
+ * ```
14
+ */
15
+ export interface Position {
16
+ readonly start: number
17
+ readonly end: number
18
+ }
19
+
20
+ /**
21
+ * PiDetection value object represents a single prompt injection detection
22
+ *
23
+ * @remarks
24
+ * This value object encapsulates all information about a single detected pattern:
25
+ * - What type of pattern was detected
26
+ * - The exact text that matched
27
+ * - Where it was found in the content
28
+ * - How confident the detection is
29
+ *
30
+ * **Immutability:**
31
+ * All properties are readonly and the object is frozen to ensure immutability.
32
+ *
33
+ * @property pattern_type - Type of malicious pattern detected (e.g., "role_swapping", "instruction_override")
34
+ * @property matched_pattern - The exact text that matched the pattern
35
+ * @property position - Location of the pattern in the content (start and end indices)
36
+ * @property confidence - Confidence score for this specific detection (0-1)
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const detection = new PiDetection(
41
+ * 'role_swapping',
42
+ * 'you are no longer an AI assistant',
43
+ * { start: 12, end: 47 },
44
+ * 0.95
45
+ * )
46
+ * ```
47
+ */
48
+ export class PiDetection {
49
+ readonly pattern_type: string
50
+ readonly matched_pattern: string
51
+ readonly position: Position
52
+ readonly confidence: RiskScore
53
+
54
+ constructor(
55
+ pattern_type: string,
56
+ matched_pattern: string,
57
+ position: Position,
58
+ confidence: RiskScore
59
+ ) {
60
+ // Validate pattern_type
61
+ if (!pattern_type || typeof pattern_type !== 'string' || pattern_type.trim().length === 0) {
62
+ throw new Error('PiDetection pattern_type must be a non-empty string')
63
+ }
64
+
65
+ // Validate matched_pattern
66
+ if (!matched_pattern || typeof matched_pattern !== 'string') {
67
+ throw new Error('PiDetection matched_pattern must be a non-empty string')
68
+ }
69
+
70
+ // Validate position
71
+ if (!position || typeof position !== 'object') {
72
+ throw new TypeError('PiDetection position must be an object with start and end properties')
73
+ }
74
+
75
+ if (typeof position.start !== 'number' || !Number.isFinite(position.start) || position.start < 0) {
76
+ throw new Error('PiDetection position.start must be a valid non-negative number')
77
+ }
78
+
79
+ if (typeof position.end !== 'number' || !Number.isFinite(position.end) || position.end < 0) {
80
+ throw new Error('PiDetection position.end must be a valid non-negative number')
81
+ }
82
+
83
+ if (position.end <= position.start) {
84
+ throw new Error('PiDetection position.end must be greater than position.start')
85
+ }
86
+
87
+ // Validate confidence
88
+ if (typeof confidence !== 'number' || !Number.isFinite(confidence)) {
89
+ throw new TypeError('PiDetection confidence must be a valid number')
90
+ }
91
+
92
+ if (confidence < 0 || confidence > 1) {
93
+ throw new Error('PiDetection confidence must be between 0 and 1')
94
+ }
95
+
96
+ // Validate that matched_pattern length matches position
97
+ const patternLength = position.end - position.start
98
+ if (matched_pattern.length !== patternLength) {
99
+ throw new Error(
100
+ `PiDetection matched_pattern length (${matched_pattern.length}) does not match position range (${patternLength})`
101
+ )
102
+ }
103
+
104
+ this.pattern_type = pattern_type.trim()
105
+ this.matched_pattern = matched_pattern
106
+ this.position = Object.freeze({ ...position })
107
+ this.confidence = confidence
108
+
109
+ // Freeze the entire object for immutability
110
+ Object.freeze(this)
111
+ }
112
+
113
+ /**
114
+ * Returns the length of the detected pattern
115
+ */
116
+ getLength(): number {
117
+ return this.position.end - this.position.start
118
+ }
119
+
120
+ /**
121
+ * Returns true if this detection has high confidence (>= 0.7)
122
+ */
123
+ isHighConfidence(): boolean {
124
+ return this.confidence >= 0.7
125
+ }
126
+
127
+ /**
128
+ * Returns true if this detection has medium confidence (>= 0.3 and < 0.7)
129
+ */
130
+ isMediumConfidence(): boolean {
131
+ return this.confidence >= 0.3 && this.confidence < 0.7
132
+ }
133
+
134
+ /**
135
+ * Returns true if this detection has low confidence (< 0.3)
136
+ */
137
+ isLowConfidence(): boolean {
138
+ return this.confidence < 0.3
139
+ }
140
+ }