@ai-pip/core 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 (43) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +165 -0
  3. package/package.json +79 -0
  4. package/src/cpe/envelope.ts +115 -0
  5. package/src/cpe/exceptions/EnvelopeError.ts +11 -0
  6. package/src/cpe/exceptions/index.ts +6 -0
  7. package/src/cpe/index.ts +35 -0
  8. package/src/cpe/types.ts +68 -0
  9. package/src/cpe/utils.ts +65 -0
  10. package/src/cpe/value-objects/Metadata.ts +78 -0
  11. package/src/cpe/value-objects/Nonce.ts +57 -0
  12. package/src/cpe/value-objects/Signature.ts +83 -0
  13. package/src/cpe/value-objects/index.ts +8 -0
  14. package/src/csl/classify.ts +77 -0
  15. package/src/csl/exceptions/ClassificationError.ts +16 -0
  16. package/src/csl/exceptions/SegmentationError.ts +19 -0
  17. package/src/csl/exceptions/index.ts +3 -0
  18. package/src/csl/index.ts +34 -0
  19. package/src/csl/lineage.ts +40 -0
  20. package/src/csl/segment.ts +100 -0
  21. package/src/csl/types.ts +113 -0
  22. package/src/csl/utils.ts +30 -0
  23. package/src/csl/value-objects/ContentHash.ts +48 -0
  24. package/src/csl/value-objects/LineageEntry.ts +33 -0
  25. package/src/csl/value-objects/Origin-map.ts +51 -0
  26. package/src/csl/value-objects/Origin.ts +52 -0
  27. package/src/csl/value-objects/TrustLevel.ts +33 -0
  28. package/src/csl/value-objects/index.ts +14 -0
  29. package/src/index.ts +18 -0
  30. package/src/isl/exceptions/SanitizationError.ts +14 -0
  31. package/src/isl/exceptions/index.ts +2 -0
  32. package/src/isl/index.ts +20 -0
  33. package/src/isl/sanitize.ts +93 -0
  34. package/src/isl/types.ts +87 -0
  35. package/src/isl/value-objects/AnomalyScore.ts +40 -0
  36. package/src/isl/value-objects/Pattern.ts +158 -0
  37. package/src/isl/value-objects/PiDetection.ts +92 -0
  38. package/src/isl/value-objects/PiDetectionResult.ts +129 -0
  39. package/src/isl/value-objects/PolicyRule.ts +117 -0
  40. package/src/isl/value-objects/index.ts +41 -0
  41. package/src/shared/index.ts +13 -0
  42. package/src/shared/lineage.ts +53 -0
  43. package/tsconfig.json +27 -0
