tlc-claude-code 1.4.8 → 1.4.9

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 (169) hide show
  1. package/package.json +1 -1
  2. package/server/index.js +229 -14
  3. package/server/lib/compliance/control-mapper.js +401 -0
  4. package/server/lib/compliance/control-mapper.test.js +117 -0
  5. package/server/lib/compliance/evidence-linker.js +296 -0
  6. package/server/lib/compliance/evidence-linker.test.js +121 -0
  7. package/server/lib/compliance/gdpr-checklist.js +416 -0
  8. package/server/lib/compliance/gdpr-checklist.test.js +131 -0
  9. package/server/lib/compliance/hipaa-checklist.js +277 -0
  10. package/server/lib/compliance/hipaa-checklist.test.js +101 -0
  11. package/server/lib/compliance/iso27001-checklist.js +287 -0
  12. package/server/lib/compliance/iso27001-checklist.test.js +99 -0
  13. package/server/lib/compliance/multi-framework-reporter.js +284 -0
  14. package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
  15. package/server/lib/compliance/pci-dss-checklist.js +214 -0
  16. package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
  17. package/server/lib/compliance/trust-centre.js +187 -0
  18. package/server/lib/compliance/trust-centre.test.js +93 -0
  19. package/server/lib/dashboard/api-server.js +155 -0
  20. package/server/lib/dashboard/api-server.test.js +155 -0
  21. package/server/lib/dashboard/health-api.js +199 -0
  22. package/server/lib/dashboard/health-api.test.js +122 -0
  23. package/server/lib/dashboard/notes-api.js +234 -0
  24. package/server/lib/dashboard/notes-api.test.js +134 -0
  25. package/server/lib/dashboard/router-api.js +176 -0
  26. package/server/lib/dashboard/router-api.test.js +132 -0
  27. package/server/lib/dashboard/tasks-api.js +289 -0
  28. package/server/lib/dashboard/tasks-api.test.js +161 -0
  29. package/server/lib/dashboard/tlc-introspection.js +197 -0
  30. package/server/lib/dashboard/tlc-introspection.test.js +138 -0
  31. package/server/lib/dashboard/version-api.js +222 -0
  32. package/server/lib/dashboard/version-api.test.js +112 -0
  33. package/server/lib/dashboard/websocket-server.js +104 -0
  34. package/server/lib/dashboard/websocket-server.test.js +118 -0
  35. package/server/lib/deploy/branch-classifier.js +163 -0
  36. package/server/lib/deploy/branch-classifier.test.js +164 -0
  37. package/server/lib/deploy/deployment-approval.js +299 -0
  38. package/server/lib/deploy/deployment-approval.test.js +296 -0
  39. package/server/lib/deploy/deployment-audit.js +374 -0
  40. package/server/lib/deploy/deployment-audit.test.js +307 -0
  41. package/server/lib/deploy/deployment-executor.js +335 -0
  42. package/server/lib/deploy/deployment-executor.test.js +329 -0
  43. package/server/lib/deploy/deployment-rules.js +163 -0
  44. package/server/lib/deploy/deployment-rules.test.js +188 -0
  45. package/server/lib/deploy/rollback-manager.js +379 -0
  46. package/server/lib/deploy/rollback-manager.test.js +321 -0
  47. package/server/lib/deploy/security-gates.js +236 -0
  48. package/server/lib/deploy/security-gates.test.js +222 -0
  49. package/server/lib/k8s/gitops-config.js +188 -0
  50. package/server/lib/k8s/gitops-config.test.js +59 -0
  51. package/server/lib/k8s/helm-generator.js +196 -0
  52. package/server/lib/k8s/helm-generator.test.js +59 -0
  53. package/server/lib/k8s/kustomize-generator.js +176 -0
  54. package/server/lib/k8s/kustomize-generator.test.js +58 -0
  55. package/server/lib/k8s/network-policy.js +114 -0
  56. package/server/lib/k8s/network-policy.test.js +53 -0
  57. package/server/lib/k8s/pod-security.js +114 -0
  58. package/server/lib/k8s/pod-security.test.js +55 -0
  59. package/server/lib/k8s/rbac-generator.js +132 -0
  60. package/server/lib/k8s/rbac-generator.test.js +57 -0
  61. package/server/lib/k8s/resource-manager.js +172 -0
  62. package/server/lib/k8s/resource-manager.test.js +60 -0
  63. package/server/lib/k8s/secrets-encryption.js +168 -0
  64. package/server/lib/k8s/secrets-encryption.test.js +49 -0
  65. package/server/lib/monitoring/alert-manager.js +238 -0
  66. package/server/lib/monitoring/alert-manager.test.js +106 -0
  67. package/server/lib/monitoring/health-check.js +226 -0
  68. package/server/lib/monitoring/health-check.test.js +176 -0
  69. package/server/lib/monitoring/incident-manager.js +230 -0
  70. package/server/lib/monitoring/incident-manager.test.js +98 -0
  71. package/server/lib/monitoring/log-aggregator.js +147 -0
  72. package/server/lib/monitoring/log-aggregator.test.js +89 -0
  73. package/server/lib/monitoring/metrics-collector.js +337 -0
  74. package/server/lib/monitoring/metrics-collector.test.js +172 -0
  75. package/server/lib/monitoring/status-page.js +214 -0
  76. package/server/lib/monitoring/status-page.test.js +105 -0
  77. package/server/lib/monitoring/uptime-monitor.js +194 -0
  78. package/server/lib/monitoring/uptime-monitor.test.js +109 -0
  79. package/server/lib/network/fail2ban-config.js +294 -0
  80. package/server/lib/network/fail2ban-config.test.js +275 -0
  81. package/server/lib/network/firewall-manager.js +252 -0
  82. package/server/lib/network/firewall-manager.test.js +254 -0
  83. package/server/lib/network/geoip-filter.js +282 -0
  84. package/server/lib/network/geoip-filter.test.js +264 -0
  85. package/server/lib/network/rate-limiter.js +229 -0
  86. package/server/lib/network/rate-limiter.test.js +293 -0
  87. package/server/lib/network/request-validator.js +351 -0
  88. package/server/lib/network/request-validator.test.js +345 -0
  89. package/server/lib/network/security-headers.js +251 -0
  90. package/server/lib/network/security-headers.test.js +283 -0
  91. package/server/lib/network/tls-config.js +210 -0
  92. package/server/lib/network/tls-config.test.js +248 -0
  93. package/server/lib/security/auth-security.js +369 -0
  94. package/server/lib/security/auth-security.test.js +448 -0
  95. package/server/lib/security/cis-benchmark.js +152 -0
  96. package/server/lib/security/cis-benchmark.test.js +137 -0
  97. package/server/lib/security/compose-templates.js +312 -0
  98. package/server/lib/security/compose-templates.test.js +229 -0
  99. package/server/lib/security/container-runtime.js +456 -0
  100. package/server/lib/security/container-runtime.test.js +503 -0
  101. package/server/lib/security/cors-validator.js +278 -0
  102. package/server/lib/security/cors-validator.test.js +310 -0
  103. package/server/lib/security/crypto-utils.js +253 -0
  104. package/server/lib/security/crypto-utils.test.js +409 -0
  105. package/server/lib/security/dockerfile-linter.js +459 -0
  106. package/server/lib/security/dockerfile-linter.test.js +483 -0
  107. package/server/lib/security/dockerfile-templates.js +278 -0
  108. package/server/lib/security/dockerfile-templates.test.js +164 -0
  109. package/server/lib/security/error-sanitizer.js +426 -0
  110. package/server/lib/security/error-sanitizer.test.js +331 -0
  111. package/server/lib/security/headers-generator.js +368 -0
  112. package/server/lib/security/headers-generator.test.js +398 -0
  113. package/server/lib/security/image-scanner.js +83 -0
  114. package/server/lib/security/image-scanner.test.js +106 -0
  115. package/server/lib/security/input-validator.js +352 -0
  116. package/server/lib/security/input-validator.test.js +330 -0
  117. package/server/lib/security/network-policy.js +174 -0
  118. package/server/lib/security/network-policy.test.js +164 -0
  119. package/server/lib/security/output-encoder.js +237 -0
  120. package/server/lib/security/output-encoder.test.js +276 -0
  121. package/server/lib/security/path-validator.js +359 -0
  122. package/server/lib/security/path-validator.test.js +293 -0
  123. package/server/lib/security/query-builder.js +421 -0
  124. package/server/lib/security/query-builder.test.js +318 -0
  125. package/server/lib/security/secret-detector.js +290 -0
  126. package/server/lib/security/secret-detector.test.js +354 -0
  127. package/server/lib/security/secrets-validator.js +137 -0
  128. package/server/lib/security/secrets-validator.test.js +120 -0
  129. package/server/lib/security-testing/dast-runner.js +154 -0
  130. package/server/lib/security-testing/dast-runner.test.js +62 -0
  131. package/server/lib/security-testing/dependency-scanner.js +172 -0
  132. package/server/lib/security-testing/dependency-scanner.test.js +64 -0
  133. package/server/lib/security-testing/pentest-runner.js +230 -0
  134. package/server/lib/security-testing/pentest-runner.test.js +60 -0
  135. package/server/lib/security-testing/sast-runner.js +136 -0
  136. package/server/lib/security-testing/sast-runner.test.js +62 -0
  137. package/server/lib/security-testing/secret-scanner.js +153 -0
  138. package/server/lib/security-testing/secret-scanner.test.js +66 -0
  139. package/server/lib/security-testing/security-gate.js +216 -0
  140. package/server/lib/security-testing/security-gate.test.js +115 -0
  141. package/server/lib/security-testing/security-reporter.js +303 -0
  142. package/server/lib/security-testing/security-reporter.test.js +114 -0
  143. package/server/lib/standards/audit-checker.js +546 -0
  144. package/server/lib/standards/audit-checker.test.js +415 -0
  145. package/server/lib/standards/cleanup-executor.js +452 -0
  146. package/server/lib/standards/cleanup-executor.test.js +293 -0
  147. package/server/lib/standards/refactor-stepper.js +425 -0
  148. package/server/lib/standards/refactor-stepper.test.js +298 -0
  149. package/server/lib/standards/standards-injector.js +167 -0
  150. package/server/lib/standards/standards-injector.test.js +232 -0
  151. package/server/lib/user-management.test.js +284 -0
  152. package/server/lib/vps/backup-manager.js +157 -0
  153. package/server/lib/vps/backup-manager.test.js +59 -0
  154. package/server/lib/vps/caddy-config.js +159 -0
  155. package/server/lib/vps/caddy-config.test.js +48 -0
  156. package/server/lib/vps/compose-orchestrator.js +219 -0
  157. package/server/lib/vps/compose-orchestrator.test.js +50 -0
  158. package/server/lib/vps/database-config.js +208 -0
  159. package/server/lib/vps/database-config.test.js +47 -0
  160. package/server/lib/vps/deploy-script.js +211 -0
  161. package/server/lib/vps/deploy-script.test.js +53 -0
  162. package/server/lib/vps/secrets-manager.js +148 -0
  163. package/server/lib/vps/secrets-manager.test.js +58 -0
  164. package/server/lib/vps/server-hardening.js +174 -0
  165. package/server/lib/vps/server-hardening.test.js +70 -0
  166. package/server/package-lock.json +19 -0
  167. package/server/package.json +1 -0
  168. package/server/templates/CLAUDE.md +37 -0
  169. package/server/templates/CODING-STANDARDS.md +408 -0
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Deployment Approval Tests
3
+ */
4
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
5
+ import {
6
+ createApprovalRequest,
7
+ verifyApproval,
8
+ verify2FA,
9
+ getApprovers,
10
+ checkApprovalStatus,
11
+ APPROVAL_STATUS,
12
+ createDeploymentApproval,
13
+ } from './deployment-approval.js';
14
+
15
+ describe('deployment-approval', () => {
16
+ describe('APPROVAL_STATUS', () => {
17
+ it('defines all status constants', () => {
18
+ expect(APPROVAL_STATUS.PENDING).toBe('pending');
19
+ expect(APPROVAL_STATUS.APPROVED).toBe('approved');
20
+ expect(APPROVAL_STATUS.REJECTED).toBe('rejected');
21
+ expect(APPROVAL_STATUS.EXPIRED).toBe('expired');
22
+ expect(APPROVAL_STATUS.CANCELLED).toBe('cancelled');
23
+ });
24
+ });
25
+
26
+ describe('createApprovalRequest', () => {
27
+ it('creates approval request with required fields', () => {
28
+ const request = createApprovalRequest({
29
+ branch: 'main',
30
+ requestedBy: 'alice',
31
+ tier: 'stable',
32
+ });
33
+
34
+ expect(request.id).toBeDefined();
35
+ expect(request.branch).toBe('main');
36
+ expect(request.requestedBy).toBe('alice');
37
+ expect(request.tier).toBe('stable');
38
+ expect(request.status).toBe('pending');
39
+ expect(request.createdAt).toBeDefined();
40
+ });
41
+
42
+ it('sets expiry time', () => {
43
+ const request = createApprovalRequest({
44
+ branch: 'main',
45
+ requestedBy: 'alice',
46
+ tier: 'stable',
47
+ expiresInMinutes: 30,
48
+ });
49
+
50
+ const expectedExpiry = new Date(request.createdAt);
51
+ expectedExpiry.setMinutes(expectedExpiry.getMinutes() + 30);
52
+ expect(new Date(request.expiresAt).getTime()).toBeCloseTo(expectedExpiry.getTime(), -3);
53
+ });
54
+
55
+ it('defaults to 60 minute expiry', () => {
56
+ const request = createApprovalRequest({
57
+ branch: 'main',
58
+ requestedBy: 'alice',
59
+ tier: 'stable',
60
+ });
61
+
62
+ const created = new Date(request.createdAt);
63
+ const expires = new Date(request.expiresAt);
64
+ const diffMinutes = (expires - created) / 60000;
65
+ expect(diffMinutes).toBe(60);
66
+ });
67
+
68
+ it('includes commit info when provided', () => {
69
+ const request = createApprovalRequest({
70
+ branch: 'main',
71
+ requestedBy: 'alice',
72
+ tier: 'stable',
73
+ commitSha: 'abc123',
74
+ commitMessage: 'Fix bug',
75
+ });
76
+
77
+ expect(request.commitSha).toBe('abc123');
78
+ expect(request.commitMessage).toBe('Fix bug');
79
+ });
80
+ });
81
+
82
+ describe('verifyApproval', () => {
83
+ it('approves with valid approver', async () => {
84
+ const request = createApprovalRequest({
85
+ branch: 'main',
86
+ requestedBy: 'alice',
87
+ tier: 'stable',
88
+ });
89
+
90
+ const result = await verifyApproval(request, {
91
+ approver: 'bob',
92
+ approvers: ['bob', 'carol'],
93
+ });
94
+
95
+ expect(result.status).toBe('approved');
96
+ expect(result.approvedBy).toBe('bob');
97
+ expect(result.approvedAt).toBeDefined();
98
+ });
99
+
100
+ it('rejects if not in approvers list', async () => {
101
+ const request = createApprovalRequest({
102
+ branch: 'main',
103
+ requestedBy: 'alice',
104
+ tier: 'stable',
105
+ });
106
+
107
+ const result = await verifyApproval(request, {
108
+ approver: 'dave',
109
+ approvers: ['bob', 'carol'],
110
+ });
111
+
112
+ expect(result.status).toBe('rejected');
113
+ expect(result.reason).toContain('not authorized');
114
+ });
115
+
116
+ it('rejects expired requests', async () => {
117
+ const request = createApprovalRequest({
118
+ branch: 'main',
119
+ requestedBy: 'alice',
120
+ tier: 'stable',
121
+ expiresInMinutes: -1, // Already expired
122
+ });
123
+
124
+ const result = await verifyApproval(request, {
125
+ approver: 'bob',
126
+ approvers: ['bob'],
127
+ });
128
+
129
+ expect(result.status).toBe('expired');
130
+ });
131
+
132
+ it('prevents self-approval', async () => {
133
+ const request = createApprovalRequest({
134
+ branch: 'main',
135
+ requestedBy: 'alice',
136
+ tier: 'stable',
137
+ });
138
+
139
+ const result = await verifyApproval(request, {
140
+ approver: 'alice',
141
+ approvers: ['alice', 'bob'],
142
+ allowSelfApproval: false,
143
+ });
144
+
145
+ expect(result.status).toBe('rejected');
146
+ expect(result.reason).toContain('self-approval');
147
+ });
148
+
149
+ it('allows self-approval when configured', async () => {
150
+ const request = createApprovalRequest({
151
+ branch: 'main',
152
+ requestedBy: 'alice',
153
+ tier: 'stable',
154
+ });
155
+
156
+ const result = await verifyApproval(request, {
157
+ approver: 'alice',
158
+ approvers: ['alice'],
159
+ allowSelfApproval: true,
160
+ });
161
+
162
+ expect(result.status).toBe('approved');
163
+ });
164
+ });
165
+
166
+ describe('verify2FA', () => {
167
+ it('verifies valid TOTP code', async () => {
168
+ const mockVerifier = vi.fn().mockResolvedValue(true);
169
+ const result = await verify2FA('alice', '123456', { verifier: mockVerifier });
170
+
171
+ expect(result.verified).toBe(true);
172
+ expect(mockVerifier).toHaveBeenCalledWith('alice', '123456');
173
+ });
174
+
175
+ it('rejects invalid TOTP code', async () => {
176
+ const mockVerifier = vi.fn().mockResolvedValue(false);
177
+ const result = await verify2FA('alice', '000000', { verifier: mockVerifier });
178
+
179
+ expect(result.verified).toBe(false);
180
+ });
181
+
182
+ it('handles verification errors', async () => {
183
+ const mockVerifier = vi.fn().mockRejectedValue(new Error('Service unavailable'));
184
+ const result = await verify2FA('alice', '123456', { verifier: mockVerifier });
185
+
186
+ expect(result.verified).toBe(false);
187
+ expect(result.error).toContain('Service unavailable');
188
+ });
189
+
190
+ it('validates code format', async () => {
191
+ const result = await verify2FA('alice', 'abc', {});
192
+ expect(result.verified).toBe(false);
193
+ expect(result.error).toContain('Invalid code format');
194
+ });
195
+ });
196
+
197
+ describe('getApprovers', () => {
198
+ it('returns approvers from config', () => {
199
+ const config = {
200
+ deployment: {
201
+ approvers: ['alice', 'bob'],
202
+ },
203
+ };
204
+ const approvers = getApprovers(config);
205
+ expect(approvers).toEqual(['alice', 'bob']);
206
+ });
207
+
208
+ it('returns empty array when not configured', () => {
209
+ const approvers = getApprovers({});
210
+ expect(approvers).toEqual([]);
211
+ });
212
+
213
+ it('filters by tier when specified', () => {
214
+ const config = {
215
+ deployment: {
216
+ approvers: {
217
+ stable: ['alice', 'bob'],
218
+ dev: ['carol'],
219
+ },
220
+ },
221
+ };
222
+ expect(getApprovers(config, 'stable')).toEqual(['alice', 'bob']);
223
+ expect(getApprovers(config, 'dev')).toEqual(['carol']);
224
+ });
225
+ });
226
+
227
+ describe('checkApprovalStatus', () => {
228
+ it('returns pending for fresh request', () => {
229
+ const request = createApprovalRequest({
230
+ branch: 'main',
231
+ requestedBy: 'alice',
232
+ tier: 'stable',
233
+ });
234
+
235
+ const status = checkApprovalStatus(request);
236
+ expect(status).toBe('pending');
237
+ });
238
+
239
+ it('returns expired for old request', () => {
240
+ const request = createApprovalRequest({
241
+ branch: 'main',
242
+ requestedBy: 'alice',
243
+ tier: 'stable',
244
+ expiresInMinutes: -1,
245
+ });
246
+
247
+ const status = checkApprovalStatus(request);
248
+ expect(status).toBe('expired');
249
+ });
250
+
251
+ it('returns approved when approved', () => {
252
+ const request = createApprovalRequest({
253
+ branch: 'main',
254
+ requestedBy: 'alice',
255
+ tier: 'stable',
256
+ });
257
+ request.status = 'approved';
258
+ request.approvedBy = 'bob';
259
+
260
+ const status = checkApprovalStatus(request);
261
+ expect(status).toBe('approved');
262
+ });
263
+ });
264
+
265
+ describe('createDeploymentApproval', () => {
266
+ it('creates approval manager', () => {
267
+ const approval = createDeploymentApproval();
268
+ expect(approval.createRequest).toBeDefined();
269
+ expect(approval.approve).toBeDefined();
270
+ expect(approval.reject).toBeDefined();
271
+ expect(approval.checkStatus).toBeDefined();
272
+ });
273
+
274
+ it('stores pending requests', async () => {
275
+ const approval = createDeploymentApproval();
276
+ const request = await approval.createRequest({
277
+ branch: 'main',
278
+ requestedBy: 'alice',
279
+ tier: 'stable',
280
+ });
281
+
282
+ const retrieved = approval.getRequest(request.id);
283
+ expect(retrieved).toBeDefined();
284
+ expect(retrieved.branch).toBe('main');
285
+ });
286
+
287
+ it('lists pending requests', async () => {
288
+ const approval = createDeploymentApproval();
289
+ await approval.createRequest({ branch: 'main', requestedBy: 'alice', tier: 'stable' });
290
+ await approval.createRequest({ branch: 'release/1.0', requestedBy: 'bob', tier: 'stable' });
291
+
292
+ const pending = approval.listPending();
293
+ expect(pending).toHaveLength(2);
294
+ });
295
+ });
296
+ });
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Deployment Audit Module
3
+ *
4
+ * Provides audit logging for deployment events with:
5
+ * - Immutable event logging
6
+ * - Checksum chain for integrity verification
7
+ * - Query and export capabilities (JSON, CSV, CEF)
8
+ */
9
+ import crypto from 'crypto';
10
+
11
+ /**
12
+ * Audit event type constants
13
+ */
14
+ export const AUDIT_EVENTS = {
15
+ DEPLOYMENT_STARTED: 'deployment_started',
16
+ DEPLOYMENT_COMPLETED: 'deployment_completed',
17
+ DEPLOYMENT_FAILED: 'deployment_failed',
18
+ APPROVAL_REQUESTED: 'approval_requested',
19
+ APPROVAL_GRANTED: 'approval_granted',
20
+ APPROVAL_DENIED: 'approval_denied',
21
+ ROLLBACK_TRIGGERED: 'rollback_triggered',
22
+ ROLLBACK_COMPLETED: 'rollback_completed',
23
+ SECURITY_GATE_PASSED: 'security_gate_passed',
24
+ SECURITY_GATE_FAILED: 'security_gate_failed',
25
+ };
26
+
27
+ /**
28
+ * Generate a unique ID
29
+ * @returns {string} Unique identifier
30
+ */
31
+ function generateId() {
32
+ return `audit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
33
+ }
34
+
35
+ /**
36
+ * Calculate SHA-256 checksum of an entry
37
+ * @param {object} entry - The entry to checksum
38
+ * @param {string} [previousChecksum] - Previous entry's checksum
39
+ * @returns {string} SHA-256 hex digest
40
+ */
41
+ function calculateChecksum(entry, previousChecksum = '') {
42
+ const data = JSON.stringify({
43
+ id: entry.id,
44
+ event: entry.event,
45
+ deploymentId: entry.deploymentId,
46
+ branch: entry.branch,
47
+ user: entry.user,
48
+ timestamp: entry.timestamp,
49
+ metadata: entry.metadata,
50
+ previousChecksum,
51
+ });
52
+ return crypto.createHash('sha256').update(data).digest('hex');
53
+ }
54
+
55
+ /**
56
+ * Log a deployment event
57
+ * @param {object} options - Event options
58
+ * @param {string} options.event - Event type
59
+ * @param {string} options.deploymentId - Deployment identifier
60
+ * @param {string} options.branch - Branch name
61
+ * @param {string} options.user - User who triggered the event
62
+ * @param {object} [options.metadata] - Additional metadata
63
+ * @param {string} [options.previousChecksum] - Previous entry's checksum
64
+ * @param {function} [options.writeFn] - Function to write the entry
65
+ * @returns {Promise<object>} The logged entry
66
+ */
67
+ export async function logDeploymentEvent(options) {
68
+ const {
69
+ event,
70
+ deploymentId,
71
+ branch,
72
+ user,
73
+ metadata = {},
74
+ previousChecksum = null,
75
+ writeFn,
76
+ } = options;
77
+
78
+ const entry = {
79
+ id: generateId(),
80
+ event,
81
+ deploymentId,
82
+ branch,
83
+ user,
84
+ timestamp: new Date().toISOString(),
85
+ metadata,
86
+ previousChecksum,
87
+ };
88
+
89
+ entry.checksum = calculateChecksum(entry, previousChecksum);
90
+
91
+ if (writeFn) {
92
+ await writeFn(entry);
93
+ }
94
+
95
+ return entry;
96
+ }
97
+
98
+ /**
99
+ * Query the audit log
100
+ * @param {object} options - Query options
101
+ * @param {string} [options.startDate] - Start date for range filter
102
+ * @param {string} [options.endDate] - End date for range filter
103
+ * @param {string} [options.user] - Filter by user
104
+ * @param {string} [options.branch] - Filter by branch
105
+ * @param {string} [options.event] - Filter by event type
106
+ * @param {number} [options.limit] - Maximum entries to return
107
+ * @param {number} [options.offset] - Offset for pagination
108
+ * @param {function} options.queryFn - Function to fetch entries
109
+ * @returns {Promise<object[]>} Matching entries
110
+ */
111
+ export async function queryAuditLog(options) {
112
+ const {
113
+ startDate,
114
+ endDate,
115
+ user,
116
+ branch,
117
+ event,
118
+ limit,
119
+ offset = 0,
120
+ queryFn,
121
+ } = options;
122
+
123
+ let entries = await queryFn();
124
+
125
+ // Apply filters
126
+ if (startDate || endDate) {
127
+ entries = entries.filter((entry) => {
128
+ const ts = new Date(entry.timestamp);
129
+ if (startDate && ts < new Date(startDate)) return false;
130
+ if (endDate && ts > new Date(endDate)) return false;
131
+ return true;
132
+ });
133
+ }
134
+
135
+ if (user) {
136
+ entries = entries.filter((entry) => entry.user === user);
137
+ }
138
+
139
+ if (branch) {
140
+ entries = entries.filter((entry) => entry.branch === branch);
141
+ }
142
+
143
+ if (event) {
144
+ entries = entries.filter((entry) => entry.event === event);
145
+ }
146
+
147
+ // Apply pagination
148
+ if (offset > 0) {
149
+ entries = entries.slice(offset);
150
+ }
151
+
152
+ if (limit !== undefined) {
153
+ entries = entries.slice(0, limit);
154
+ }
155
+
156
+ return entries;
157
+ }
158
+
159
+ /**
160
+ * Export audit log in various formats
161
+ * @param {object} options - Export options
162
+ * @param {string} options.format - Export format (json, csv, cef)
163
+ * @param {boolean} [options.includeMetadata] - Include export metadata
164
+ * @param {function} options.queryFn - Function to fetch entries
165
+ * @returns {Promise<string>} Exported data
166
+ */
167
+ export async function exportAuditLog(options) {
168
+ const { format, includeMetadata = false, queryFn, ...queryOptions } = options;
169
+
170
+ const entries = await queryFn(queryOptions);
171
+
172
+ switch (format) {
173
+ case 'json':
174
+ return exportAsJson(entries, includeMetadata);
175
+ case 'csv':
176
+ return exportAsCsv(entries);
177
+ case 'cef':
178
+ return exportAsCef(entries);
179
+ default:
180
+ throw new Error(`Unsupported export format: ${format}`);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Export entries as JSON
186
+ * @param {object[]} entries - Entries to export
187
+ * @param {boolean} includeMetadata - Include export metadata
188
+ * @returns {string} JSON string
189
+ */
190
+ function exportAsJson(entries, includeMetadata) {
191
+ const result = {
192
+ entries,
193
+ exportedAt: new Date().toISOString(),
194
+ };
195
+
196
+ if (includeMetadata) {
197
+ result.totalEntries = entries.length;
198
+ }
199
+
200
+ return JSON.stringify(result, null, 2);
201
+ }
202
+
203
+ /**
204
+ * Export entries as CSV
205
+ * @param {object[]} entries - Entries to export
206
+ * @returns {string} CSV string
207
+ */
208
+ function exportAsCsv(entries) {
209
+ const headers = ['id', 'event', 'branch', 'user', 'timestamp'];
210
+ const lines = [headers.join(',')];
211
+
212
+ for (const entry of entries) {
213
+ const row = headers.map((h) => {
214
+ const value = entry[h] || '';
215
+ // Escape quotes and wrap in quotes if contains comma
216
+ if (typeof value === 'string' && (value.includes(',') || value.includes('"'))) {
217
+ return `"${value.replace(/"/g, '""')}"`;
218
+ }
219
+ return value;
220
+ });
221
+ lines.push(row.join(','));
222
+ }
223
+
224
+ return lines.join('\n');
225
+ }
226
+
227
+ /**
228
+ * Export entries as CEF (Common Event Format) for SIEM integration
229
+ * @param {object[]} entries - Entries to export
230
+ * @returns {string} CEF formatted string
231
+ */
232
+ function exportAsCef(entries) {
233
+ const lines = [];
234
+
235
+ for (const entry of entries) {
236
+ // CEF format: CEF:Version|Device Vendor|Device Product|Device Version|Signature ID|Name|Severity|Extension
237
+ const cefLine = [
238
+ 'CEF:0',
239
+ 'DeploymentAudit',
240
+ 'DeploymentAudit',
241
+ '1.0',
242
+ entry.event,
243
+ entry.event,
244
+ getSeverity(entry.event),
245
+ `branch=${entry.branch} user=${entry.user} deploymentId=${entry.deploymentId || ''} timestamp=${entry.timestamp}`,
246
+ ].join('|');
247
+ lines.push(cefLine);
248
+ }
249
+
250
+ return lines.join('\n');
251
+ }
252
+
253
+ /**
254
+ * Get severity level for CEF format based on event type
255
+ * @param {string} event - Event type
256
+ * @returns {number} Severity (0-10)
257
+ */
258
+ function getSeverity(event) {
259
+ const severityMap = {
260
+ [AUDIT_EVENTS.DEPLOYMENT_STARTED]: 1,
261
+ [AUDIT_EVENTS.DEPLOYMENT_COMPLETED]: 1,
262
+ [AUDIT_EVENTS.DEPLOYMENT_FAILED]: 7,
263
+ [AUDIT_EVENTS.APPROVAL_REQUESTED]: 3,
264
+ [AUDIT_EVENTS.APPROVAL_GRANTED]: 1,
265
+ [AUDIT_EVENTS.APPROVAL_DENIED]: 5,
266
+ [AUDIT_EVENTS.ROLLBACK_TRIGGERED]: 6,
267
+ [AUDIT_EVENTS.ROLLBACK_COMPLETED]: 3,
268
+ [AUDIT_EVENTS.SECURITY_GATE_PASSED]: 1,
269
+ [AUDIT_EVENTS.SECURITY_GATE_FAILED]: 8,
270
+ };
271
+ return severityMap[event] || 5;
272
+ }
273
+
274
+ /**
275
+ * Create a deployment audit instance with storage
276
+ * @param {object} [options] - Configuration options
277
+ * @param {object} [options.storage] - Storage backend with read/write methods
278
+ * @returns {object} Audit instance with log, query, export, verifyIntegrity methods
279
+ */
280
+ export function createDeploymentAudit(options = {}) {
281
+ const { storage } = options;
282
+
283
+ // In-memory storage if none provided
284
+ let entries = [];
285
+ let lastChecksum = null;
286
+
287
+ const internalStorage = {
288
+ write: async (entry) => {
289
+ entries.push(entry);
290
+ return true;
291
+ },
292
+ read: async () => [...entries],
293
+ };
294
+
295
+ const activeStorage = storage || internalStorage;
296
+
297
+ return {
298
+ /**
299
+ * Log a deployment event
300
+ * @param {object} eventOptions - Event options
301
+ * @returns {Promise<object>} The logged entry
302
+ */
303
+ async log(eventOptions) {
304
+ const entry = await logDeploymentEvent({
305
+ ...eventOptions,
306
+ previousChecksum: lastChecksum,
307
+ writeFn: activeStorage.write,
308
+ });
309
+ lastChecksum = entry.checksum;
310
+ return entry;
311
+ },
312
+
313
+ /**
314
+ * Query the audit log
315
+ * @param {object} queryOptions - Query options
316
+ * @returns {Promise<object[]>} Matching entries
317
+ */
318
+ async query(queryOptions = {}) {
319
+ return queryAuditLog({
320
+ ...queryOptions,
321
+ queryFn: activeStorage.read,
322
+ });
323
+ },
324
+
325
+ /**
326
+ * Export the audit log
327
+ * @param {object} exportOptions - Export options
328
+ * @returns {Promise<string>} Exported data
329
+ */
330
+ async export(exportOptions) {
331
+ return exportAuditLog({
332
+ ...exportOptions,
333
+ queryFn: activeStorage.read,
334
+ });
335
+ },
336
+
337
+ /**
338
+ * Verify the integrity of the audit log
339
+ * @returns {Promise<object>} Integrity result with valid flag and tampered entries
340
+ */
341
+ async verifyIntegrity() {
342
+ const allEntries = await activeStorage.read();
343
+ const tamperedEntries = [];
344
+ let prevChecksum = null;
345
+
346
+ for (let i = 0; i < allEntries.length; i++) {
347
+ const entry = allEntries[i];
348
+ const expectedChecksum = calculateChecksum(entry, prevChecksum);
349
+
350
+ if (entry.checksum !== expectedChecksum) {
351
+ tamperedEntries.push({ index: i, entry });
352
+ }
353
+
354
+ prevChecksum = entry.checksum;
355
+ }
356
+
357
+ return {
358
+ valid: tamperedEntries.length === 0,
359
+ tamperedEntries,
360
+ };
361
+ },
362
+
363
+ /**
364
+ * Internal method for testing - tamper with an entry
365
+ * @param {number} index - Entry index
366
+ * @param {object} changes - Changes to apply
367
+ */
368
+ _tamperEntry(index, changes) {
369
+ if (entries[index]) {
370
+ Object.assign(entries[index], changes);
371
+ }
372
+ },
373
+ };
374
+ }