tlc-claude-code 1.3.0 → 1.4.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.
- package/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +0 -16
- package/dashboard/dist/components/WorkspacePane.d.ts +1 -1
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Flow Documenter
|
|
3
|
+
*
|
|
4
|
+
* Documents data flows through the system including:
|
|
5
|
+
* - Data source identification (user input, APIs, databases)
|
|
6
|
+
* - Data transformation tracking
|
|
7
|
+
* - Data destination documentation
|
|
8
|
+
* - Sensitivity classification
|
|
9
|
+
* - Mermaid flow diagram generation
|
|
10
|
+
* - Retention policy documentation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Patterns for identifying data sources in code
|
|
15
|
+
*/
|
|
16
|
+
const SOURCE_PATTERNS = {
|
|
17
|
+
user_input: [
|
|
18
|
+
/new FormData\s*\(/gi,
|
|
19
|
+
/document\.getElementById\s*\(['"]\w+['"]\)\.value/gi,
|
|
20
|
+
/document\.querySelector\s*\([^)]+\)\.value/gi,
|
|
21
|
+
/event\.target\.elements\.\w+\.value/gi,
|
|
22
|
+
/\.(value|files|checked)\s*[;,)]/gi,
|
|
23
|
+
/req\.body/gi,
|
|
24
|
+
/req\.query/gi,
|
|
25
|
+
/req\.params/gi,
|
|
26
|
+
],
|
|
27
|
+
api: [
|
|
28
|
+
/app\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
29
|
+
/router\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
30
|
+
/fetch\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
31
|
+
/axios\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
32
|
+
/\/api\/\w+/gi,
|
|
33
|
+
],
|
|
34
|
+
database: [
|
|
35
|
+
/new Database\s*\(/gi,
|
|
36
|
+
/mysql\.createPool/gi,
|
|
37
|
+
/mongoose\.connect/gi,
|
|
38
|
+
/new Pool\s*\(/gi,
|
|
39
|
+
/createConnection/gi,
|
|
40
|
+
/SELECT\s+.+\s+FROM\s+/gi,
|
|
41
|
+
/INSERT\s+INTO\s+/gi,
|
|
42
|
+
/UPDATE\s+\w+\s+SET/gi,
|
|
43
|
+
/DELETE\s+FROM\s+/gi,
|
|
44
|
+
/pg\.connect/gi,
|
|
45
|
+
/knex\s*\(/gi,
|
|
46
|
+
/prisma\./gi,
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Sensitivity classification rules
|
|
52
|
+
*/
|
|
53
|
+
const SENSITIVITY_RULES = {
|
|
54
|
+
critical: [
|
|
55
|
+
'password',
|
|
56
|
+
'secret',
|
|
57
|
+
'ssn',
|
|
58
|
+
'social_security',
|
|
59
|
+
'credit_card',
|
|
60
|
+
'card_number',
|
|
61
|
+
'cvv',
|
|
62
|
+
'pin',
|
|
63
|
+
'private_key',
|
|
64
|
+
'api_key',
|
|
65
|
+
'apikey',
|
|
66
|
+
'access_token',
|
|
67
|
+
'refresh_token',
|
|
68
|
+
'auth_token',
|
|
69
|
+
],
|
|
70
|
+
high: [
|
|
71
|
+
'email',
|
|
72
|
+
'phone',
|
|
73
|
+
'address',
|
|
74
|
+
'date_of_birth',
|
|
75
|
+
'dob',
|
|
76
|
+
'ip_address',
|
|
77
|
+
'location',
|
|
78
|
+
'gps',
|
|
79
|
+
'biometric',
|
|
80
|
+
],
|
|
81
|
+
medium: [
|
|
82
|
+
'name',
|
|
83
|
+
'first_name',
|
|
84
|
+
'last_name',
|
|
85
|
+
'username',
|
|
86
|
+
'user_id',
|
|
87
|
+
'account_id',
|
|
88
|
+
'order_id',
|
|
89
|
+
'transaction_id',
|
|
90
|
+
],
|
|
91
|
+
low: [
|
|
92
|
+
'timestamp',
|
|
93
|
+
'page_number',
|
|
94
|
+
'sort_order',
|
|
95
|
+
'page_views',
|
|
96
|
+
'count',
|
|
97
|
+
'total',
|
|
98
|
+
'quantity',
|
|
99
|
+
'status',
|
|
100
|
+
'type',
|
|
101
|
+
'category',
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Default retention policies by sensitivity level
|
|
107
|
+
*/
|
|
108
|
+
const DEFAULT_RETENTION = {
|
|
109
|
+
critical: '1 year (or until account deletion)',
|
|
110
|
+
high: '3 years',
|
|
111
|
+
medium: '5 years',
|
|
112
|
+
low: '7 years',
|
|
113
|
+
public: 'Indefinite',
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Identify data sources in code
|
|
118
|
+
* @param {string} code - Source code to analyze
|
|
119
|
+
* @returns {Array} Array of identified data sources
|
|
120
|
+
*/
|
|
121
|
+
export function identifyDataSources(code) {
|
|
122
|
+
const sources = [];
|
|
123
|
+
const seen = new Set();
|
|
124
|
+
|
|
125
|
+
for (const [type, patterns] of Object.entries(SOURCE_PATTERNS)) {
|
|
126
|
+
for (const pattern of patterns) {
|
|
127
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
128
|
+
let match;
|
|
129
|
+
|
|
130
|
+
while ((match = regex.exec(code)) !== null) {
|
|
131
|
+
const name = extractSourceName(match, type);
|
|
132
|
+
const key = `${type}:${name}`;
|
|
133
|
+
|
|
134
|
+
if (!seen.has(key)) {
|
|
135
|
+
seen.add(key);
|
|
136
|
+
sources.push({
|
|
137
|
+
type,
|
|
138
|
+
name,
|
|
139
|
+
match: match[0],
|
|
140
|
+
location: match.index,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return sources;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Extract a meaningful name from a regex match
|
|
152
|
+
* @param {Array} match - Regex match result
|
|
153
|
+
* @param {string} type - Source type
|
|
154
|
+
* @returns {string} Extracted name
|
|
155
|
+
*/
|
|
156
|
+
function extractSourceName(match, type) {
|
|
157
|
+
const fullMatch = match[0];
|
|
158
|
+
|
|
159
|
+
if (type === 'api') {
|
|
160
|
+
// Extract URL path from API patterns
|
|
161
|
+
const urlMatch = fullMatch.match(/['"`]([^'"`]+)['"`]/);
|
|
162
|
+
if (urlMatch) {
|
|
163
|
+
return urlMatch[1];
|
|
164
|
+
}
|
|
165
|
+
return fullMatch;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (type === 'user_input') {
|
|
169
|
+
if (fullMatch.includes('FormData')) {
|
|
170
|
+
return 'FormData';
|
|
171
|
+
}
|
|
172
|
+
if (fullMatch.includes('getElementById')) {
|
|
173
|
+
const idMatch = fullMatch.match(/getElementById\s*\(['"](\w+)['"]\)/);
|
|
174
|
+
return idMatch ? `form_${idMatch[1]}` : 'form_element';
|
|
175
|
+
}
|
|
176
|
+
if (fullMatch.includes('querySelector')) {
|
|
177
|
+
return 'form_selector';
|
|
178
|
+
}
|
|
179
|
+
if (fullMatch.includes('req.body')) {
|
|
180
|
+
return 'request_body';
|
|
181
|
+
}
|
|
182
|
+
if (fullMatch.includes('req.query')) {
|
|
183
|
+
return 'query_params';
|
|
184
|
+
}
|
|
185
|
+
if (fullMatch.includes('req.params')) {
|
|
186
|
+
return 'url_params';
|
|
187
|
+
}
|
|
188
|
+
return 'user_input';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (type === 'database') {
|
|
192
|
+
if (fullMatch.includes('SELECT')) {
|
|
193
|
+
const tableMatch = fullMatch.match(/FROM\s+(\w+)/i);
|
|
194
|
+
return tableMatch ? `query:${tableMatch[1]}` : 'sql_query';
|
|
195
|
+
}
|
|
196
|
+
if (fullMatch.includes('INSERT')) {
|
|
197
|
+
const tableMatch = fullMatch.match(/INTO\s+(\w+)/i);
|
|
198
|
+
return tableMatch ? `insert:${tableMatch[1]}` : 'sql_insert';
|
|
199
|
+
}
|
|
200
|
+
if (fullMatch.includes('mongoose')) {
|
|
201
|
+
return 'mongodb';
|
|
202
|
+
}
|
|
203
|
+
if (fullMatch.includes('mysql')) {
|
|
204
|
+
return 'mysql';
|
|
205
|
+
}
|
|
206
|
+
if (fullMatch.includes('Pool') || fullMatch.includes('pg')) {
|
|
207
|
+
return 'postgresql';
|
|
208
|
+
}
|
|
209
|
+
return 'database';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return fullMatch.substring(0, 50);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Track a data flow through the system
|
|
217
|
+
* @param {Object} flowDefinition - Flow definition
|
|
218
|
+
* @returns {Object} Tracked flow with metadata
|
|
219
|
+
*/
|
|
220
|
+
export function trackDataFlow(flowDefinition) {
|
|
221
|
+
const {
|
|
222
|
+
id = generateFlowId(),
|
|
223
|
+
name,
|
|
224
|
+
source,
|
|
225
|
+
steps = [],
|
|
226
|
+
destination,
|
|
227
|
+
dataTypes = [],
|
|
228
|
+
sensitivity,
|
|
229
|
+
retention,
|
|
230
|
+
} = flowDefinition;
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
id,
|
|
234
|
+
name,
|
|
235
|
+
source,
|
|
236
|
+
transformations: steps.map((step, index) => ({
|
|
237
|
+
...step,
|
|
238
|
+
order: index + 1,
|
|
239
|
+
})),
|
|
240
|
+
destination,
|
|
241
|
+
dataTypes,
|
|
242
|
+
sensitivity: sensitivity || 'unclassified',
|
|
243
|
+
retention: retention || null,
|
|
244
|
+
createdAt: new Date().toISOString(),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generate a unique flow ID
|
|
250
|
+
* @returns {string} Generated ID
|
|
251
|
+
*/
|
|
252
|
+
function generateFlowId() {
|
|
253
|
+
return `flow-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Classify data types by sensitivity level
|
|
258
|
+
* @param {Array} dataTypes - Array of data type objects with name and optional value
|
|
259
|
+
* @returns {Array} Array of classified data types
|
|
260
|
+
*/
|
|
261
|
+
export function classifyData(dataTypes) {
|
|
262
|
+
return dataTypes.map((dataType) => {
|
|
263
|
+
const name = typeof dataType === 'string' ? dataType : dataType.name;
|
|
264
|
+
const lowerName = name.toLowerCase();
|
|
265
|
+
|
|
266
|
+
let sensitivity = 'low';
|
|
267
|
+
let reason = 'No sensitive patterns detected';
|
|
268
|
+
|
|
269
|
+
// Check against sensitivity rules
|
|
270
|
+
for (const [level, keywords] of Object.entries(SENSITIVITY_RULES)) {
|
|
271
|
+
if (keywords.some((keyword) => lowerName.includes(keyword))) {
|
|
272
|
+
sensitivity = level;
|
|
273
|
+
reason = `Matches ${level} sensitivity pattern: ${keywords.find((k) => lowerName.includes(k))}`;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Special handling for PII that might have different labels
|
|
279
|
+
if (sensitivity === 'high' && isPII(lowerName)) {
|
|
280
|
+
reason = `Personal Identifiable Information (PII): ${name}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
name,
|
|
285
|
+
value: dataType.value,
|
|
286
|
+
sensitivity,
|
|
287
|
+
reason,
|
|
288
|
+
classification: getSensitivityLabel(sensitivity),
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Check if a data type is PII
|
|
295
|
+
* @param {string} name - Data type name (lowercase)
|
|
296
|
+
* @returns {boolean} True if PII
|
|
297
|
+
*/
|
|
298
|
+
function isPII(name) {
|
|
299
|
+
const piiPatterns = ['email', 'phone', 'address', 'name', 'dob', 'birth'];
|
|
300
|
+
return piiPatterns.some((pattern) => name.includes(pattern));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get a human-readable sensitivity label
|
|
305
|
+
* @param {string} level - Sensitivity level
|
|
306
|
+
* @returns {string} Human-readable label
|
|
307
|
+
*/
|
|
308
|
+
function getSensitivityLabel(level) {
|
|
309
|
+
const labels = {
|
|
310
|
+
critical: 'Critical - Requires encryption and strict access control',
|
|
311
|
+
high: 'High - Contains PII, requires protection',
|
|
312
|
+
medium: 'Medium - Internal use, standard protection',
|
|
313
|
+
low: 'Low - Non-sensitive data',
|
|
314
|
+
public: 'Public - Can be freely shared',
|
|
315
|
+
};
|
|
316
|
+
return labels[level] || 'Unclassified';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Generate a Mermaid flow diagram for a data flow
|
|
321
|
+
* @param {Object} flow - Data flow object
|
|
322
|
+
* @returns {string} Mermaid diagram syntax
|
|
323
|
+
*/
|
|
324
|
+
export function generateFlowDiagram(flow) {
|
|
325
|
+
const lines = ['flowchart LR'];
|
|
326
|
+
const nodeIds = [];
|
|
327
|
+
|
|
328
|
+
// Add source node
|
|
329
|
+
const sourceId = 'source';
|
|
330
|
+
const sourceShape = getNodeShape(flow.source.type, flow.source.name);
|
|
331
|
+
lines.push(` ${sourceId}${sourceShape}`);
|
|
332
|
+
nodeIds.push(sourceId);
|
|
333
|
+
|
|
334
|
+
// Add transformation nodes
|
|
335
|
+
if (flow.transformations && flow.transformations.length > 0) {
|
|
336
|
+
flow.transformations.forEach((transform, index) => {
|
|
337
|
+
const nodeId = `step${index + 1}`;
|
|
338
|
+
const label = transform.step || transform.description;
|
|
339
|
+
lines.push(` ${nodeId}[${label}]`);
|
|
340
|
+
nodeIds.push(nodeId);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Add destination node
|
|
345
|
+
const destId = 'dest';
|
|
346
|
+
const destShape = getNodeShape(flow.destination.type, flow.destination.name);
|
|
347
|
+
lines.push(` ${destId}${destShape}`);
|
|
348
|
+
nodeIds.push(destId);
|
|
349
|
+
|
|
350
|
+
// Add connections
|
|
351
|
+
for (let i = 0; i < nodeIds.length - 1; i++) {
|
|
352
|
+
lines.push(` ${nodeIds[i]} --> ${nodeIds[i + 1]}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return lines.join('\n');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Get the Mermaid node shape for a given type
|
|
360
|
+
* @param {string} type - Node type (user_input, api, database)
|
|
361
|
+
* @param {string} name - Node name/label
|
|
362
|
+
* @returns {string} Mermaid node syntax
|
|
363
|
+
*/
|
|
364
|
+
function getNodeShape(type, name) {
|
|
365
|
+
switch (type) {
|
|
366
|
+
case 'database':
|
|
367
|
+
return `[(${name})]`;
|
|
368
|
+
case 'api':
|
|
369
|
+
return `{{${name}}}`;
|
|
370
|
+
case 'user_input':
|
|
371
|
+
default:
|
|
372
|
+
return `[${name}]`;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Document retention policy for a data flow
|
|
378
|
+
* @param {Object} dataFlow - Data flow object
|
|
379
|
+
* @param {Object} retentionPolicy - Optional custom retention policy
|
|
380
|
+
* @returns {Object} Data flow with retention documentation
|
|
381
|
+
*/
|
|
382
|
+
export function documentRetention(dataFlow, retentionPolicy = {}) {
|
|
383
|
+
const {
|
|
384
|
+
default: defaultRetention = DEFAULT_RETENTION[dataFlow.sensitivity] || '5 years',
|
|
385
|
+
byType = {},
|
|
386
|
+
legal = null,
|
|
387
|
+
deletionProcedure = 'Standard secure deletion',
|
|
388
|
+
} = retentionPolicy;
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
...dataFlow,
|
|
392
|
+
retention: {
|
|
393
|
+
default: defaultRetention,
|
|
394
|
+
byType,
|
|
395
|
+
legal,
|
|
396
|
+
deletionProcedure,
|
|
397
|
+
lastReviewed: new Date().toISOString(),
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get inventory of all data types across flows
|
|
404
|
+
* @param {DataFlowDocumenter} documenter - Documenter instance
|
|
405
|
+
* @returns {Array} Array of unique data types with metadata
|
|
406
|
+
*/
|
|
407
|
+
export function getDataInventory(documenter) {
|
|
408
|
+
const inventory = new Map();
|
|
409
|
+
|
|
410
|
+
for (const flow of documenter.getFlows()) {
|
|
411
|
+
for (const dataType of flow.dataTypes || []) {
|
|
412
|
+
const name = typeof dataType === 'string' ? dataType : dataType.name;
|
|
413
|
+
|
|
414
|
+
if (!inventory.has(name)) {
|
|
415
|
+
const classification = classifyData([{ name }])[0];
|
|
416
|
+
inventory.set(name, {
|
|
417
|
+
name,
|
|
418
|
+
sensitivity: classification.sensitivity,
|
|
419
|
+
reason: classification.reason,
|
|
420
|
+
usedInFlows: [],
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
inventory.get(name).usedInFlows.push(flow.id);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return Array.from(inventory.values());
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Get lineage information for a specific data type
|
|
433
|
+
* @param {DataFlowDocumenter} documenter - Documenter instance
|
|
434
|
+
* @param {string} dataTypeName - Name of the data type to trace
|
|
435
|
+
* @returns {Object} Lineage information
|
|
436
|
+
*/
|
|
437
|
+
export function getDataLineage(documenter, dataTypeName) {
|
|
438
|
+
const flows = documenter.getFlows().filter((flow) => {
|
|
439
|
+
const dataTypes = flow.dataTypes || [];
|
|
440
|
+
return dataTypes.some((dt) => {
|
|
441
|
+
const name = typeof dt === 'string' ? dt : dt.name;
|
|
442
|
+
return name === dataTypeName;
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const path = [];
|
|
447
|
+
if (flows.length > 0) {
|
|
448
|
+
// Build path from flows
|
|
449
|
+
for (const flow of flows) {
|
|
450
|
+
if (!path.includes(flow.source.name)) {
|
|
451
|
+
path.push(flow.source.name);
|
|
452
|
+
}
|
|
453
|
+
for (const transform of flow.transformations || []) {
|
|
454
|
+
const stepName = transform.step || transform.description;
|
|
455
|
+
if (!path.includes(stepName)) {
|
|
456
|
+
path.push(stepName);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (!path.includes(flow.destination.name)) {
|
|
460
|
+
path.push(flow.destination.name);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
dataType: dataTypeName,
|
|
467
|
+
flows,
|
|
468
|
+
path: path.join(' -> '),
|
|
469
|
+
totalFlows: flows.length,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Export data flow report in various formats
|
|
475
|
+
* @param {DataFlowDocumenter} documenter - Documenter instance
|
|
476
|
+
* @param {Object} options - Export options
|
|
477
|
+
* @returns {Object|string} Report in specified format
|
|
478
|
+
*/
|
|
479
|
+
export function exportDataFlowReport(documenter, options = {}) {
|
|
480
|
+
const { format = 'json', includeDiagrams = false } = options;
|
|
481
|
+
|
|
482
|
+
const flows = documenter.getFlows();
|
|
483
|
+
const inventory = getDataInventory(documenter);
|
|
484
|
+
|
|
485
|
+
// Build sensitivity summary
|
|
486
|
+
const sensitivitySummary = {
|
|
487
|
+
critical: inventory.filter((d) => d.sensitivity === 'critical').length,
|
|
488
|
+
high: inventory.filter((d) => d.sensitivity === 'high').length,
|
|
489
|
+
medium: inventory.filter((d) => d.sensitivity === 'medium').length,
|
|
490
|
+
low: inventory.filter((d) => d.sensitivity === 'low').length,
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// Build diagrams if requested
|
|
494
|
+
const diagrams = {};
|
|
495
|
+
if (includeDiagrams) {
|
|
496
|
+
for (const flow of flows) {
|
|
497
|
+
diagrams[flow.id] = generateFlowDiagram(flow);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const report = {
|
|
502
|
+
title: 'Data Flow Report',
|
|
503
|
+
generatedAt: new Date().toISOString(),
|
|
504
|
+
flows,
|
|
505
|
+
dataInventory: inventory,
|
|
506
|
+
sensitivitySummary,
|
|
507
|
+
diagrams: includeDiagrams ? diagrams : undefined,
|
|
508
|
+
totalFlows: flows.length,
|
|
509
|
+
totalDataTypes: inventory.length,
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
if (format === 'markdown') {
|
|
513
|
+
return generateMarkdownReport(report);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (format === 'compliance') {
|
|
517
|
+
return {
|
|
518
|
+
...report,
|
|
519
|
+
complianceInfo: {
|
|
520
|
+
gdprRelevant: inventory.some((d) => d.sensitivity === 'critical' || d.sensitivity === 'high'),
|
|
521
|
+
piiCount: inventory.filter((d) => d.sensitivity === 'high').length,
|
|
522
|
+
sensitiveCount: inventory.filter((d) => d.sensitivity === 'critical').length,
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return report;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Generate markdown report
|
|
532
|
+
* @param {Object} report - Report object
|
|
533
|
+
* @returns {string} Markdown formatted report
|
|
534
|
+
*/
|
|
535
|
+
function generateMarkdownReport(report) {
|
|
536
|
+
const lines = [
|
|
537
|
+
'# Data Flow Report',
|
|
538
|
+
'',
|
|
539
|
+
`Generated: ${report.generatedAt}`,
|
|
540
|
+
'',
|
|
541
|
+
'## Summary',
|
|
542
|
+
'',
|
|
543
|
+
`- Total Flows: ${report.totalFlows}`,
|
|
544
|
+
`- Total Data Types: ${report.totalDataTypes}`,
|
|
545
|
+
'',
|
|
546
|
+
'### Sensitivity Distribution',
|
|
547
|
+
'',
|
|
548
|
+
`- Critical: ${report.sensitivitySummary.critical}`,
|
|
549
|
+
`- High: ${report.sensitivitySummary.high}`,
|
|
550
|
+
`- Medium: ${report.sensitivitySummary.medium}`,
|
|
551
|
+
`- Low: ${report.sensitivitySummary.low}`,
|
|
552
|
+
'',
|
|
553
|
+
'## Flows',
|
|
554
|
+
'',
|
|
555
|
+
];
|
|
556
|
+
|
|
557
|
+
for (const flow of report.flows) {
|
|
558
|
+
lines.push(`### ${flow.name}`);
|
|
559
|
+
lines.push('');
|
|
560
|
+
lines.push(`- **ID:** ${flow.id}`);
|
|
561
|
+
lines.push(`- **Source:** ${flow.source.type} - ${flow.source.name}`);
|
|
562
|
+
lines.push(`- **Destination:** ${flow.destination.type} - ${flow.destination.name}`);
|
|
563
|
+
lines.push(`- **Sensitivity:** ${flow.sensitivity}`);
|
|
564
|
+
if (flow.retention) {
|
|
565
|
+
lines.push(`- **Retention:** ${flow.retention}`);
|
|
566
|
+
}
|
|
567
|
+
lines.push('');
|
|
568
|
+
|
|
569
|
+
if (flow.transformations && flow.transformations.length > 0) {
|
|
570
|
+
lines.push('**Transformations:**');
|
|
571
|
+
for (const t of flow.transformations) {
|
|
572
|
+
lines.push(`- ${t.step}: ${t.description}`);
|
|
573
|
+
}
|
|
574
|
+
lines.push('');
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (report.diagrams && report.diagrams[flow.id]) {
|
|
578
|
+
lines.push('```mermaid');
|
|
579
|
+
lines.push(report.diagrams[flow.id]);
|
|
580
|
+
lines.push('```');
|
|
581
|
+
lines.push('');
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
lines.push('## Data Inventory');
|
|
586
|
+
lines.push('');
|
|
587
|
+
lines.push('| Data Type | Sensitivity | Used In Flows |');
|
|
588
|
+
lines.push('|-----------|-------------|---------------|');
|
|
589
|
+
|
|
590
|
+
for (const item of report.dataInventory) {
|
|
591
|
+
lines.push(`| ${item.name} | ${item.sensitivity} | ${item.usedInFlows.join(', ')} |`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return lines.join('\n');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* DataFlowDocumenter class for managing data flows
|
|
599
|
+
*/
|
|
600
|
+
export class DataFlowDocumenter {
|
|
601
|
+
constructor() {
|
|
602
|
+
this.flows = new Map();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Add a data flow
|
|
607
|
+
* @param {Object} flow - Flow definition
|
|
608
|
+
*/
|
|
609
|
+
addDataFlow(flow) {
|
|
610
|
+
const trackedFlow = trackDataFlow(flow);
|
|
611
|
+
this.flows.set(trackedFlow.id, trackedFlow);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Get all flows
|
|
616
|
+
* @returns {Array} Array of all flows
|
|
617
|
+
*/
|
|
618
|
+
getFlows() {
|
|
619
|
+
return Array.from(this.flows.values());
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Get a specific flow by ID
|
|
624
|
+
* @param {string} id - Flow ID
|
|
625
|
+
* @returns {Object|undefined} Flow or undefined
|
|
626
|
+
*/
|
|
627
|
+
getFlowById(id) {
|
|
628
|
+
return this.flows.get(id);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Remove a data flow
|
|
633
|
+
* @param {string} id - Flow ID to remove
|
|
634
|
+
*/
|
|
635
|
+
removeDataFlow(id) {
|
|
636
|
+
this.flows.delete(id);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Update an existing data flow
|
|
641
|
+
* @param {string} id - Flow ID to update
|
|
642
|
+
* @param {Object} updates - Updates to apply
|
|
643
|
+
*/
|
|
644
|
+
updateDataFlow(id, updates) {
|
|
645
|
+
const existing = this.flows.get(id);
|
|
646
|
+
if (existing) {
|
|
647
|
+
this.flows.set(id, { ...existing, ...updates });
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Get flow count
|
|
653
|
+
* @returns {number} Number of flows
|
|
654
|
+
*/
|
|
655
|
+
getFlowCount() {
|
|
656
|
+
return this.flows.size;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Clear all flows
|
|
661
|
+
*/
|
|
662
|
+
clear() {
|
|
663
|
+
this.flows.clear();
|
|
664
|
+
}
|
|
665
|
+
}
|