@ai-pip/csl 0.1.3 → 0.1.5
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/layers/csl/index.ts +1 -0
- package/layers/csl/src/adapters/index.ts +10 -0
- package/layers/csl/src/adapters/input/DOMAdapter.ts +236 -0
- package/layers/csl/src/adapters/input/UIAdapter.ts +0 -0
- package/layers/csl/src/adapters/output/ConsoleLogger.ts +34 -0
- package/layers/csl/src/adapters/output/CryptoHashGenerator.ts +29 -0
- package/layers/csl/src/adapters/output/FilePolicyRepository.ts +0 -0
- package/layers/csl/src/adapters/output/InMemoryPolicyRepository.ts +135 -0
- package/layers/csl/src/adapters/output/SystemTimestampProvider.ts +9 -0
- package/layers/csl/src/domain/entities/CSLResult.ts +309 -0
- package/layers/csl/src/domain/entities/Segment.ts +338 -0
- package/layers/csl/src/domain/entities/index.ts +2 -0
- package/layers/csl/src/domain/exceptions/ClassificationError.ts +26 -0
- package/layers/csl/src/domain/exceptions/SegmentationError.ts +30 -0
- package/layers/csl/src/domain/exceptions/index.ts +2 -0
- package/layers/csl/src/domain/index.ts +4 -0
- package/layers/csl/src/domain/services/AnomalyService.ts +255 -0
- package/layers/csl/src/domain/services/LineageService.ts +224 -0
- package/layers/csl/src/domain/services/NormalizationService.ts +392 -0
- package/layers/csl/src/domain/services/OriginClassificationService.ts +69 -0
- package/layers/csl/src/domain/services/PiDetectionService.ts +475 -0
- package/layers/csl/src/domain/services/PolicyService.ts +296 -0
- package/layers/csl/src/domain/services/SegmentClassificationService.ts +105 -0
- package/layers/csl/src/domain/services/SerializationService.ts +229 -0
- package/layers/csl/src/domain/services/index.ts +7 -0
- package/layers/csl/src/domain/value-objects/AnomalyScore.ts +23 -0
- package/layers/csl/src/domain/value-objects/ContentHash.ts +54 -0
- package/layers/csl/src/domain/value-objects/LineageEntry.ts +42 -0
- package/layers/csl/src/domain/value-objects/Origin-map.ts +67 -0
- package/layers/csl/src/domain/value-objects/Origin.ts +99 -0
- package/layers/csl/src/domain/value-objects/Pattern.ts +221 -0
- package/layers/csl/src/domain/value-objects/PiDetection.ts +140 -0
- package/layers/csl/src/domain/value-objects/PiDetectionResult.ts +275 -0
- package/layers/csl/src/domain/value-objects/PolicyRule.ts +151 -0
- package/layers/csl/src/domain/value-objects/TrustLevel.ts +34 -0
- package/layers/csl/src/domain/value-objects/index.ts +10 -0
- package/layers/csl/src/index.ts +7 -0
- package/layers/csl/src/ports/index.ts +10 -0
- package/layers/csl/src/ports/input/ClassificationPort.ts +76 -0
- package/layers/csl/src/ports/input/SegmentationPort.ts +81 -0
- package/layers/csl/src/ports/output/DOMAdapter.ts +14 -0
- package/layers/csl/src/ports/output/HashGenerator.ts +18 -0
- package/layers/csl/src/ports/output/Logger.ts +17 -0
- package/layers/csl/src/ports/output/PolicyRepository.ts +29 -0
- package/layers/csl/src/ports/output/SegmentClassified.ts +8 -0
- package/layers/csl/src/ports/output/TimeStampProvider.ts +5 -0
- package/layers/csl/src/services/CSLService.ts +393 -0
- package/layers/csl/src/services/index.ts +1 -0
- package/layers/csl/src/types/entities-types.ts +37 -0
- package/layers/csl/src/types/index.ts +4 -0
- package/layers/csl/src/types/pi-types.ts +111 -0
- package/layers/csl/src/types/port-output-types.ts +17 -0
- package/layers/csl/src/types/value-objects-types.ts +213 -0
- package/layers/csl/src/utils/colors.ts +25 -0
- package/layers/csl/src/utils/pattern-helpers.ts +174 -0
- package/package.json +4 -5
- package/src/index.ts +36 -36
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import type { Segment } from '../entities'
|
|
2
|
+
import type { PolicyRule } from '../value-objects'
|
|
3
|
+
import type { PolicyRepositoryPort } from '../../ports/output/PolicyRepository'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Policy violation information
|
|
7
|
+
*/
|
|
8
|
+
export interface PolicyViolation {
|
|
9
|
+
readonly type: 'blocked_intent' | 'sensitive_scope' | 'role_protection' | 'context_leak'
|
|
10
|
+
readonly description: string
|
|
11
|
+
readonly severity: 'high' | 'medium' | 'low'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Policy validation result
|
|
16
|
+
*/
|
|
17
|
+
export interface PolicyValidationResult {
|
|
18
|
+
readonly isValid: boolean
|
|
19
|
+
readonly violations: readonly PolicyViolation[]
|
|
20
|
+
readonly policyVersion: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* PolicyService applies policy rules to content segments.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* This service validates segments against active policy rules retrieved from
|
|
28
|
+
* a PolicyRepository. It checks for:
|
|
29
|
+
* - Blocked intents in content
|
|
30
|
+
* - Sensitive scope violations
|
|
31
|
+
* - Role protection violations
|
|
32
|
+
* - Context leak prevention
|
|
33
|
+
*
|
|
34
|
+
* **Key Features:**
|
|
35
|
+
* - Depends on PolicyRepositoryPort for policy retrieval
|
|
36
|
+
* - Validates segments against active policies
|
|
37
|
+
* - Returns detailed violation information
|
|
38
|
+
* - Supports async policy loading
|
|
39
|
+
*
|
|
40
|
+
* **Usage:**
|
|
41
|
+
* PolicyService is instantiated with a PolicyRepository implementation.
|
|
42
|
+
* It validates segments during the processing pipeline to ensure compliance.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const policyRepository = new InMemoryPolicyRepository()
|
|
47
|
+
* const policyService = new PolicyService(policyRepository)
|
|
48
|
+
*
|
|
49
|
+
* // Validate a segment
|
|
50
|
+
* const result = await policyService.validateSegment(segment)
|
|
51
|
+
*
|
|
52
|
+
* if (!result.isValid) {
|
|
53
|
+
* console.log(`Policy violations: ${result.violations.length}`)
|
|
54
|
+
* result.violations.forEach(v => console.log(`- ${v.description}`))
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export class PolicyService {
|
|
59
|
+
constructor(private readonly policyRepository: PolicyRepositoryPort) {
|
|
60
|
+
if (!policyRepository) {
|
|
61
|
+
throw new TypeError('PolicyService: policyRepository is required')
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validates a segment against the active policy
|
|
67
|
+
*
|
|
68
|
+
* @param segment - The segment to validate
|
|
69
|
+
* @returns PolicyValidationResult with validation status and violations
|
|
70
|
+
*
|
|
71
|
+
* @throws {TypeError} If segment is not a Segment instance
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* const result = await policyService.validateSegment(segment)
|
|
76
|
+
* if (!result.isValid) {
|
|
77
|
+
* // Handle violations
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
async validateSegment(segment: Segment): Promise<PolicyValidationResult> {
|
|
82
|
+
if (!segment || typeof segment !== 'object' || !('id' in segment)) {
|
|
83
|
+
throw new TypeError('PolicyService.validateSegment: segment must be a Segment instance')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Get active policy
|
|
87
|
+
const policy = await this.policyRepository.getActivePolicy()
|
|
88
|
+
|
|
89
|
+
// Collect all violations
|
|
90
|
+
const violations: PolicyViolation[] = []
|
|
91
|
+
|
|
92
|
+
// Check for blocked intents
|
|
93
|
+
const intentViolations = this.checkBlockedIntents(segment, policy)
|
|
94
|
+
violations.push(...intentViolations)
|
|
95
|
+
|
|
96
|
+
// Check for sensitive scope violations
|
|
97
|
+
const scopeViolations = this.checkSensitiveScope(segment, policy)
|
|
98
|
+
violations.push(...scopeViolations)
|
|
99
|
+
|
|
100
|
+
// Check role protection
|
|
101
|
+
const roleViolations = this.checkRoleProtection(segment, policy)
|
|
102
|
+
violations.push(...roleViolations)
|
|
103
|
+
|
|
104
|
+
// Check context leak prevention
|
|
105
|
+
const contextLeakViolations = this.checkContextLeakPrevention(segment, policy)
|
|
106
|
+
violations.push(...contextLeakViolations)
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
isValid: violations.length === 0,
|
|
110
|
+
violations: Object.freeze(violations),
|
|
111
|
+
policyVersion: policy.version,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Checks if the segment contains any blocked intents
|
|
117
|
+
*/
|
|
118
|
+
private checkBlockedIntents(segment: Segment, policy: PolicyRule): PolicyViolation[] {
|
|
119
|
+
const violations: PolicyViolation[] = []
|
|
120
|
+
const content = segment.content.toLowerCase()
|
|
121
|
+
|
|
122
|
+
for (const blockedIntent of policy.blockedIntents) {
|
|
123
|
+
const intentPattern = blockedIntent.toLowerCase()
|
|
124
|
+
|
|
125
|
+
// Simple pattern matching (can be enhanced with regex or NLP)
|
|
126
|
+
if (content.includes(intentPattern)) {
|
|
127
|
+
violations.push({
|
|
128
|
+
type: 'blocked_intent',
|
|
129
|
+
description: `Blocked intent detected: ${blockedIntent}`,
|
|
130
|
+
severity: 'high',
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return violations
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Checks if the segment contains sensitive scope content
|
|
140
|
+
*/
|
|
141
|
+
private checkSensitiveScope(segment: Segment, policy: PolicyRule): PolicyViolation[] {
|
|
142
|
+
const violations: PolicyViolation[] = []
|
|
143
|
+
const content = segment.content.toLowerCase()
|
|
144
|
+
|
|
145
|
+
for (const sensitiveScope of policy.sensitiveScope) {
|
|
146
|
+
const scopePattern = sensitiveScope.toLowerCase()
|
|
147
|
+
|
|
148
|
+
// Simple pattern matching (can be enhanced)
|
|
149
|
+
if (content.includes(scopePattern)) {
|
|
150
|
+
violations.push({
|
|
151
|
+
type: 'sensitive_scope',
|
|
152
|
+
description: `Sensitive scope detected: ${sensitiveScope}`,
|
|
153
|
+
severity: 'medium',
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return violations
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Checks if the segment violates role protection rules
|
|
163
|
+
*/
|
|
164
|
+
private checkRoleProtection(segment: Segment, policy: PolicyRule): PolicyViolation[] {
|
|
165
|
+
const violations: PolicyViolation[] = []
|
|
166
|
+
const content = segment.content.toLowerCase()
|
|
167
|
+
|
|
168
|
+
// Check for protected role mentions
|
|
169
|
+
for (const protectedRole of policy.roleProtection.protectedRoles) {
|
|
170
|
+
const rolePattern = protectedRole.toLowerCase()
|
|
171
|
+
|
|
172
|
+
// Check for role override attempts
|
|
173
|
+
const overridePatterns = [
|
|
174
|
+
`you are no longer ${rolePattern}`,
|
|
175
|
+
`forget you are ${rolePattern}`,
|
|
176
|
+
`ignore your role as ${rolePattern}`,
|
|
177
|
+
`you are now ${rolePattern}`,
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
for (const pattern of overridePatterns) {
|
|
181
|
+
if (content.includes(pattern)) {
|
|
182
|
+
violations.push({
|
|
183
|
+
type: 'role_protection',
|
|
184
|
+
description: `Protected role override attempt: ${protectedRole}`,
|
|
185
|
+
severity: 'high',
|
|
186
|
+
})
|
|
187
|
+
break // Only report once per role
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check for immutable instruction modifications
|
|
193
|
+
for (const immutableInstruction of policy.roleProtection.immutableInstructions) {
|
|
194
|
+
const instructionPattern = immutableInstruction.toLowerCase()
|
|
195
|
+
|
|
196
|
+
// Check for instruction override attempts
|
|
197
|
+
const overridePatterns = [
|
|
198
|
+
`ignore ${instructionPattern}`,
|
|
199
|
+
`forget ${instructionPattern}`,
|
|
200
|
+
`disregard ${instructionPattern}`,
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
for (const pattern of overridePatterns) {
|
|
204
|
+
if (content.includes(pattern)) {
|
|
205
|
+
violations.push({
|
|
206
|
+
type: 'role_protection',
|
|
207
|
+
description: `Immutable instruction override attempt: ${immutableInstruction}`,
|
|
208
|
+
severity: 'high',
|
|
209
|
+
})
|
|
210
|
+
break
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return violations
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Checks if the segment violates context leak prevention rules
|
|
220
|
+
*/
|
|
221
|
+
private checkContextLeakPrevention(segment: Segment, policy: PolicyRule): PolicyViolation[] {
|
|
222
|
+
const violations: PolicyViolation[] = []
|
|
223
|
+
|
|
224
|
+
if (!policy.isContextLeakPreventionEnabled()) {
|
|
225
|
+
return violations // Context leak prevention is disabled
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const content = segment.content
|
|
229
|
+
|
|
230
|
+
// Check for metadata exposure patterns
|
|
231
|
+
if (policy.contextLeakPrevention.blockMetadataExposure) {
|
|
232
|
+
const metadataPatterns = [
|
|
233
|
+
/system\s+prompt/i,
|
|
234
|
+
/initial\s+instructions/i,
|
|
235
|
+
/base\s+instructions/i,
|
|
236
|
+
/original\s+prompt/i,
|
|
237
|
+
/hidden\s+instructions/i,
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
for (const pattern of metadataPatterns) {
|
|
241
|
+
if (pattern.test(content)) {
|
|
242
|
+
violations.push({
|
|
243
|
+
type: 'context_leak',
|
|
244
|
+
description: 'Potential metadata exposure detected',
|
|
245
|
+
severity: 'medium',
|
|
246
|
+
})
|
|
247
|
+
break
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Check for internal references
|
|
253
|
+
if (policy.contextLeakPrevention.sanitizeInternalReferences) {
|
|
254
|
+
const internalReferencePatterns = [
|
|
255
|
+
/internal\s+reference/i,
|
|
256
|
+
/system\s+variable/i,
|
|
257
|
+
/config\s+value/i,
|
|
258
|
+
/secret\s+key/i,
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
for (const pattern of internalReferencePatterns) {
|
|
262
|
+
if (pattern.test(content)) {
|
|
263
|
+
violations.push({
|
|
264
|
+
type: 'context_leak',
|
|
265
|
+
description: 'Potential internal reference exposure detected',
|
|
266
|
+
severity: 'low',
|
|
267
|
+
})
|
|
268
|
+
break
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return violations
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Checks if a segment should be blocked based on policy violations
|
|
278
|
+
*
|
|
279
|
+
* @param segment - The segment to check
|
|
280
|
+
* @returns true if the segment should be blocked, false otherwise
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```typescript
|
|
284
|
+
* const shouldBlock = await policyService.shouldBlock(segment)
|
|
285
|
+
* if (shouldBlock) {
|
|
286
|
+
* // Block the segment
|
|
287
|
+
* }
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
async shouldBlock(segment: Segment): Promise<boolean> {
|
|
291
|
+
const result = await this.validateSegment(segment)
|
|
292
|
+
|
|
293
|
+
// Block if there are high-severity violations
|
|
294
|
+
return result.violations.some(v => v.severity === 'high')
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { SegmentClassified } from "../../ports";
|
|
2
|
+
import type { Segment } from "../entities";
|
|
3
|
+
import { OriginClassificationService } from "./OriginClassificationService";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SegmentClassificationService classifies individual segments by assigning trust levels.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* This service orchestrates the classification of a Segment by:
|
|
10
|
+
* 1. Using OriginClassificationService to determine TrustLevel from the Segment's Origin
|
|
11
|
+
* 2. Assigning the TrustLevel to the Segment entity
|
|
12
|
+
* 3. Returning a SegmentClassified DTO with the classification result
|
|
13
|
+
*
|
|
14
|
+
* **Key Characteristics:**
|
|
15
|
+
* - Works with individual Segment entities
|
|
16
|
+
* - Uses ClassificationService internally for origin-based classification
|
|
17
|
+
* - Assigns TrustLevel to the Segment (mutates the segment)
|
|
18
|
+
* - Returns a DTO (SegmentClassified) with classification information
|
|
19
|
+
*
|
|
20
|
+
* **Usage in Pipeline:**
|
|
21
|
+
* This service is typically used in the SegmentationService pipeline to classify
|
|
22
|
+
* segments after they are created from DOM adaptation.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const originClassificationService = new OriginClassificationService()
|
|
27
|
+
* const segmentClassifier = new SegmentClassificationService(originClassificationService)
|
|
28
|
+
*
|
|
29
|
+
* const segment = new Segment({
|
|
30
|
+
* id: 'seg-123',
|
|
31
|
+
* origin: new Origin(OriginType.USER),
|
|
32
|
+
* content: 'Hello, how can I help you?',
|
|
33
|
+
* mime: 'text/plain',
|
|
34
|
+
* timestamp: Date.now(),
|
|
35
|
+
* source: 'user-input-field'
|
|
36
|
+
* })
|
|
37
|
+
*
|
|
38
|
+
* const classified = segmentClassifier.classify(segment)
|
|
39
|
+
* // segment.trustLevel is now assigned (TrustLevelType.UC)
|
|
40
|
+
* // classified contains: { segmentId, text, origin, trustLevel }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export class SegmentClassificationService {
|
|
44
|
+
constructor(
|
|
45
|
+
private readonly originClassificationService: OriginClassificationService
|
|
46
|
+
) {}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Classifies a segment by assigning a trust level based on its origin.
|
|
50
|
+
*
|
|
51
|
+
* @param segment - The Segment entity to classify. Must have an origin assigned.
|
|
52
|
+
*
|
|
53
|
+
* @returns A SegmentClassified object containing:
|
|
54
|
+
* - segmentId: The unique identifier of the segment
|
|
55
|
+
* - text: The content text of the segment
|
|
56
|
+
* - origin: The origin of the segment
|
|
57
|
+
* - trustLevel: The trust level assigned based on the origin
|
|
58
|
+
*
|
|
59
|
+
* @throws {ClassificationError} If classification fails (origin not mapped)
|
|
60
|
+
* @throws {Error} If segment already has a trust level assigned
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const classified = segmentClassifier.classify(segment)
|
|
65
|
+
* console.log(classified.trustLevel.value) // 'UC', 'STC', or 'TC'
|
|
66
|
+
* console.log(segment.trustLevel?.value) // Same value (assigned to segment)
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
classify(segment: Segment): SegmentClassified {
|
|
70
|
+
// 1. Use ClassificationService to get TrustLevel from the segment's Origin
|
|
71
|
+
const trustLevel = this.originClassificationService.classify(segment.origin);
|
|
72
|
+
|
|
73
|
+
// 2. Assign the TrustLevel to the Segment entity
|
|
74
|
+
segment.assignTrustLevel(trustLevel);
|
|
75
|
+
|
|
76
|
+
// 3. Return the classification result as a DTO
|
|
77
|
+
return {
|
|
78
|
+
segmentId: segment.id,
|
|
79
|
+
text: segment.content,
|
|
80
|
+
origin: segment.origin,
|
|
81
|
+
trustLevel: trustLevel
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Classifies multiple segments in batch.
|
|
87
|
+
*
|
|
88
|
+
* @param segments - Array of Segment entities to classify
|
|
89
|
+
*
|
|
90
|
+
* @returns Array of SegmentClassified objects, one for each segment
|
|
91
|
+
*
|
|
92
|
+
* @throws {ClassificationError} If classification fails for any segment
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const segments = [segment1, segment2, segment3]
|
|
97
|
+
* const classified = segmentClassifier.classifyMany(segments)
|
|
98
|
+
* // All segments now have trustLevel assigned
|
|
99
|
+
* // Returns array of SegmentClassified objects
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
classifyMany(segments: Segment[]): SegmentClassified[] {
|
|
103
|
+
return segments.map(segment => this.classify(segment));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import type { Segment } from '../entities'
|
|
2
|
+
import { LineageEntry } from '../value-objects'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Serialized representation of a Segment for storage/transmission
|
|
6
|
+
*/
|
|
7
|
+
export interface SerializedSegment {
|
|
8
|
+
readonly id: string
|
|
9
|
+
readonly origin: string
|
|
10
|
+
readonly content: string
|
|
11
|
+
readonly mime: string
|
|
12
|
+
readonly timestamp: number
|
|
13
|
+
readonly source: string
|
|
14
|
+
readonly metadata?: Record<string, unknown>
|
|
15
|
+
readonly trustLevel?: string
|
|
16
|
+
readonly hash?: {
|
|
17
|
+
readonly value: string
|
|
18
|
+
readonly algorithm: string
|
|
19
|
+
}
|
|
20
|
+
readonly anomalyScore?: {
|
|
21
|
+
readonly score: number
|
|
22
|
+
readonly action: string
|
|
23
|
+
}
|
|
24
|
+
readonly piDetection?: {
|
|
25
|
+
readonly score: number
|
|
26
|
+
readonly detections: Array<{
|
|
27
|
+
readonly pattern_type: string
|
|
28
|
+
readonly confidence: number
|
|
29
|
+
readonly position?: {
|
|
30
|
+
readonly start: number
|
|
31
|
+
readonly end: number
|
|
32
|
+
}
|
|
33
|
+
}>
|
|
34
|
+
readonly action: string
|
|
35
|
+
}
|
|
36
|
+
readonly lineage?: Array<{
|
|
37
|
+
readonly step: string
|
|
38
|
+
readonly timestamp: number
|
|
39
|
+
readonly notes?: string
|
|
40
|
+
}>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* SerializationService provides serialization of domain entities to plain objects.
|
|
45
|
+
*
|
|
46
|
+
* @remarks
|
|
47
|
+
* This service converts domain entities (like Segment) into serializable formats
|
|
48
|
+
* (plain objects) that can be stored, transmitted, or logged. All methods are static
|
|
49
|
+
* since serialization is a stateless operation.
|
|
50
|
+
*
|
|
51
|
+
* **Key Features:**
|
|
52
|
+
* - Stateless: All methods are static
|
|
53
|
+
* - Type-safe: Returns well-defined serialized interfaces
|
|
54
|
+
* - Immutable: Serialized objects are frozen
|
|
55
|
+
* - Handles optional properties gracefully
|
|
56
|
+
*
|
|
57
|
+
* **Usage:**
|
|
58
|
+
* SerializationService is used when:
|
|
59
|
+
* - Storing segments in databases
|
|
60
|
+
* - Transmitting segments over networks
|
|
61
|
+
* - Logging segment information
|
|
62
|
+
* - Creating CSLResult output
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* // Serialize a segment
|
|
67
|
+
* const serialized = SerializationService.serializeSegment(segment)
|
|
68
|
+
*
|
|
69
|
+
* // Store or transmit
|
|
70
|
+
* await database.save(serialized)
|
|
71
|
+
*
|
|
72
|
+
* // Or log
|
|
73
|
+
* console.log(JSON.stringify(serialized, null, 2))
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export class SerializationService {
|
|
77
|
+
/**
|
|
78
|
+
* Serializes a Segment entity to a plain object
|
|
79
|
+
*
|
|
80
|
+
* @param segment - The segment to serialize
|
|
81
|
+
* @returns SerializedSegment object ready for storage/transmission
|
|
82
|
+
*
|
|
83
|
+
* @throws {TypeError} If segment is not a Segment instance
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const serialized = SerializationService.serializeSegment(segment)
|
|
88
|
+
* const json = JSON.stringify(serialized)
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
static serializeSegment(segment: Segment): SerializedSegment {
|
|
92
|
+
if (!segment || typeof segment !== 'object' || !('id' in segment)) {
|
|
93
|
+
throw new TypeError('SerializationService.serializeSegment: segment must be a Segment instance')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const serialized: SerializedSegment = {
|
|
97
|
+
id: segment.id,
|
|
98
|
+
origin: segment.origin.type,
|
|
99
|
+
content: segment.content,
|
|
100
|
+
mime: segment.mime,
|
|
101
|
+
timestamp: segment.timestamp,
|
|
102
|
+
source: segment.source,
|
|
103
|
+
...(segment.metadata && { metadata: { ...segment.metadata } }),
|
|
104
|
+
...(segment.trustLevel && { trustLevel: segment.trustLevel.value }),
|
|
105
|
+
...(segment.hash && {
|
|
106
|
+
hash: {
|
|
107
|
+
value: segment.hash.value,
|
|
108
|
+
algorithm: segment.hash.algorithm,
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
...(segment.anomalyScore && {
|
|
112
|
+
anomalyScore: {
|
|
113
|
+
score: segment.anomalyScore.score,
|
|
114
|
+
action: segment.anomalyScore.action,
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
...(segment.piDetection && {
|
|
118
|
+
piDetection: {
|
|
119
|
+
score: segment.piDetection.score,
|
|
120
|
+
detections: segment.piDetection.detections.map(detection => ({
|
|
121
|
+
pattern_type: detection.pattern_type,
|
|
122
|
+
confidence: detection.confidence,
|
|
123
|
+
...(detection.position && {
|
|
124
|
+
position: {
|
|
125
|
+
start: detection.position.start,
|
|
126
|
+
end: detection.position.end,
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
})),
|
|
130
|
+
action: segment.piDetection.action,
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
...(segment.lineage && segment.lineage.length > 0 && {
|
|
134
|
+
lineage: segment.lineage.map(entry => {
|
|
135
|
+
if (entry.hasNotes() && entry.notes) {
|
|
136
|
+
return {
|
|
137
|
+
step: entry.step,
|
|
138
|
+
timestamp: entry.timestamp,
|
|
139
|
+
notes: entry.notes,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
step: entry.step,
|
|
144
|
+
timestamp: entry.timestamp,
|
|
145
|
+
}
|
|
146
|
+
}),
|
|
147
|
+
}),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return Object.freeze(serialized)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Serializes an array of segments
|
|
155
|
+
*
|
|
156
|
+
* @param segments - Array of segments to serialize
|
|
157
|
+
* @returns Array of SerializedSegment objects
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* const serialized = SerializationService.serializeSegments(segments)
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
static serializeSegments(segments: readonly Segment[]): SerializedSegment[] {
|
|
165
|
+
if (!Array.isArray(segments)) {
|
|
166
|
+
throw new TypeError('SerializationService.serializeSegments: segments must be an array')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return segments.map(segment => this.serializeSegment(segment))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Serializes a LineageEntry to a plain object
|
|
174
|
+
*
|
|
175
|
+
* @param entry - The lineage entry to serialize
|
|
176
|
+
* @returns Plain object representation
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* const serialized = SerializationService.serializeLineageEntry(entry)
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
static serializeLineageEntry(entry: LineageEntry): {
|
|
184
|
+
readonly step: string
|
|
185
|
+
readonly timestamp: number
|
|
186
|
+
readonly notes?: string
|
|
187
|
+
} {
|
|
188
|
+
if (!(entry instanceof LineageEntry)) {
|
|
189
|
+
throw new TypeError('SerializationService.serializeLineageEntry: entry must be a LineageEntry instance')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (entry.hasNotes() && entry.notes) {
|
|
193
|
+
return Object.freeze({
|
|
194
|
+
step: entry.step,
|
|
195
|
+
timestamp: entry.timestamp,
|
|
196
|
+
notes: entry.notes,
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
return Object.freeze({
|
|
200
|
+
step: entry.step,
|
|
201
|
+
timestamp: entry.timestamp,
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Serializes an array of lineage entries
|
|
207
|
+
*
|
|
208
|
+
* @param entries - Array of lineage entries to serialize
|
|
209
|
+
* @returns Array of serialized lineage entry objects
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* const serialized = SerializationService.serializeLineageEntries(entries)
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
static serializeLineageEntries(
|
|
217
|
+
entries: readonly LineageEntry[]
|
|
218
|
+
): Array<{
|
|
219
|
+
readonly step: string
|
|
220
|
+
readonly timestamp: number
|
|
221
|
+
readonly notes?: string
|
|
222
|
+
}> {
|
|
223
|
+
if (!Array.isArray(entries)) {
|
|
224
|
+
throw new TypeError('SerializationService.serializeLineageEntries: entries must be an array')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return entries.map(entry => this.serializeLineageEntry(entry))
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './OriginClassificationService';
|
|
2
|
+
export * from './PiDetectionService';
|
|
3
|
+
export * from './NormalizationService';
|
|
4
|
+
export * from './LineageService';
|
|
5
|
+
export * from './AnomalyService';
|
|
6
|
+
export * from './SerializationService';
|
|
7
|
+
export * from './PolicyService';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AnomalyAction, RiskScore } from "../../types";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export class AnomalyScore {
|
|
5
|
+
readonly score: RiskScore
|
|
6
|
+
readonly action: AnomalyAction
|
|
7
|
+
|
|
8
|
+
constructor(score: RiskScore, action: AnomalyAction) {
|
|
9
|
+
if (score < 0 || score > 1) {
|
|
10
|
+
throw new Error('Anomaly score must be a value between 0 and 1')
|
|
11
|
+
}
|
|
12
|
+
this.score = score
|
|
13
|
+
this.action = action
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
isHighRisk(): boolean {
|
|
17
|
+
return this.action === 'BLOCK'
|
|
18
|
+
}
|
|
19
|
+
isWarnRisk(): boolean {
|
|
20
|
+
return this.action === 'WARN'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|