@runhalo/engine 0.2.0 → 0.3.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.
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ /**
3
+ * Data Retention Policy Scaffold
4
+ * Addresses: coppa-retention-005
5
+ * Generates data retention utility with cleanup script and deletion handler
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.retentionPolicyTemplate = void 0;
9
+ function generateReact(typescript) {
10
+ const ext = typescript ? 'ts' : 'js';
11
+ const typeAnnotations = typescript;
12
+ const utility = `/**
13
+ * Data Retention Policy — COPPA-compliant data lifecycle management
14
+ *
15
+ * COPPA requires:
16
+ * - Data collected from children must be deleted when no longer needed
17
+ * - Parents can request deletion of their child's data at any time
18
+ * - Deletion must be completed within a reasonable timeframe (48-hour SLA recommended)
19
+ * - Only collect data that is reasonably necessary
20
+ *
21
+ * This utility provides:
22
+ * - Schema helpers for marking records with retention metadata
23
+ * - A cleanup function for purging expired data
24
+ * - A deletion request handler for parent-initiated deletion
25
+ *
26
+ * Usage:
27
+ * import { DataRetention, RETENTION_POLICIES } from './data-retention';
28
+ *
29
+ * // Mark a record with retention metadata
30
+ * const record = DataRetention.markForRetention(userData, 'user_profile');
31
+ *
32
+ * // Check for expired records
33
+ * const expired = DataRetention.isExpired(record);
34
+ *
35
+ * // Handle parent deletion request
36
+ * await DataRetention.handleDeletionRequest(userId);
37
+ *
38
+ * Generated by Halo (runhalo.dev) — COPPA compliance scaffold
39
+ */
40
+ ${typeAnnotations ? `
41
+ export interface RetentionPolicy {
42
+ /** Category of data */
43
+ category: string;
44
+ /** Maximum retention period in days */
45
+ maxRetentionDays: number;
46
+ /** Whether this data type is required for service operation */
47
+ essential: boolean;
48
+ /** Description for privacy policy */
49
+ description: string;
50
+ }
51
+
52
+ export interface RetentionMetadata {
53
+ /** When the data was created */
54
+ createdAt: string;
55
+ /** When the data should be deleted */
56
+ expiresAt: string;
57
+ /** Retention policy category applied */
58
+ retentionCategory: string;
59
+ /** Whether a deletion request is pending */
60
+ deletionRequested?: boolean;
61
+ /** When deletion was requested */
62
+ deletionRequestedAt?: string;
63
+ }
64
+
65
+ export interface DeletionRequest {
66
+ userId: string;
67
+ requestedAt: string;
68
+ /** COPPA SLA: must be completed within this deadline */
69
+ deadline: string;
70
+ status: 'pending' | 'processing' | 'completed' | 'failed';
71
+ categories: string[];
72
+ }
73
+ ` : ''}
74
+ /**
75
+ * Default retention policies — customize per your data categories.
76
+ * COPPA best practice: shorter retention = lower risk.
77
+ */
78
+ ${typeAnnotations ? 'export const RETENTION_POLICIES: Record<string, RetentionPolicy> = {' : 'const RETENTION_POLICIES = {'}
79
+ /** User profile data — retained while account is active + 30 day grace period */
80
+ user_profile: {
81
+ category: 'user_profile',
82
+ maxRetentionDays: 365,
83
+ essential: true,
84
+ description: 'Basic account information needed for service operation',
85
+ },
86
+ /** Session data — short-lived, purged after 24 hours */
87
+ session: {
88
+ category: 'session',
89
+ maxRetentionDays: 1,
90
+ essential: true,
91
+ description: 'Temporary session data for authentication',
92
+ },
93
+ /** Analytics/telemetry — aggregated, purged after 90 days */
94
+ analytics: {
95
+ category: 'analytics',
96
+ maxRetentionDays: 90,
97
+ essential: false,
98
+ description: 'Anonymous usage analytics for product improvement',
99
+ },
100
+ /** User-generated content — retained while account is active */
101
+ user_content: {
102
+ category: 'user_content',
103
+ maxRetentionDays: 365,
104
+ essential: false,
105
+ description: 'Content created by the user within the service',
106
+ },
107
+ /** Support/communication — retained for 180 days */
108
+ support: {
109
+ category: 'support',
110
+ maxRetentionDays: 180,
111
+ essential: false,
112
+ description: 'Customer support interactions and communications',
113
+ },
114
+ };
115
+
116
+ /** COPPA-recommended maximum time to process a deletion request (48 hours) */
117
+ const DELETION_SLA_HOURS = 48;
118
+
119
+ ${typeAnnotations ? 'export class DataRetention {' : 'class DataRetention {'}
120
+ /**
121
+ * Add retention metadata to a data record.
122
+ * Call this when creating or updating user data.
123
+ */
124
+ static markForRetention(
125
+ record${typeAnnotations ? ': Record<string, any>' : ''},
126
+ category${typeAnnotations ? ': string' : ''},
127
+ policyOverrides${typeAnnotations ? '?: Partial<RetentionPolicy>' : ''} = {}
128
+ )${typeAnnotations ? ': Record<string, any> & { _retention: RetentionMetadata }' : ''} {
129
+ const policy = { ...(RETENTION_POLICIES[category] || RETENTION_POLICIES.user_profile), ...policyOverrides };
130
+ const now = new Date();
131
+ const expiresAt = new Date(now.getTime() + policy.maxRetentionDays * 24 * 60 * 60 * 1000);
132
+
133
+ return {
134
+ ...record,
135
+ _retention: {
136
+ createdAt: now.toISOString(),
137
+ expiresAt: expiresAt.toISOString(),
138
+ retentionCategory: policy.category,
139
+ },
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Check if a record has expired based on its retention metadata
145
+ */
146
+ static isExpired(record${typeAnnotations ? ': { _retention?: RetentionMetadata }' : ''})${typeAnnotations ? ': boolean' : ''} {
147
+ if (!record._retention?.expiresAt) return false;
148
+ return new Date() > new Date(record._retention.expiresAt);
149
+ }
150
+
151
+ /**
152
+ * Check if a deletion request is pending for a record
153
+ */
154
+ static isDeletionPending(record${typeAnnotations ? ': { _retention?: RetentionMetadata }' : ''})${typeAnnotations ? ': boolean' : ''} {
155
+ return record._retention?.deletionRequested === true;
156
+ }
157
+
158
+ /**
159
+ * Create a deletion request with COPPA SLA deadline.
160
+ *
161
+ * IMPORTANT: Implement the actual data deletion in your database layer.
162
+ * This function creates the request record — you must process it.
163
+ */
164
+ static createDeletionRequest(
165
+ userId${typeAnnotations ? ': string' : ''},
166
+ categories${typeAnnotations ? ': string[]' : ''} = Object.keys(RETENTION_POLICIES)
167
+ )${typeAnnotations ? ': DeletionRequest' : ''} {
168
+ const now = new Date();
169
+ const deadline = new Date(now.getTime() + DELETION_SLA_HOURS * 60 * 60 * 1000);
170
+
171
+ return {
172
+ userId,
173
+ requestedAt: now.toISOString(),
174
+ deadline: deadline.toISOString(), // 48-hour SLA
175
+ status: 'pending',
176
+ categories,
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Get all retention policies (for privacy policy page generation)
182
+ */
183
+ static getPolicies()${typeAnnotations ? ': Record<string, RetentionPolicy>' : ''} {
184
+ return { ...RETENTION_POLICIES };
185
+ }
186
+
187
+ /**
188
+ * Filter an array of records, removing expired ones.
189
+ * Use this in a scheduled cleanup job.
190
+ *
191
+ * Example (cron job):
192
+ * const records = await db.query('SELECT * FROM user_data');
193
+ * const { active, expired } = DataRetention.partitionByExpiry(records);
194
+ * await db.deleteMany(expired.map(r => r.id));
195
+ */
196
+ static partitionByExpiry(records${typeAnnotations ? ': Array<{ _retention?: RetentionMetadata }>' : ''})${typeAnnotations ? ': { active: any[]; expired: any[] }' : ''} {
197
+ const active${typeAnnotations ? ': any[]' : ''} = [];
198
+ const expired${typeAnnotations ? ': any[]' : ''} = [];
199
+
200
+ for (const record of records) {
201
+ if (DataRetention.isExpired(record) || DataRetention.isDeletionPending(record)) {
202
+ expired.push(record);
203
+ } else {
204
+ active.push(record);
205
+ }
206
+ }
207
+
208
+ return { active, expired };
209
+ }
210
+ }
211
+
212
+ ${typeAnnotations ? '' : 'module.exports = { DataRetention, RETENTION_POLICIES, DELETION_SLA_HOURS };'}
213
+ ${typeAnnotations ? 'export { DELETION_SLA_HOURS };' : ''}
214
+ `;
215
+ return [
216
+ {
217
+ relativePath: `utils/data-retention.${ext}`,
218
+ content: utility,
219
+ description: 'COPPA-compliant data retention utility with cleanup and deletion request handling',
220
+ },
221
+ ];
222
+ }
223
+ function generatePlainJS() {
224
+ // Reuse the same logic but without TypeScript annotations
225
+ const files = generateReact(false);
226
+ // Adjust path for plain JS (no utils/ nesting)
227
+ return files.map(f => ({
228
+ ...f,
229
+ relativePath: 'data-retention.js',
230
+ }));
231
+ }
232
+ exports.retentionPolicyTemplate = {
233
+ scaffoldId: 'retention-policy',
234
+ name: 'Data Retention Policy',
235
+ description: 'COPPA-compliant data retention lifecycle management with 48-hour deletion SLA',
236
+ ruleIds: ['coppa-retention-005'],
237
+ generate(framework, typescript) {
238
+ switch (framework) {
239
+ case 'react':
240
+ case 'nextjs':
241
+ return generateReact(typescript);
242
+ default:
243
+ return generatePlainJS();
244
+ }
245
+ },
246
+ };
247
+ //# sourceMappingURL=retention-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention-policy.js","sourceRoot":"","sources":["../../../src/scaffolds/templates/retention-policy.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runhalo/engine",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Halo rule engine — COPPA 2.0 risk detection via tree-sitter AST analysis",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",