@happyvertical/smrt-secrets 0.40.64 → 0.40.66

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.
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
+ "sensitiveFieldsExcluded": true,
3
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
4
5
  "packageName": "@happyvertical/smrt-secrets",
5
- "packageVersion": "0.40.64",
6
+ "packageVersion": "0.40.66",
6
7
  "sourceManifestPath": "dist/manifest.json",
7
8
  "agentDocPath": "AGENTS.md",
8
9
  "sourceHashes": {
9
- "manifest": "5bfa42e8d42b2bc83c3b7cf98cf2e57f7132ab1a451a4a0441bec1af3c8332bc",
10
- "packageJson": "807c504bd77cdd91b7dcdc02b7fb48f4f639bef4dd5dbfa9d9feea1fb0d35f1f",
10
+ "manifest": "3c59ec684f5d80e87e16d95be4201f4579d7df4b5faf0f1f994d567820134ca0",
11
+ "packageJson": "d7e067a4b8b689bff9a985129037a8897bd72bb797b6f90753fc698f632edcde",
11
12
  "agents": "c2be546ffbe79a0df95401c10a60e23f4f31cb8232cb1349fcdb7e5682852d4a"
12
13
  },
13
14
  "exports": [
@@ -63,6 +64,80 @@
63
64
  "getUserActivity",
64
65
  "listLogs"
65
66
  ],
67
+ "methodSignatures": [
68
+ {
69
+ "name": "cleanup",
70
+ "async": true,
71
+ "params": [
72
+ "olderThanDays?: number"
73
+ ],
74
+ "returns": "Promise<number>"
75
+ },
76
+ {
77
+ "name": "countByAction",
78
+ "async": true,
79
+ "params": [
80
+ "tenantId: string | null",
81
+ "since?: Date"
82
+ ],
83
+ "returns": "Promise<Record<SecretAuditAction, number>>"
84
+ },
85
+ {
86
+ "name": "countByResult",
87
+ "async": true,
88
+ "params": [
89
+ "tenantId: string | null",
90
+ "since?: Date"
91
+ ],
92
+ "returns": "Promise<Record<SecretAuditResult, number>>"
93
+ },
94
+ {
95
+ "name": "getRecentDenials",
96
+ "async": true,
97
+ "params": [
98
+ "tenantId: string | null",
99
+ "limit?: number"
100
+ ],
101
+ "returns": "Promise<SecretAuditLog[]>"
102
+ },
103
+ {
104
+ "name": "getRecentFailures",
105
+ "async": true,
106
+ "params": [
107
+ "tenantId: string | null",
108
+ "limit?: number"
109
+ ],
110
+ "returns": "Promise<SecretAuditLog[]>"
111
+ },
112
+ {
113
+ "name": "getSecretHistory",
114
+ "async": true,
115
+ "params": [
116
+ "tenantId: string | null",
117
+ "secretName: string",
118
+ "limit?: number"
119
+ ],
120
+ "returns": "Promise<SecretAuditLog[]>"
121
+ },
122
+ {
123
+ "name": "getUserActivity",
124
+ "async": true,
125
+ "params": [
126
+ "tenantId: string | null",
127
+ "userId: string",
128
+ "limit?: number"
129
+ ],
130
+ "returns": "Promise<SecretAuditLog[]>"
131
+ },
132
+ {
133
+ "name": "listLogs",
134
+ "async": true,
135
+ "params": [
136
+ "options?: ListAuditLogsOptions"
137
+ ],
138
+ "returns": "Promise<SecretAuditLog[]>"
139
+ }
140
+ ],
66
141
  "surfaces": [],
67
142
  "relationshipFeatures": [
68
143
  "uuidColumns"
@@ -89,6 +164,77 @@
89
164
  "listExpiring",
90
165
  "listSecrets"
91
166
  ],
167
+ "methodSignatures": [
168
+ {
169
+ "name": "countByStatus",
170
+ "async": true,
171
+ "params": [
172
+ "tenantId: string"
173
+ ],
174
+ "returns": "Promise<Record<SecretStatus, number>>"
175
+ },
176
+ {
177
+ "name": "deleteByName",
178
+ "async": true,
179
+ "params": [
180
+ "tenantId: string",
181
+ "name: string"
182
+ ],
183
+ "returns": "Promise<boolean>"
184
+ },
185
+ {
186
+ "name": "findByName",
187
+ "async": true,
188
+ "params": [
189
+ "tenantId: string",
190
+ "name: string"
191
+ ],
192
+ "returns": "Promise<Secret | null>"
193
+ },
194
+ {
195
+ "name": "getCategories",
196
+ "async": true,
197
+ "params": [
198
+ "tenantId: string"
199
+ ],
200
+ "returns": "Promise<string[]>"
201
+ },
202
+ {
203
+ "name": "listActive",
204
+ "async": true,
205
+ "params": [
206
+ "tenantId: string"
207
+ ],
208
+ "returns": "Promise<Secret[]>"
209
+ },
210
+ {
211
+ "name": "listByCategory",
212
+ "async": true,
213
+ "params": [
214
+ "tenantId: string",
215
+ "category: string"
216
+ ],
217
+ "returns": "Promise<Secret[]>"
218
+ },
219
+ {
220
+ "name": "listExpiring",
221
+ "async": true,
222
+ "params": [
223
+ "tenantId: string",
224
+ "daysAhead?: number"
225
+ ],
226
+ "returns": "Promise<Secret[]>"
227
+ },
228
+ {
229
+ "name": "listSecrets",
230
+ "async": true,
231
+ "params": [
232
+ "tenantId: string",
233
+ "options?: ListSecretsOptions"
234
+ ],
235
+ "returns": "Promise<Secret[]>"
236
+ }
237
+ ],
92
238
  "surfaces": [],