@@ -0,0 +1,158 @@
1
+ import type { RiskScore } from '../types'
2
+
3
+ /**
4
+ * Pattern - tipo puro
5
+ */
6
+ export type Pattern = {
7
+ readonly pattern_type: string
8
+ readonly regex: RegExp
9
+ readonly base_confidence: RiskScore
10
+ readonly description: string
11
+ }
12
+
13
+ /**
14
+ * Constantes de seguridad para pattern matching
15
+ */
16
+ export const MAX_CONTENT_LENGTH = 10_000_000 // 10MB
17
+ export const MAX_PATTERN_LENGTH = 10_000
18
+ export const MAX_MATCHES = 10_000
19
+
20
+ /**
21
+ * Valida pattern_type - función pura
22
+ */
23
+ function validatePatternType(pattern_type: unknown): asserts pattern_type is string {
24
+ if (!pattern_type || typeof pattern_type !== 'string' || pattern_type.trim().length === 0) {
25
+ throw new TypeError('Pattern pattern_type must be a non-empty string')
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Valida regex - función pura
31
+ */
32
+ function validateRegex(regex: unknown): asserts regex is string | RegExp {
33
+ if (!regex || (typeof regex !== 'string' && !(regex instanceof RegExp))) {
34
+ throw new TypeError('Pattern regex must be a string or a RegExp')
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Valida base_confidence - función pura
40
+ */
41
+ function validateBaseConfidence(base_confidence: unknown): asserts base_confidence is number {
42
+ if (typeof base_confidence !== 'number' || !Number.isFinite(base_confidence)) {
43
+ throw new TypeError('Pattern base_confidence must be a valid number')
44
+ }
45
+
46
+ if (base_confidence < 0 || base_confidence > 1) {
47
+ throw new Error('Pattern base_confidence must be between 0 and 1')
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Valida description - función pura
53
+ */
54
+ function validateDescription(description: unknown): void {
55
+ if (description !== undefined && (typeof description !== 'string' || description.trim().length === 0)) {
56
+ throw new TypeError('Pattern description must be a non-empty string if provided')
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Compila regex string a RegExp - función pura
62
+ */
63
+ function compileRegexString(regex: string): RegExp {
64
+ try {
65
+ return new RegExp(regex, 'i')
66
+ } catch (error) {
67
+ const errorMessage = error instanceof Error ? error.message : String(error)
68
+ throw new TypeError(`Pattern regex must be a valid regular expression: ${regex}. Original error: ${errorMessage}`)
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Clona RegExp - función pura
74
+ */
75
+ function cloneRegExp(regex: RegExp): RegExp {
76
+ return new RegExp(regex.source, regex.flags)
77
+ }
78
+
79
+ /**
80
+ * Crea un Pattern - función pura
81
+ */
82
+ export function createPattern(
83
+ pattern_type: string,
84
+ regex: string | RegExp,
85
+ base_confidence: RiskScore,
86
+ description?: string
87
+ ): Pattern {
88
+ // Validar inputs
89
+ validatePatternType(pattern_type)
90
+ validateRegex(regex)
91
+ validateBaseConfidence(base_confidence)
92
+ validateDescription(description)
93
+
94
+ // Validar y procesar regex
95
+ const regexSource = typeof regex === 'string' ? regex : regex.source
96
+
97
+ if (regexSource.length > MAX_PATTERN_LENGTH) {
98
+ throw new Error(`Pattern regex source exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`)
99
+ }
100
+
101
+ // Normalizar regex a RegExp
102
+ const normalizedRegex = typeof regex === 'string'
103
+ ? compileRegexString(regex)
104
+ : cloneRegExp(regex)
105
+
106
+ return {
107
+ pattern_type: pattern_type.trim(),
108
+ regex: normalizedRegex,
109
+ base_confidence,
110
+ description: description?.trim() ?? ''
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Funciones puras para Pattern matching
116
+ */
117
+ export function matchesPattern(pattern: Pattern, content: string): boolean {
118
+ if (!content || typeof content !== 'string') {
119
+ throw new TypeError('Pattern.matches: content must be a non-empty string')
120
+ }
121
+
122
+ if (content.length > MAX_CONTENT_LENGTH) {
123
+ throw new Error(
124
+ `Pattern.matches: Content length (${content.length}) exceeds maximum allowed length (${MAX_CONTENT_LENGTH})`
125
+ )
126
+ }
127
+
128
+ return pattern.regex.test(content)
129
+ }
130
+
131
+ export function findMatch(pattern: Pattern, content: string): {
132
+ matched: string
133
+ position: { start: number; end: number }
134
+ } | null {
135
+ if (!content || typeof content !== 'string') {
136
+ throw new TypeError('Pattern.findMatch: content must be a non-empty string')
137
+ }
138
+
139
+ if (content.length > MAX_CONTENT_LENGTH) {
140
+ throw new Error(
141
+ `Pattern.findMatch: Content length (${content.length}) exceeds maximum allowed length (${MAX_CONTENT_LENGTH})`
142
+ )
143
+ }
144
+
145
+ const match = pattern.regex.exec(content)
146
+ if (match?.index === undefined) {
147
+ return null
148
+ }
149
+
150
+ return {
151
+ matched: match[0],
152
+ position: {
153
+ start: match.index,
154
+ end: match.index + match[0].length
155
+ }
156
+ }
157
+ }
158
+
@@ -0,0 +1,92 @@
1
+ import type { Position, RiskScore } from '../types'
2
+
3
+ /**
4
+ * PiDetection - tipo puro
5
+ */
6
+ export type PiDetection = {
7
+ readonly pattern_type: string
8
+ readonly matched_pattern: string
9
+ readonly position: Position
10
+ readonly confidence: RiskScore
11
+ }
12
+
13
+ /**
14
+ * Crea un PiDetection - función pura
15
+ */
16
+ export function createPiDetection(
17
+ pattern_type: string,
18
+ matched_pattern: string,
19
+ position: Position,
20
+ confidence: RiskScore
21
+ ): PiDetection {
22
+ // Validar pattern_type
23
+ if (!pattern_type || typeof pattern_type !== 'string' || pattern_type.trim().length === 0) {
24
+ throw new Error('PiDetection pattern_type must be a non-empty string')
25
+ }
26
+
27
+ // Validar matched_pattern
28
+ if (!matched_pattern || typeof matched_pattern !== 'string') {
29
+ throw new Error('PiDetection matched_pattern must be a non-empty string')
30
+ }
31
+
32
+ // Validar position
33
+ if (!position || typeof position !== 'object') {
34
+ throw new TypeError('PiDetection position must be an object with start and end properties')
35
+ }
36
+
37
+ if (typeof position.start !== 'number' || !Number.isFinite(position.start) || position.start < 0) {
38
+ throw new Error('PiDetection position.start must be a valid non-negative number')
39
+ }
40
+
41
+ if (typeof position.end !== 'number' || !Number.isFinite(position.end) || position.end < 0) {
42
+ throw new Error('PiDetection position.end must be a valid non-negative number')
43
+ }
44
+
45
+ if (position.end <= position.start) {
46
+ throw new Error('PiDetection position.end must be greater than position.start')
47
+ }
48
+
49
+ // Validar confidence
50
+ if (typeof confidence !== 'number' || !Number.isFinite(confidence)) {
51
+ throw new TypeError('PiDetection confidence must be a valid number')
52
+ }
53
+
54
+ if (confidence < 0 || confidence > 1) {
55
+ throw new Error('PiDetection confidence must be between 0 and 1')
56
+ }
57
+
58
+ // Validar que matched_pattern length coincide con position
59
+ const patternLength = position.end - position.start
60
+ if (matched_pattern.length !== patternLength) {
61
+ throw new Error(
62
+ `PiDetection matched_pattern length (${matched_pattern.length}) does not match position range (${patternLength})`
63
+ )
64
+ }
65
+
66
+ return {
67
+ pattern_type: pattern_type.trim(),
68
+ matched_pattern,
69
+ position: Object.freeze({ ...position }),
70
+ confidence
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Funciones puras para PiDetection
76
+ */
77
+ export function getDetectionLength(detection: PiDetection): number {
78
+ return detection.position.end - detection.position.start
79
+ }
80
+
81
+ export function isHighConfidence(detection: PiDetection): boolean {
82
+ return detection.confidence >= 0.7
83
+ }
84
+
85
+ export function isMediumConfidence(detection: PiDetection): boolean {
86
+ return detection.confidence >= 0.3 && detection.confidence < 0.7
87
+ }
88
+
89
+ export function isLowConfidence(detection: PiDetection): boolean {
90
+ return detection.confidence < 0.3
91
+ }
92
+
@@ -0,0 +1,129 @@
1
+ import type { AnomalyAction, RiskScore } from '../types'
2
+ import type { PiDetection } from './PiDetection'
3
+
4
+ /**
5
+ * PiDetectionResult - tipo puro
6
+ */
7
+ export type PiDetectionResult = {
8
+ readonly detections: readonly PiDetection[]
9
+ readonly score: RiskScore
10
+ readonly action: AnomalyAction
11
+ readonly patterns: readonly string[]
12
+ readonly detected: boolean
13
+ }
14
+
15
+ /**
16
+ * Calcula score agregado desde detecciones individuales - función pura
17
+ */
18
+ function calculateAggregatedScore(detections: readonly PiDetection[]): RiskScore {
19
+ if (detections.length === 0) {
20
+ return 0
21
+ }
22
+
23
+ if (detections.length === 1) {
24
+ const first = detections[0]
25
+ if (!first) return 0
26
+ return first.confidence
27
+ }
28
+
29
+ // Usar probabilidad complementaria: 1 - (1-c1)*(1-c2)*...
30
+ let complementaryProduct = 1
31
+ for (const detection of detections) {
32
+ complementaryProduct *= (1 - detection.confidence)
33
+ }
34
+
35
+ const aggregatedScore = 1 - complementaryProduct
36
+ return Math.max(0, Math.min(1, aggregatedScore))
37
+ }
38
+
39
+ /**
40
+ * Determina acción basada en score - función pura
41
+ */
42
+ function determineActionFromScore(score: RiskScore): AnomalyAction {
43
+ if (score >= 0.7) {
44
+ return 'BLOCK'
45
+ } else if (score >= 0.3) {
46
+ return 'WARN'
47
+ } else {
48
+ return 'ALLOW'
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Crea un PiDetectionResult - función pura
54
+ */
55
+ export function createPiDetectionResult(
56
+ detections: readonly PiDetection[],
57
+ action?: AnomalyAction
58
+ ): PiDetectionResult {
59
+ // Validar detections array
60
+ if (!Array.isArray(detections)) {
61
+ throw new TypeError('PiDetectionResult detections must be an array')
62
+ }
63
+
64
+ // Calcular score agregado
65
+ const score = calculateAggregatedScore(detections)
66
+
67
+ // Determinar acción (o usar la proporcionada)
68
+ const finalAction = action ?? determineActionFromScore(score)
69
+
70
+ // Validar acción
71
+ if (!['ALLOW', 'WARN', 'BLOCK'].includes(finalAction)) {
72
+ throw new Error(`Invalid AnomalyAction: ${finalAction}. Must be one of: ALLOW, WARN, BLOCK`)
73
+ }
74
+
75
+ // Validar que la acción coincide con el score calculado
76
+ const expectedAction = determineActionFromScore(score)
77
+ if (finalAction !== expectedAction) {
78
+ throw new Error(
79
+ `PiDetectionResult action mismatch. Calculated score ${score} requires action '${expectedAction}', but '${finalAction}' was provided`
80
+ )
81
+ }
82
+
83
+ // Derivar patterns array
84
+ const patterns: readonly string[] = detections.map((detection: PiDetection) => detection.pattern_type)
85
+
86
+ const result: PiDetectionResult = {
87
+ detections: Object.freeze(Array.from(detections)) as readonly PiDetection[],
88
+ score,
89
+ action: finalAction,
90
+ patterns: Object.freeze(Array.from(patterns)),
91
+ detected: detections.length > 0
92
+ }
93
+
94
+ return result
95
+ }
96
+
97
+ /**
98
+ * Funciones puras para PiDetectionResult
99
+ */
100
+ export function hasDetections(result: PiDetectionResult): boolean {
101
+ return result.detected
102
+ }
103
+
104
+ // shouldBlock y shouldWarn NO son core - son decisiones que van a ModelGateway
105
+ // El core solo produce señales (score, action), no decide acciones finales
106
+
107
+ export function getDetectionCount(result: PiDetectionResult): number {
108
+ return result.detections.length
109
+ }
110
+
111
+ export function getDetectionsByType(
112
+ result: PiDetectionResult,
113
+ pattern_type: string
114
+ ): readonly PiDetection[] {
115
+ return result.detections.filter(detection => detection.pattern_type === pattern_type)
116
+ }
117
+
118
+ export function getHighestConfidenceDetection(
119
+ result: PiDetectionResult
120
+ ): PiDetection | undefined {
121
+ if (result.detections.length === 0) {
122
+ return undefined
123
+ }
124
+
125
+ return result.detections.reduce((highest, current) => {
126
+ return current.confidence > highest.confidence ? current : highest
127
+ })
128
+ }
129
+
@@ -0,0 +1,117 @@
1
+ import type { BlockedIntent, ImmutableInstruction, ProtectedRole, SensitiveScope } from '../types'
2
+
3
+ /**
4
+ * RoleProtectionConfig - configuración de protección de roles
5
+ */
6
+ export type RoleProtectionConfig = {
7
+ readonly protectedRoles: readonly ProtectedRole[]
8
+ readonly immutableInstructions: readonly ImmutableInstruction[]
9
+ }
10
+
11
+ /**
12
+ * ContextLeakPreventionConfig - configuración de prevención de fuga de contexto
13
+ */
14
+ export type ContextLeakPreventionConfig = {
15
+ readonly enabled: boolean
16
+ readonly blockMetadataExposure: boolean
17
+ readonly sanitizeInternalReferences: boolean
18
+ }
19
+
20
+ /**
21
+ * PolicyRule - tipo puro
22
+ */
23
+ export type PolicyRule = {
24
+ readonly version: string
25
+ readonly blockedIntents: readonly BlockedIntent[]
26
+ readonly sensitiveScope: readonly SensitiveScope[]
27
+ readonly roleProtection: RoleProtectionConfig
28
+ readonly contextLeakPrevention: ContextLeakPreventionConfig
29
+ }
30
+
31
+ /**
32
+ * Crea un PolicyRule - función pura
33
+ */
34
+ export function createPolicyRule(
35
+ version: string,
36
+ blockedIntents: readonly BlockedIntent[],
37
+ sensitiveScope: readonly SensitiveScope[],
38
+ roleProtection: RoleProtectionConfig,
39
+ contextLeakPrevention: ContextLeakPreventionConfig
40
+ ): PolicyRule {
41
+ if (!version || typeof version !== 'string' || version.trim().length === 0) {
42
+ throw new Error('PolicyRule version must be a non-empty string')
43
+ }
44
+
45
+ if (!Array.isArray(blockedIntents)) {
46
+ throw new TypeError('PolicyRule blockedIntents must be an array')
47
+ }
48
+
49
+ if (!Array.isArray(sensitiveScope)) {
50
+ throw new TypeError('PolicyRule sensitiveScope must be an array')
51
+ }
52
+
53
+ if (!roleProtection || typeof roleProtection !== 'object') {
54
+ throw new TypeError('PolicyRule roleProtection must be an object')
55
+ }
56
+
57
+ if (!Array.isArray(roleProtection.protectedRoles)) {
58
+ throw new TypeError('PolicyRule roleProtection.protectedRoles must be an array')
59
+ }
60
+
61
+ if (!Array.isArray(roleProtection.immutableInstructions)) {
62
+ throw new TypeError('PolicyRule roleProtection.immutableInstructions must be an array')
63
+ }
64
+
65
+ if (!contextLeakPrevention || typeof contextLeakPrevention !== 'object') {
66
+ throw new TypeError('PolicyRule contextLeakPrevention must be an object')
67
+ }
68
+
69
+ if (typeof contextLeakPrevention.enabled !== 'boolean') {
70
+ throw new TypeError('PolicyRule contextLeakPrevention.enabled must be a boolean')
71
+ }
72
+
73
+ if (typeof contextLeakPrevention.blockMetadataExposure !== 'boolean') {
74
+ throw new TypeError('PolicyRule contextLeakPrevention.blockMetadataExposure must be a boolean')
75
+ }
76
+
77
+ if (typeof contextLeakPrevention.sanitizeInternalReferences !== 'boolean') {
78
+ throw new TypeError('PolicyRule contextLeakPrevention.sanitizeInternalReferences must be a boolean')
79
+ }
80
+
81
+ const result: PolicyRule = {
82
+ version: version.trim(),
83
+ blockedIntents: Object.freeze(Array.from(blockedIntents)) as readonly BlockedIntent[],
84
+ sensitiveScope: Object.freeze(Array.from(sensitiveScope)) as readonly SensitiveScope[],
85
+ roleProtection: {
86
+ protectedRoles: Object.freeze(Array.from(roleProtection.protectedRoles)) as readonly ProtectedRole[],
87
+ immutableInstructions: Object.freeze(Array.from(roleProtection.immutableInstructions)) as readonly ImmutableInstruction[]
88
+ },
89
+ contextLeakPrevention: Object.freeze({ ...contextLeakPrevention })
90
+ }
91
+
92
+ return result
93
+ }
94
+
95
+ /**
96
+ * Funciones puras para PolicyRule
97
+ */
98
+ export function isIntentBlocked(policy: PolicyRule, intent: string): boolean {
99
+ return policy.blockedIntents.includes(intent)
100
+ }
101
+
102
+ export function isScopeSensitive(policy: PolicyRule, scope: string): boolean {
103
+ return policy.sensitiveScope.includes(scope)
104
+ }
105
+
106
+ export function isRoleProtected(policy: PolicyRule, role: string): boolean {
107
+ return policy.roleProtection.protectedRoles.includes(role)
108
+ }
109
+
110
+ export function isInstructionImmutable(policy: PolicyRule, instruction: string): boolean {
111
+ return policy.roleProtection.immutableInstructions.includes(instruction)
112
+ }
113
+
114
+ export function isContextLeakPreventionEnabled(policy: PolicyRule): boolean {
115
+ return policy.contextLeakPrevention.enabled
116
+ }
117
+
@@ -0,0 +1,41 @@
1
+ // Tipos
2
+ export type { PiDetection } from './PiDetection'
3
+ export type { PiDetectionResult } from './PiDetectionResult'
4
+ export type { AnomalyScore } from './AnomalyScore'
5
+ export type { Pattern } from './Pattern'
6
+ // Funciones de creación
7
+ export {
8
+ createPiDetection,
9
+ getDetectionLength,
10
+ isHighConfidence,
11
+ isMediumConfidence,
12
+ isLowConfidence
13
+ } from './PiDetection'
14
+
15
+ export {
16
+ createPiDetectionResult,
17
+ hasDetections,
18
+ getDetectionCount,
19
+ getDetectionsByType,
20
+ getHighestConfidenceDetection
21
+ } from './PiDetectionResult'
22
+
23
+ export {
24
+ createAnomalyScore,
25
+ isHighRisk,
26
+ isWarnRisk,
27
+ isLowRisk
28
+ } from './AnomalyScore'
29
+
30
+ export {
31
+ createPattern,
32
+ matchesPattern,
33
+ findMatch,
34
+ MAX_CONTENT_LENGTH,
35
+ MAX_PATTERN_LENGTH,
36
+ MAX_MATCHES
37
+ } from './Pattern'
38
+
39
+ // PolicyRule NO es core - va a ModelGateway/Policy Engine
40
+ // Se mantiene el tipo para compatibilidad pero las funciones de decisión no son core
41
+
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared utilities for all layers - funciones puras compartidas
3
+ *
4
+ * @remarks
5
+ * Solo funciones básicas de manejo de linaje.
6
+ * Auditoría y análisis avanzado van al SDK.
7
+ */
8
+
9
+ // Lineage básico
10
+ export * from './lineage'
11
+
12
+ // Funciones de auditoría NO son core - van al SDK/tooling
13
+ // El core solo preserva linaje, no lo analiza
@@ -0,0 +1,53 @@
1
+ import type { LineageEntry } from '@/csl/value-objects'
2
+
3
+ /**
4
+ * Lineage global - funciones puras para manejar linaje entre capas
5
+ *
6
+ * @remarks
7
+ * El linaje es compartido entre todas las capas (CSL, ISL, CPE, etc.)
8
+ * Cada capa puede agregar entradas al linaje de un segmento.
9
+ */
10
+
11
+ /**
12
+ * Agrega una entrada de linaje a un array existente - función pura
13
+ *
14
+ * @param lineage - Array de entradas de linaje existentes
15
+ * @param entry - Nueva entrada a agregar
16
+ * @returns Nuevo array con la entrada agregada
17
+ */
18
+ export function addLineageEntry(
19
+ lineage: readonly LineageEntry[],
20
+ entry: LineageEntry
21
+ ): LineageEntry[] {
22
+ return [...lineage, entry]
23
+ }
24
+
25
+ /**
26
+ * Agrega múltiples entradas de linaje - función pura
27
+ */
28
+ export function addLineageEntries(
29
+ lineage: readonly LineageEntry[],
30
+ entries: readonly LineageEntry[]
31
+ ): LineageEntry[] {
32
+ return [...lineage, ...entries]
33
+ }
34
+
35
+ /**
36
+ * Filtra entradas de linaje por step - función pura
37
+ */
38
+ export function filterLineageByStep(
39
+ lineage: readonly LineageEntry[],
40
+ step: string
41
+ ): LineageEntry[] {
42
+ return lineage.filter(entry => entry.step === step)
43
+ }
44
+
45
+ /**
46
+ * Obtiene la última entrada de linaje - función pura
47
+ */
48
+ export function getLastLineageEntry(
49
+ lineage: readonly LineageEntry[]
50
+ ): LineageEntry | undefined {
51
+ return lineage.length > 0 ? lineage.at(-1) : undefined
52
+ }
53
+
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+
7
+ "outDir": "dist",
8
+ "baseUrl": ".",
9
+ "paths": {
10
+ "@/*": ["src/*"]
11
+ },
12
+
13
+ "strict": true,
14
+
15
+ "declaration": true,
16
+ "emitDeclarationOnly": true,
17
+ "declarationMap": false,
18
+
19
+ "types": ["node"],
20
+
21
+ "noUncheckedIndexedAccess": true,
22
+ "exactOptionalPropertyTypes": true,
23
+
24
+ "skipLibCheck": true
25
+ },
26
+ "include": ["src/**/*", "test/**/*"]
27
+ }