93
239
  "relationshipFeatures": [
94
240
  "uuidColumns"
@@ -115,6 +261,65 @@
115
261
  "listKeyVersions",
116
262
  "markCompromised"
117
263
  ],
264
+ "methodSignatures": [
265
+ {
266
+ "name": "cleanupRetiredKeys",
267
+ "async": true,
268
+ "params": [
269
+ "olderThanDays?: number"
270
+ ],
271
+ "returns": "Promise<number>"
272
+ },
273
+ {
274
+ "name": "countByStatus",
275
+ "async": true,
276
+ "returns": "Promise<Record<TenantKeyStatus, number>>"
277
+ },
278
+ {
279
+ "name": "findKeysNeedingRotation",
280
+ "async": true,
281
+ "returns": "Promise<TenantKey[]>"
282
+ },
283
+ {
284
+ "name": "getActiveKey",
285
+ "async": true,
286
+ "params": [
287
+ "tenantId: string"
288
+ ],
289
+ "returns": "Promise<TenantKey | null>"
290
+ },
291
+ {
292
+ "name": "getKeyVersion",
293
+ "async": true,
294
+ "params": [
295
+ "tenantId: string",
296
+ "version: number"
297
+ ],
298
+ "returns": "Promise<TenantKey | null>"
299
+ },
300
+ {
301
+ "name": "listAllActiveKeys",
302
+ "async": true,
303
+ "returns": "Promise<TenantKey[]>"
304
+ },
305
+ {
306
+ "name": "listKeyVersions",
307
+ "async": true,
308
+ "params": [
309
+ "tenantId: string"
310
+ ],
311
+ "returns": "Promise<TenantKey[]>"
312
+ },
313
+ {
314
+ "name": "markCompromised",
315
+ "async": true,
316
+ "params": [
317
+ "tenantId: string",
318
+ "keyId: string"
319
+ ],
320
+ "returns": "Promise<boolean>"
321
+ }
322
+ ],
118
323
  "surfaces": [],
119
324
  "relationshipFeatures": [
120
325
  "uuidColumns"
@@ -134,43 +339,50 @@
134
339
  "name": "tenantId",
135
340
  "type": "text",
136
341
  "required": false,
137
- "columnType": "UUID"
342
+ "columnType": "UUID",
343
+ "default": ""
138
344
  },
139
345
  {
140
346
  "name": "name",
141
347
  "type": "text",
142
348
  "required": false,
143
- "columnType": "TEXT"
349
+ "columnType": "TEXT",
350
+ "default": ""
144
351
  },
145
352
  {
146
353
  "name": "description",
147
354
  "type": "text",
148
355
  "required": false,
149
- "columnType": "TEXT"
356
+ "columnType": "TEXT",
357
+ "default": ""
150
358
  },
151
359
  {
152
360
  "name": "category",
153
361
  "type": "text",
154
362
  "required": false,
155
- "columnType": "TEXT"
363
+ "columnType": "TEXT",
364
+ "default": ""
156
365
  },
157
366
  {
158
367
  "name": "encryptedValue",
159
368
  "type": "text",
160
369
  "required": false,
161
- "columnType": "TEXT"
370
+ "columnType": "TEXT",
371
+ "default": ""
162
372
  },
163
373
  {
164
374
  "name": "keyVersion",
165
375
  "type": "integer",
166
376
  "required": false,
167
- "columnType": "INTEGER"
377
+ "columnType": "INTEGER",
378
+ "default": 1
168
379
  },
169
380
  {
170
381
  "name": "status",
171
382
  "type": "text",
172
383
  "required": false,
173
- "columnType": "TEXT"
384
+ "columnType": "TEXT",
385
+ "default": "active"
174
386
  },
175
387
  {
176
388
  "name": "expiresAt",
@@ -188,13 +400,15 @@
188
400
  "name": "accessCount",
189
401
  "type": "integer",
190
402
  "required": false,
191
- "columnType": "INTEGER"
403
+ "columnType": "INTEGER",
404
+ "default": 0
192
405
  },
193
406
  {
194
407
  "name": "metadata",
195
408
  "type": "json",
196
409
  "required": false,
197
- "columnType": "JSON"
410
+ "columnType": "JSON",
411
+ "default": {}
198
412
  }
199
413
  ],
200
414
  "relationships": [],
@@ -206,6 +420,37 @@
206
420
  "isUsable",
207
421
  "recordAccess"
208
422
  ],
423
+ "methodSignatures": [
424
+ {
425
+ "name": "disable",
426
+ "returns": "void"
427
+ },
428
+ {
429
+ "name": "enable",
430
+ "returns": "void"
431
+ },
432
+ {
433
+ "name": "isActive",
434
+ "returns": "boolean"
435
+ },
436
+ {
437
+ "name": "isExpired",
438
+ "returns": "boolean"
439
+ },
440
+ {
441
+ "name": "isUsable",
442
+ "returns": "boolean"
443
+ },
444
+ {
445
+ "name": "recordAccess",
446
+ "returns": "void"
447
+ }
448
+ ],
449
+ "tenant": {
450
+ "scoped": true,
451
+ "mode": "required",
452
+ "field": "tenantId"
453
+ },
209
454
  "surfaces": [
210
455
  {
211
456
  "kind": "cli",
@@ -245,7 +490,8 @@
245
490
  "name": "secretName",
246
491
  "type": "text",
247
492
  "required": false,
248
- "columnType": "TEXT"
493
+ "columnType": "TEXT",
494
+ "default": ""
249
495
  },
250
496
  {
251
497
  "name": "userId",
@@ -258,31 +504,36 @@
258
504
  "name": "action",
259
505
  "type": "text",
260
506
  "required": false,
261
- "columnType": "TEXT"
507
+ "columnType": "TEXT",
508
+ "default": "read"
262
509
  },
263
510
  {
264
511
  "name": "result",
265
512
  "type": "text",
266
513
  "required": false,
267
- "columnType": "TEXT"
514
+ "columnType": "TEXT",
515
+ "default": "success"
268
516
  },
269
517
  {
270
518
  "name": "ipAddress",
271
519
  "type": "text",
272
520
  "required": false,
273
- "columnType": "TEXT"
521
+ "columnType": "TEXT",
522
+ "default": ""
274
523
  },
275
524
  {
276
525
  "name": "userAgent",
277
526
  "type": "text",
278
527
  "required": false,
279
- "columnType": "TEXT"
528
+ "columnType": "TEXT",
529
+ "default": ""
280
530
  },
281
531
  {
282
532
  "name": "details",
283
533
  "type": "json",
284
534
  "required": false,
285
- "columnType": "JSON"
535
+ "columnType": "JSON",
536
+ "default": {}
286
537
  }
287
538
  ],
288
539
  "relationships": [
@@ -309,6 +560,37 @@
309
560
  "isSuccess",
310
561
  "isWriteAction"
311
562
  ],
563
+ "methodSignatures": [
564
+ {
565
+ "name": "isDenied",
566
+ "returns": "boolean"
567
+ },
568
+ {
569
+ "name": "isFailure",
570
+ "returns": "boolean"
571
+ },
572
+ {
573
+ "name": "isKeyOperation",
574
+ "returns": "boolean"
575
+ },
576
+ {
577
+ "name": "isReadAction",
578
+ "returns": "boolean"
579
+ },
580
+ {
581
+ "name": "isSuccess",
582
+ "returns": "boolean"
583
+ },
584
+ {
585
+ "name": "isWriteAction",
586
+ "returns": "boolean"
587
+ }
588
+ ],
589
+ "tenant": {
590
+ "scoped": true,
591
+ "mode": "required",
592
+ "field": "tenantId"
593
+ },
312
594
  "surfaces": [
313
595
  {
314
596
  "kind": "cli",
@@ -337,31 +619,36 @@
337
619
  "name": "tenantId",
338
620
  "type": "text",
339
621
  "required": false,
340
- "columnType": "TEXT"
622
+ "columnType": "TEXT",
623
+ "default": ""
341
624
  },
342
625
  {
343
626
  "name": "wrappedKey",
344
627
  "type": "text",
345
628
  "required": false,
346
- "columnType": "TEXT"
629
+ "columnType": "TEXT",
630
+ "default": ""
347
631
  },
348
632
  {
349
633
  "name": "amkKeyId",
350
634
  "type": "text",
351
635
  "required": false,
352
- "columnType": "TEXT"
636
+ "columnType": "TEXT",
637
+ "default": ""
353
638
  },
354
639
  {
355
640
  "name": "status",
356
641
  "type": "text",
357
642
  "required": false,
358
- "columnType": "TEXT"
643
+ "columnType": "TEXT",
644
+ "default": "active"
359
645
  },
360
646
  {
361
647
  "name": "version",
362
648
  "type": "integer",
363
649
  "required": false,
364
- "columnType": "INTEGER"
650
+ "columnType": "INTEGER",
651
+ "default": 1
365
652
  },
366
653
  {
367
654
  "name": "rotateAfter",
@@ -387,6 +674,40 @@
387
674
  "needsRotation",
388
675
  "retire"
389
676
  ],
677
+ "methodSignatures": [
678
+ {
679
+ "name": "canDecrypt",
680
+ "returns": "boolean"
681
+ },
682
+ {
683
+ "name": "canEncrypt",
684
+ "returns": "boolean"
685
+ },
686
+ {
687
+ "name": "isActive",
688
+ "returns": "boolean"
689
+ },
690
+ {
691
+ "name": "isCompromised",
692
+ "returns": "boolean"
693
+ },
694
+ {
695
+ "name": "isRetired",
696
+ "returns": "boolean"
697
+ },
698
+ {
699
+ "name": "markCompromised",
700
+ "returns": "void"
701
+ },
702
+ {
703
+ "name": "needsRotation",
704
+ "returns": "boolean"
705
+ },
706
+ {
707
+ "name": "retire",
708
+ "returns": "void"
709
+ }
710
+ ],
390
711
  "surfaces": [
391
712
  {
392
713
  "kind": "cli",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-secrets",
3
- "version": "0.40.64",
3
+ "version": "0.40.66",
4
4
  "description": "Per-tenant secret management with envelope encryption for SMRT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -27,19 +27,19 @@
27
27
  }
28
28
  },
29
29
  "dependencies": {
30
- "@happyvertical/logger": "^0.86.1",
31
- "@happyvertical/secrets": "^0.86.1",
32
- "@happyvertical/sql": "^0.86.1",
33
- "@happyvertical/utils": "^0.86.1",
34
- "@happyvertical/smrt-core": "0.40.64",
35
- "@happyvertical/smrt-tenancy": "0.40.64"
30
+ "@happyvertical/logger": "^0.86.3",
31
+ "@happyvertical/secrets": "^0.86.3",
32
+ "@happyvertical/sql": "^0.86.3",
33
+ "@happyvertical/utils": "^0.86.3",
34
+ "@happyvertical/smrt-core": "0.40.66",
35
+ "@happyvertical/smrt-tenancy": "0.40.66"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "24.13.2",
39
39
  "typescript": "5.9.3",
40
40
  "vite": "8.1.4",
41
41
  "vitest": "4.1.10",
42
- "@happyvertical/smrt-vitest": "0.40.64"
42
+ "@happyvertical/smrt-vitest": "0.40.66"
43
43
  },
44
44
  "keywords": [
45
45
  "smrt",
@@ -1 +0,0 @@
1
- {"version":3,"file":"TenantKey-B7LnzyOd.js","names":[],"sources":["../../src/__smrt-register__.ts","../../src/models/SecretAuditLog.ts","../../src/models/Secret.ts","../../src/models/TenantKey.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * SecretAuditLog model - Audit trail for secret operations\n * @packageDocumentation\n */\n\nimport type {\n SmrtCreateInput,\n SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport {\n crossPackageRef,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\n\n/**\n * Secret audit action types\n */\nexport type SecretAuditAction =\n | 'create'\n | 'read'\n | 'update'\n | 'delete'\n | 'rotate_key'\n | 'disable'\n | 'enable'\n | 'expire';\n\n/**\n * Audit result types\n */\nexport type SecretAuditResult = 'success' | 'failure' | 'denied';\n\n/**\n * Constructor options for {@link SecretAuditLog}. Each field is optional and\n * mirrors a persisted column.\n */\nexport interface SecretAuditLogOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n secretId?: string | null;\n secretName?: string;\n userId?: string | null;\n action?: SecretAuditAction;\n result?: SecretAuditResult;\n ipAddress?: string;\n userAgent?: string;\n details?: Record<string, unknown>;\n}\n\n/**\n * SecretAuditLog records all operations on secrets for compliance\n * and security monitoring.\n *\n * Every secret operation (create, read, update, delete, key rotation)\n * is logged with the user, action, result, and relevant details.\n *\n * **Retention**: Audit logs should be retained according to your\n * compliance requirements (typically 1-7 years).\n *\n * @example\n * ```typescript\n * // Query recent audit logs\n * const logs = await auditLogs.list({\n * where: { tenantId: 'tenant-123' },\n * orderBy: 'created_at DESC',\n * limit: 100\n * });\n *\n * // Filter by action\n * const reads = await auditLogs.list({\n * where: {\n * tenantId: 'tenant-123',\n * action: 'read'\n * }\n * });\n *\n * // Filter by secret name\n * const apiKeyLogs = await auditLogs.list({\n * where: {\n * tenantId: 'tenant-123',\n * secretName: 'stripe-api-key'\n * }\n * });\n * ```\n */\n// Intentional exception to standards.md §7 (`@TenantScoped({ mode: 'optional' })`):\n// `SecretAuditLog` uses the inline `tenantScoped: true` form on `@smrt()` rather than\n// the dedicated `@TenantScoped` decorator. Audit-trail queries are read-mostly and run\n// in mixed contexts — under a tenant for tenant-scoped reports, and (prospectively)\n// under super-admin bypass for compliance review. Cross-tenant audit queries should\n// be wrapped in `withSuperAdminBypass()` from `@happyvertical/smrt-tenancy` at the\n// call site; this package has no such call sites today, so the pattern is\n// prescriptive guidance for compliance tooling rather than current practice. See\n// `packages/secrets/CLAUDE.md` \"Known exceptions to monorepo standards\" for context.\n@smrt({\n tenantScoped: true,\n api: { include: [] }, // No API exposure\n mcp: { include: [] }, // No MCP exposure\n // CLI runs in-process for compliance review; HTTP exposure is intentionally\n // excluded, so skipApiCheck acknowledges the cli.include / api.include divergence.\n cli: { include: ['list'], skipApiCheck: true },\n})\nexport class SecretAuditLog extends SmrtObject {\n /**\n * Tenant associated with the audited secret operation.\n */\n tenantId: string | null = null;\n\n /**\n * ID of the secret (may be null for deleted secrets)\n */\n @foreignKey('Secret')\n secretId: string | null = null;\n\n /**\n * Name of the secret at the time of the operation\n */\n secretName: string = '';\n\n /**\n * ID of the user who performed the action, or `null` for system-initiated\n * operations with no authenticated user. Stored as a native `uuid` column on\n * Postgres, so a non-UUID actor sentinel must never reach it —\n * {@link createAuditEntry} normalizes the `'system'` sentinel to null (#1444).\n */\n @crossPackageRef('@happyvertical/smrt-users:User')\n userId: string | null = null;\n\n /**\n * The action that was performed\n */\n action: SecretAuditAction = 'read';\n\n /**\n * Result of the operation\n */\n result: SecretAuditResult = 'success';\n\n /**\n * IP address of the client (if available)\n */\n ipAddress: string = '';\n\n /**\n * User agent string (if available)\n */\n userAgent: string = '';\n\n /**\n * Additional context about the operation\n */\n details: Record<string, unknown> = {};\n\n constructor(options: SecretAuditLogOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) {\n this.tenantId = options.tenantId;\n }\n if (options.secretId !== undefined) this.secretId = options.secretId;\n if (options.secretName !== undefined) this.secretName = options.secretName;\n if (options.userId !== undefined) this.userId = options.userId;\n if (options.action !== undefined) this.action = options.action;\n if (options.result !== undefined) this.result = options.result;\n if (options.ipAddress !== undefined) this.ipAddress = options.ipAddress;\n if (options.userAgent !== undefined) this.userAgent = options.userAgent;\n if (options.details !== undefined) this.details = options.details;\n }\n\n /**\n * Check if this was a successful operation\n */\n isSuccess(): boolean {\n return this.result === 'success';\n }\n\n /**\n * Check if this was a failed operation\n */\n isFailure(): boolean {\n return this.result === 'failure';\n }\n\n /**\n * Check if this was a denied operation (permission denied)\n */\n isDenied(): boolean {\n return this.result === 'denied';\n }\n\n /**\n * Check if this is a read operation\n */\n isReadAction(): boolean {\n return this.action === 'read';\n }\n\n /**\n * Check if this is a write operation (create, update, delete)\n */\n isWriteAction(): boolean {\n return ['create', 'update', 'delete'].includes(this.action);\n }\n\n /**\n * Check if this is a key operation\n */\n isKeyOperation(): boolean {\n return this.action === 'rotate_key';\n }\n}\n\n/**\n * Create an audit log entry for a secret operation\n */\nexport function createAuditEntry(params: {\n secretId?: string | null;\n secretName: string;\n tenantId?: string | null;\n userId?: string | null;\n action: SecretAuditAction;\n result: SecretAuditResult;\n ipAddress?: string;\n userAgent?: string;\n details?: Record<string, unknown>;\n}): SmrtCreateInput<SecretAuditLog> {\n const tenantId =\n params.tenantId === undefined || params.tenantId === 'system'\n ? null\n : params.tenantId;\n\n // The `userId` column is a native `uuid` on Postgres. A missing actor and the\n // `'system'`/empty sentinels are not valid UUIDs, so normalize them all to\n // null — a system-initiated operation has no authenticated user. Callers may\n // pass `null`/`undefined` directly to express that (#1444).\n const userId =\n params.userId == null || params.userId === 'system' || params.userId === ''\n ? null\n : params.userId;\n\n const entry: SmrtCreateInput<SecretAuditLog> = {\n secretId: params.secretId ?? null,\n secretName: params.secretName,\n userId,\n action: params.action,\n result: params.result,\n ipAddress: params.ipAddress ?? '',\n userAgent: params.userAgent ?? '',\n details: params.details ?? {},\n tenantId,\n };\n return entry;\n}\n","/**\n * Secret model - Tenant-scoped encrypted secrets storage\n * @packageDocumentation\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\n\n/**\n * Secret status values\n */\nexport type SecretStatus = 'active' | 'disabled' | 'expired';\n\n/**\n * Constructor options for {@link Secret}. Each field is optional and mirrors a\n * persisted column; date fields also accept the serialized forms accepted by\n * `new Date()` so hydrated rows coerce cleanly.\n */\nexport interface SecretOptions extends SmrtObjectOptions {\n tenantId?: string;\n name?: string;\n description?: string;\n category?: string;\n encryptedValue?: string;\n keyVersion?: number;\n status?: SecretStatus;\n expiresAt?: Date | string | number | null;\n lastAccessedAt?: Date | string | number | null;\n accessCount?: number;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Secret represents an encrypted value stored per-tenant.\n *\n * Secrets are tenant-scoped and use envelope encryption:\n * - Each tenant has their own Data Encryption Key (TDEK)\n * - The TDEK is wrapped by the Application Master Key (AMK)\n * - Secret values are encrypted by the unwrapped TDEK\n *\n * **Security**: This model deliberately excludes API and MCP exposure\n * to prevent accidental secret leakage. Secrets are only accessible\n * via CLI commands or direct service calls.\n *\n * @example\n * ```typescript\n * import { SecretService } from '@happyvertical/smrt-secrets';\n *\n * const service = await SecretService.create({ db });\n *\n * await withTenant({ tenantId: 'tenant-123' }, async () => {\n * // Store a secret\n * await service.store('api-key', 'sk_live_xxx', { category: 'stripe' });\n *\n * // Retrieve (auto-decrypts)\n * const apiKey = await service.retrieve('api-key');\n * });\n * ```\n */\n// Intentional exception to standards.md §7 (`@TenantScoped({ mode: 'optional' })`):\n// `Secret` uses the inline `tenantScoped: true` form on `@smrt()` rather than the\n// dedicated `@TenantScoped` decorator from `@happyvertical/smrt-tenancy`. The boolean\n// form gives us required-mode tenant scoping without depending on the tenancy package\n// at the model layer, while `SecretService` performs manual scoping by setting\n// `context = tenantId` on each row. The `(slug, context)` upsert key derived from the\n// base `SmrtObject` fields is what gives different tenants isolated namespaces for\n// secret names — switching to the decorator without changing the upsert key would\n// surface false-positive name collisions across tenants. See\n// `packages/secrets/CLAUDE.md` \"Known exceptions to monorepo standards\" for context.\n@smrt({\n tenantScoped: true,\n // NO API or MCP exposure for security\n api: { include: [] },\n mcp: { include: [] },\n // CLI runs in-process; secrets must never be reachable over HTTP, so\n // skipApiCheck acknowledges the cli.include / api.include divergence.\n cli: { include: ['list'], skipApiCheck: true }, // Only list names, not values\n})\nexport class Secret extends SmrtObject {\n /**\n * Tenant that owns this secret. Also stored in context for per-tenant name uniqueness.\n */\n tenantId: string = '';\n\n /**\n * Unique name for the secret within the tenant\n */\n name: string = '';\n\n /**\n * Human-readable description\n */\n description: string = '';\n\n /**\n * Category for organization (e.g., 'database', 'api-key', 'oauth')\n */\n category: string = '';\n\n /**\n * JSON-encoded EncryptedEnvelope from @happyvertical/secrets\n */\n encryptedValue: string = '';\n\n /**\n * Version of the tenant key used to encrypt this secret\n */\n keyVersion: number = 1;\n\n /**\n * Current status of the secret\n */\n status: SecretStatus = 'active';\n\n /**\n * Optional expiration date\n */\n expiresAt: Date | null = null;\n\n /**\n * Last time this secret was accessed (decrypted)\n */\n lastAccessedAt: Date | null = null;\n\n /**\n * Number of times this secret has been accessed\n */\n accessCount: number = 0;\n\n /**\n * Additional metadata stored with the secret\n */\n metadata: Record<string, unknown> = {};\n\n constructor(options: SecretOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.name !== undefined) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.category !== undefined) this.category = options.category;\n if (options.encryptedValue !== undefined)\n this.encryptedValue = options.encryptedValue;\n if (options.keyVersion !== undefined) this.keyVersion = options.keyVersion;\n if (options.status !== undefined) this.status = options.status;\n if (options.expiresAt !== undefined) {\n this.expiresAt =\n options.expiresAt instanceof Date\n ? options.expiresAt\n : options.expiresAt\n ? new Date(options.expiresAt)\n : null;\n }\n if (options.lastAccessedAt !== undefined) {\n this.lastAccessedAt =\n options.lastAccessedAt instanceof Date\n ? options.lastAccessedAt\n : options.lastAccessedAt\n ? new Date(options.lastAccessedAt)\n : null;\n }\n if (options.accessCount !== undefined)\n this.accessCount = options.accessCount;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n /**\n * Check if the secret is currently active\n */\n isActive(): boolean {\n return this.status === 'active';\n }\n\n /**\n * Check if the secret has expired\n */\n isExpired(): boolean {\n if (!this.expiresAt) return false;\n return new Date() >= this.expiresAt;\n }\n\n /**\n * Check if the secret can be used (active and not expired)\n */\n isUsable(): boolean {\n return this.isActive() && !this.isExpired();\n }\n\n /**\n * Record an access to this secret\n */\n recordAccess(): void {\n this.lastAccessedAt = new Date();\n this.accessCount += 1;\n }\n\n /**\n * Disable the secret\n */\n disable(): void {\n this.status = 'disabled';\n }\n\n /**\n * Enable the secret\n */\n enable(): void {\n this.status = 'active';\n }\n}\n","/**\n * TenantKey model - Tracks per-tenant Data Encryption Keys (TDEKs)\n * @packageDocumentation\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\n\n/**\n * Key status values\n */\nexport type TenantKeyStatus = 'active' | 'rotating' | 'retired' | 'compromised';\n\n/**\n * Constructor options for {@link TenantKey}. Each field is optional and mirrors\n * a persisted column; date fields also accept the serialized forms accepted by\n * `new Date()` so hydrated rows coerce cleanly.\n */\nexport interface TenantKeyOptions extends SmrtObjectOptions {\n tenantId?: string;\n wrappedKey?: string;\n amkKeyId?: string;\n status?: TenantKeyStatus;\n version?: number;\n rotateAfter?: Date | string | number | null;\n retiredAt?: Date | string | number | null;\n}\n\n/**\n * TenantKey tracks the per-tenant Data Encryption Keys (TDEKs).\n *\n * Each tenant has one or more TDEKs stored in wrapped form. The wrapped key\n * can only be decrypted using the Application Master Key (AMK).\n *\n * **Key Lifecycle**:\n * 1. `active` - Current key used for encryption\n * 2. `rotating` - Transitional state during rotation\n * 3. `retired` - Old key kept for decryption of existing secrets\n * 4. `compromised` - Key marked as compromised, should not be used\n *\n * **Note**: This model is NOT tenant-scoped itself because it tracks\n * keys FOR tenants, not secrets owned BY tenants.\n *\n * @example\n * ```typescript\n * // Get active key for a tenant\n * const key = await tenantKeys.get({\n * tenantId: 'tenant-123',\n * status: 'active'\n * });\n *\n * // List all key versions for a tenant\n * const versions = await tenantKeys.list({\n * where: { tenantId: 'tenant-123' },\n * orderBy: 'version DESC'\n * });\n * ```\n */\n// Intentional exception to standards.md §7 (`@TenantScoped({ mode: 'optional' })`):\n// `TenantKey` is deliberately NOT tenant-scoped — neither via the decorator nor via\n// `tenantScoped: true` on `@smrt()`. The row carries a `tenantId` column because each\n// TDEK belongs to a tenant, but the model itself must remain queryable across tenants\n// (e.g. cross-tenant key-rotation tooling, AMK rewrap jobs, super-admin auditing).\n// Adding the tenancy interceptor here would silently filter out keys the rotation\n// service needs to inspect. See `packages/secrets/CLAUDE.md` \"Known exceptions to\n// monorepo standards\" for the full rationale.\n@smrt({\n // NOT tenant-scoped - this model tracks keys FOR tenants\n api: { include: [] }, // No API exposure\n mcp: { include: [] }, // No MCP exposure\n // CLI runs in-process for key-rotation tooling and audit; HTTP exposure is\n // intentionally excluded, so skipApiCheck acknowledges the divergence.\n cli: { include: ['list', 'get'], skipApiCheck: true },\n})\nexport class TenantKey extends SmrtObject {\n /**\n * Tenant ID this key belongs to\n */\n tenantId: string = '';\n\n /**\n * Wrapped key data (format: wrappedKey:iv:authTag)\n */\n wrappedKey: string = '';\n\n /**\n * ID of the AMK used to wrap this key\n */\n amkKeyId: string = '';\n\n /**\n * Current status of the key\n */\n status: TenantKeyStatus = 'active';\n\n /**\n * Version number (increments on rotation)\n */\n version: number = 1;\n\n /**\n * Recommended rotation date\n */\n rotateAfter: Date | null = null;\n\n /**\n * When the key was retired (if applicable)\n */\n retiredAt: Date | null = null;\n\n constructor(options: TenantKeyOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.wrappedKey !== undefined) this.wrappedKey = options.wrappedKey;\n if (options.amkKeyId !== undefined) this.amkKeyId = options.amkKeyId;\n if (options.status !== undefined) this.status = options.status;\n if (options.version !== undefined) this.version = options.version;\n if (options.rotateAfter !== undefined) {\n this.rotateAfter =\n options.rotateAfter instanceof Date\n ? options.rotateAfter\n : options.rotateAfter\n ? new Date(options.rotateAfter)\n : null;\n }\n if (options.retiredAt !== undefined) {\n this.retiredAt =\n options.retiredAt instanceof Date\n ? options.retiredAt\n : options.retiredAt\n ? new Date(options.retiredAt)\n : null;\n }\n }\n\n /**\n * Check if this key is currently active\n */\n isActive(): boolean {\n return this.status === 'active';\n }\n\n /**\n * Check if this key needs rotation\n */\n needsRotation(): boolean {\n if (!this.rotateAfter) return false;\n return new Date() >= this.rotateAfter;\n }\n\n /**\n * Check if this key is retired\n */\n isRetired(): boolean {\n return this.status === 'retired';\n }\n\n /**\n * Check if this key is compromised\n */\n isCompromised(): boolean {\n return this.status === 'compromised';\n }\n\n /**\n * Check if this key can be used for decryption\n * (active or retired keys can decrypt)\n */\n canDecrypt(): boolean {\n return this.status === 'active' || this.status === 'retired';\n }\n\n /**\n * Check if this key can be used for encryption\n * (only active keys should encrypt)\n */\n canEncrypt(): boolean {\n return this.status === 'active';\n }\n\n /**\n * Mark this key as retired\n */\n retire(): void {\n this.status = 'retired';\n this.retiredAt = new Date();\n }\n\n /**\n * Mark this key as compromised\n */\n markCompromised(): void {\n this.status = 'compromised';\n }\n}\n"],"mappings":";;;;;;;;;;;;;ACuGO,IAAA,iBAAA,MAAM,uBAAuB,WAAW;;;;CAI7C,WAA0B;;;;CAK1B,WAC0B;;;;CAK1B,aAAqB;;;;;;;CAQrB,SACwB;;;;CAKxB,SAA4B;;;;CAK5B,SAA4B;;;;CAK5B,YAAoB;;;;CAKpB,YAAoB;;;;CAKpB,UAAmC,CAAC;CAEpC,YAAY,UAAiC,CAAC,GAAG;EAC/C,MAAM,OAAO;EACb,IAAI,QAAQ,aAAa,KAAA,GACvB,KAAK,WAAW,QAAQ;EAE1B,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,eAAe,KAAA,GAAW,KAAK,aAAa,QAAQ;EAChE,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;EACxD,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;EACxD,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;EACxD,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,YAAY,KAAA,GAAW,KAAK,UAAU,QAAQ;CAC5D;;;;CAKA,YAAqB;EACnB,OAAO,KAAK,WAAW;CACzB;;;;CAKA,YAAqB;EACnB,OAAO,KAAK,WAAW;CACzB;;;;CAKA,WAAoB;EAClB,OAAO,KAAK,WAAW;CACzB;;;;CAKA,eAAwB;EACtB,OAAO,KAAK,WAAW;CACzB;;;;CAKA,gBAAyB;EACvB,OAAO;GAAC;GAAU;GAAU;EAAQ,CAAC,CAAC,SAAS,KAAK,MAAM;CAC5D;;;;CAKA,iBAA0B;EACxB,OAAO,KAAK,WAAW;CACzB;AACF;YAlGG,WAAW,QAAQ,CAAA,GAAA,eAAA,WAAA,YAAA,KAAA,CAAA;YAcnB,gBAAgB,gCAAgC,CAAA,GAAA,eAAA,WAAA,UAAA,KAAA,CAAA;6BA/BlD,KAAK;CACJ,cAAc;CACd,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;CAGnB,KAAK;EAAE,SAAS,CAAC,MAAM;EAAG,cAAc;CAAK;AAC/C,CAAC,CAAA,GAAA,cAAA;;;;AAiHD,SAAgB,iBAAiB,QAUG;CAClC,MAAM,WACJ,OAAO,aAAa,KAAA,KAAa,OAAO,aAAa,WACjD,OACA,OAAO;CAMb,MAAM,SACJ,OAAO,UAAU,QAAQ,OAAO,WAAW,YAAY,OAAO,WAAW,KACrE,OACA,OAAO;CAab,OAAO;EAVL,UAAU,OAAO,YAAY;EAC7B,YAAY,OAAO;EACnB;EACA,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,WAAW,OAAO,aAAa;EAC/B,WAAW,OAAO,aAAa;EAC/B,SAAS,OAAO,WAAW,CAAC;EAC5B;CAEK;AACT;;;AC9KO,IAAA,SAAA,MAAM,eAAe,WAAW;;;;CAIrC,WAAmB;;;;CAKnB,OAAe;;;;CAKf,cAAsB;;;;CAKtB,WAAmB;;;;CAKnB,iBAAyB;;;;CAKzB,aAAqB;;;;CAKrB,SAAuB;;;;CAKvB,YAAyB;;;;CAKzB,iBAA8B;;;;CAK9B,cAAsB;;;;CAKtB,WAAoC,CAAC;CAErC,YAAY,UAAyB,CAAC,GAAG;EACvC,MAAM,OAAO;EACb,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,SAAS,KAAA,GAAW,KAAK,OAAO,QAAQ;EACpD,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,mBAAmB,KAAA,GAC7B,KAAK,iBAAiB,QAAQ;EAChC,IAAI,QAAQ,eAAe,KAAA,GAAW,KAAK,aAAa,QAAQ;EAChE,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;EACxD,IAAI,QAAQ,cAAc,KAAA,GACxB,KAAK,YACH,QAAQ,qBAAqB,OACzB,QAAQ,YACR,QAAQ,YACN,IAAI,KAAK,QAAQ,SAAS,IAC1B;EAEV,IAAI,QAAQ,mBAAmB,KAAA,GAC7B,KAAK,iBACH,QAAQ,0BAA0B,OAC9B,QAAQ,iBACR,QAAQ,iBACN,IAAI,KAAK,QAAQ,cAAc,IAC/B;EAEV,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;CAC9D;;;;CAKA,WAAoB;EAClB,OAAO,KAAK,WAAW;CACzB;;;;CAKA,YAAqB;EACnB,IAAI,CAAC,KAAK,WAAW,OAAO;EAC5B,uBAAO,IAAI,KAAK,KAAK,KAAK;CAC5B;;;;CAKA,WAAoB;EAClB,OAAO,KAAK,SAAS,KAAK,CAAC,KAAK,UAAU;CAC5C;;;;CAKA,eAAqB;EACnB,KAAK,iCAAiB,IAAI,KAAK;EAC/B,KAAK,eAAe;CACtB;;;;CAKA,UAAgB;EACd,KAAK,SAAS;CAChB;;;;CAKA,SAAe;EACb,KAAK,SAAS;CAChB;AACF;qBA5IC,KAAK;CACJ,cAAc;CAEd,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;CAGnB,KAAK;EAAE,SAAS,CAAC,MAAM;EAAG,cAAc;CAAK;AAC/C,CAAC,CAAA,GAAA,MAAA;;;ACHM,IAAA,YAAA,MAAM,kBAAkB,WAAW;;;;CAIxC,WAAmB;;;;CAKnB,aAAqB;;;;CAKrB,WAAmB;;;;CAKnB,SAA0B;;;;CAK1B,UAAkB;;;;CAKlB,cAA2B;;;;CAK3B,YAAyB;CAEzB,YAAY,UAA4B,CAAC,GAAG;EAC1C,MAAM,OAAO;EACb,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,eAAe,KAAA,GAAW,KAAK,aAAa,QAAQ;EAChE,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;EACxD,IAAI,QAAQ,YAAY,KAAA,GAAW,KAAK,UAAU,QAAQ;EAC1D,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cACH,QAAQ,uBAAuB,OAC3B,QAAQ,cACR,QAAQ,cACN,IAAI,KAAK,QAAQ,WAAW,IAC5B;EAEV,IAAI,QAAQ,cAAc,KAAA,GACxB,KAAK,YACH,QAAQ,qBAAqB,OACzB,QAAQ,YACR,QAAQ,YACN,IAAI,KAAK,QAAQ,SAAS,IAC1B;CAEZ;;;;CAKA,WAAoB;EAClB,OAAO,KAAK,WAAW;CACzB;;;;CAKA,gBAAyB;EACvB,IAAI,CAAC,KAAK,aAAa,OAAO;EAC9B,uBAAO,IAAI,KAAK,KAAK,KAAK;CAC5B;;;;CAKA,YAAqB;EACnB,OAAO,KAAK,WAAW;CACzB;;;;CAKA,gBAAyB;EACvB,OAAO,KAAK,WAAW;CACzB;;;;;CAMA,aAAsB;EACpB,OAAO,KAAK,WAAW,YAAY,KAAK,WAAW;CACrD;;;;;CAMA,aAAsB;EACpB,OAAO,KAAK,WAAW;CACzB;;;;CAKA,SAAe;EACb,KAAK,SAAS;EACd,KAAK,4BAAY,IAAI,KAAK;CAC5B;;;;CAKA,kBAAwB;EACtB,KAAK,SAAS;CAChB;AACF;wBAhIC,KAAK;CAEJ,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;CAGnB,KAAK;EAAE,SAAS,CAAC,QAAQ,KAAK;EAAG,cAAc;CAAK;AACtD,CAAC,CAAA,GAAA,SAAA"}