{
  "schemaVersion": 1,
  "sensitiveFieldsExcluded": true,
  "generatedAt": "1970-01-01T00:00:00.000Z",
  "packageName": "@happyvertical/smrt-secrets",
  "packageVersion": "0.42.7",
  "sourceManifestPath": "dist/manifest.json",
  "agentDocPath": "AGENTS.md",
  "sourceHashes": {
    "manifest": "bf4aa273340d5e9df6f55fb5fb2eca92983f920e6e3af90562944c7b818d9a59",
    "packageJson": "80c3dfaa1b03dca89c8caf3c4bd4a4f19934aca443d20e3f640d55ec3bc1cb77",
    "agents": "c2be546ffbe79a0df95401c10a60e23f4f31cb8232cb1349fcdb7e5682852d4a"
  },
  "exports": [
    ".",
    "./manifest",
    "./manifest.json",
    "./models",
    "./service"
  ],
  "dependencies": {
    "@happyvertical/logger": "catalog:",
    "@happyvertical/secrets": "catalog:",
    "@happyvertical/smrt-core": "workspace:*",
    "@happyvertical/smrt-tenancy": "workspace:*",
    "@happyvertical/sql": "catalog:",
    "@happyvertical/utils": "catalog:",
    "@happyvertical/smrt-vitest": "workspace:*",
    "@types/node": "24.13.2",
    "typescript": "5.9.3",
    "vite": "8.1.4",
    "vitest": "4.1.10"
  },
  "smrtDependencies": [
    "@happyvertical/smrt-core",
    "@happyvertical/smrt-tenancy",
    "@happyvertical/smrt-vitest"
  ],
  "sdkDependencies": [
    "@happyvertical/logger",
    "@happyvertical/secrets",
    "@happyvertical/sql",
    "@happyvertical/utils"
  ],
  "tags": [],
  "risks": [],
  "objects": [
    {
      "name": "SecretAuditLogCollection",
      "qualifiedName": "@happyvertical/smrt-secrets:SecretAuditLogCollection",
      "collection": "secretauditlogs",
      "tableName": "secret_audit_log_collections",
      "packageName": "@happyvertical/smrt-secrets",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "cleanup",
        "countByAction",
        "countByResult",
        "getRecentDenials",
        "getRecentFailures",
        "getSecretHistory",
        "getUserActivity",
        "listLogs"
      ],
      "methodSignatures": [
        {
          "name": "cleanup",
          "async": true,
          "params": [
            "olderThanDays?: number"
          ],
          "returns": "Promise<number>"
        },
        {
          "name": "countByAction",
          "async": true,
          "params": [
            "tenantId: string | null",
            "since?: Date"
          ],
          "returns": "Promise<Record<SecretAuditAction, number>>"
        },
        {
          "name": "countByResult",
          "async": true,
          "params": [
            "tenantId: string | null",
            "since?: Date"
          ],
          "returns": "Promise<Record<SecretAuditResult, number>>"
        },
        {
          "name": "getRecentDenials",
          "async": true,
          "params": [
            "tenantId: string | null",
            "limit?: number"
          ],
          "returns": "Promise<SecretAuditLog[]>"
        },
        {
          "name": "getRecentFailures",
          "async": true,
          "params": [
            "tenantId: string | null",
            "limit?: number"
          ],
          "returns": "Promise<SecretAuditLog[]>"
        },
        {
          "name": "getSecretHistory",
          "async": true,
          "params": [
            "tenantId: string | null",
            "secretName: string",
            "limit?: number"
          ],
          "returns": "Promise<SecretAuditLog[]>"
        },
        {
          "name": "getUserActivity",
          "async": true,
          "params": [
            "tenantId: string | null",
            "userId: string",
            "limit?: number"
          ],
          "returns": "Promise<SecretAuditLog[]>"
        },
        {
          "name": "listLogs",
          "async": true,
          "params": [
            "options?: ListAuditLogsOptions"
          ],
          "returns": "Promise<SecretAuditLog[]>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "SecretCollection",
      "qualifiedName": "@happyvertical/smrt-secrets:SecretCollection",
      "collection": "secrets",
      "tableName": "secret_collections",
      "packageName": "@happyvertical/smrt-secrets",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "countByStatus",
        "deleteByName",
        "findByName",
        "getCategories",
        "listActive",
        "listByCategory",
        "listExpiring",
        "listSecrets"
      ],
      "methodSignatures": [
        {
          "name": "countByStatus",
          "async": true,
          "params": [
            "tenantId: string"
          ],
          "returns": "Promise<Record<SecretStatus, number>>"
        },
        {
          "name": "deleteByName",
          "async": true,
          "params": [
            "tenantId: string",
            "name: string"
          ],
          "returns": "Promise<boolean>"
        },
        {
          "name": "findByName",
          "async": true,
          "params": [
            "tenantId: string",
            "name: string"
          ],
          "returns": "Promise<Secret | null>"
        },
        {
          "name": "getCategories",
          "async": true,
          "params": [
            "tenantId: string"
          ],
          "returns": "Promise<string[]>"
        },
        {
          "name": "listActive",
          "async": true,
          "params": [
            "tenantId: string"
          ],
          "returns": "Promise<Secret[]>"
        },
        {
          "name": "listByCategory",
          "async": true,
          "params": [
            "tenantId: string",
            "category: string"
          ],
          "returns": "Promise<Secret[]>"
        },
        {
          "name": "listExpiring",
          "async": true,
          "params": [
            "tenantId: string",
            "daysAhead?: number"
          ],
          "returns": "Promise<Secret[]>"
        },
        {
          "name": "listSecrets",
          "async": true,
          "params": [
            "tenantId: string",
            "options?: ListSecretsOptions"
          ],
          "returns": "Promise<Secret[]>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "TenantKeyCollection",
      "qualifiedName": "@happyvertical/smrt-secrets:TenantKeyCollection",
      "collection": "tenantkeys",
      "tableName": "tenant_key_collections",
      "packageName": "@happyvertical/smrt-secrets",
      "extends": "SmrtCollection",
      "fields": [],
      "relationships": [],
      "methods": [
        "cleanupRetiredKeys",
        "countByStatus",
        "findKeysNeedingRotation",
        "getActiveKey",
        "getKeyVersion",
        "listAllActiveKeys",
        "listKeyVersions",
        "markCompromised"
      ],
      "methodSignatures": [
        {
          "name": "cleanupRetiredKeys",
          "async": true,
          "params": [
            "olderThanDays?: number"
          ],
          "returns": "Promise<number>"
        },
        {
          "name": "countByStatus",
          "async": true,
          "returns": "Promise<Record<TenantKeyStatus, number>>"
        },
        {
          "name": "findKeysNeedingRotation",
          "async": true,
          "returns": "Promise<TenantKey[]>"
        },
        {
          "name": "getActiveKey",
          "async": true,
          "params": [
            "tenantId: string"
          ],
          "returns": "Promise<TenantKey | null>"
        },
        {
          "name": "getKeyVersion",
          "async": true,
          "params": [
            "tenantId: string",
            "version: number"
          ],
          "returns": "Promise<TenantKey | null>"
        },
        {
          "name": "listAllActiveKeys",
          "async": true,
          "returns": "Promise<TenantKey[]>"
        },
        {
          "name": "listKeyVersions",
          "async": true,
          "params": [
            "tenantId: string"
          ],
          "returns": "Promise<TenantKey[]>"
        },
        {
          "name": "markCompromised",
          "async": true,
          "params": [
            "tenantId: string",
            "keyId: string"
          ],
          "returns": "Promise<boolean>"
        }
      ],
      "surfaces": [],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "Secret",
      "qualifiedName": "@happyvertical/smrt-secrets:Secret",
      "collection": "secrets",
      "tableName": "secrets",
      "packageName": "@happyvertical/smrt-secrets",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": false,
          "columnType": "UUID",
          "default": ""
        },
        {
          "name": "name",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "description",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "category",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "encryptedValue",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "keyVersion",
          "type": "integer",
          "required": false,
          "columnType": "INTEGER",
          "default": 1
        },
        {
          "name": "status",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "active"
        },
        {
          "name": "expiresAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "lastAccessedAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "accessCount",
          "type": "integer",
          "required": false,
          "columnType": "INTEGER",
          "default": 0
        },
        {
          "name": "metadata",
          "type": "json",
          "required": false,
          "columnType": "JSON",
          "default": {}
        }
      ],
      "relationships": [],
      "methods": [
        "disable",
        "enable",
        "isActive",
        "isExpired",
        "isUsable",
        "recordAccess"
      ],
      "methodSignatures": [
        {
          "name": "disable",
          "returns": "void"
        },
        {
          "name": "enable",
          "returns": "void"
        },
        {
          "name": "isActive",
          "returns": "boolean"
        },
        {
          "name": "isExpired",
          "returns": "boolean"
        },
        {
          "name": "isUsable",
          "returns": "boolean"
        },
        {
          "name": "recordAccess",
          "returns": "void"
        }
      ],
      "tenant": {
        "scoped": true,
        "mode": "required",
        "field": "tenantId"
      },
      "conflictColumns": [
        "tenant_id",
        "slug",
        "context"
      ],
      "surfaces": [
        {
          "kind": "cli",
          "name": "secret_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-secrets:Secret"
        }
      ],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "SecretAuditLog",
      "qualifiedName": "@happyvertical/smrt-secrets:SecretAuditLog",
      "collection": "secretauditlogs",
      "tableName": "secret_audit_logs",
      "packageName": "@happyvertical/smrt-secrets",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": false,
          "columnType": "UUID"
        },
        {
          "name": "secretId",
          "type": "foreignKey",
          "required": false,
          "related": "Secret",
          "columnType": "UUID"
        },
        {
          "name": "secretName",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "userId",
          "type": "crossPackageRef",
          "required": false,
          "related": "@happyvertical/smrt-users:User",
          "columnType": "UUID"
        },
        {
          "name": "action",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "read"
        },
        {
          "name": "result",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "success"
        },
        {
          "name": "ipAddress",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "userAgent",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "details",
          "type": "json",
          "required": false,
          "columnType": "JSON",
          "default": {}
        }
      ],
      "relationships": [
        {
          "name": "secretId",
          "type": "foreignKey",
          "required": false,
          "related": "Secret",
          "columnType": "UUID"
        },
        {
          "name": "userId",
          "type": "crossPackageRef",
          "required": false,
          "related": "@happyvertical/smrt-users:User",
          "columnType": "UUID"
        }
      ],
      "methods": [
        "isDenied",
        "isFailure",
        "isKeyOperation",
        "isReadAction",
        "isSuccess",
        "isWriteAction"
      ],
      "methodSignatures": [
        {
          "name": "isDenied",
          "returns": "boolean"
        },
        {
          "name": "isFailure",
          "returns": "boolean"
        },
        {
          "name": "isKeyOperation",
          "returns": "boolean"
        },
        {
          "name": "isReadAction",
          "returns": "boolean"
        },
        {
          "name": "isSuccess",
          "returns": "boolean"
        },
        {
          "name": "isWriteAction",
          "returns": "boolean"
        }
      ],
      "tenant": {
        "scoped": true,
        "mode": "required",
        "field": "tenantId"
      },
      "conflictColumns": [
        "tenant_id",
        "slug",
        "context"
      ],
      "surfaces": [
        {
          "kind": "cli",
          "name": "secretauditlog_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-secrets:SecretAuditLog"
        }
      ],
      "relationshipFeatures": [
        "crossPackageRef",
        "foreignKey",
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    },
    {
      "name": "TenantKey",
      "qualifiedName": "@happyvertical/smrt-secrets:TenantKey",
      "collection": "tenantkeys",
      "tableName": "tenant_keys",
      "packageName": "@happyvertical/smrt-secrets",
      "extends": "SmrtObject",
      "fields": [
        {
          "name": "tenantId",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "wrappedKey",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "amkKeyId",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": ""
        },
        {
          "name": "status",
          "type": "text",
          "required": false,
          "columnType": "TEXT",
          "default": "active"
        },
        {
          "name": "version",
          "type": "integer",
          "required": false,
          "columnType": "INTEGER",
          "default": 1
        },
        {
          "name": "rotateAfter",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        },
        {
          "name": "retiredAt",
          "type": "datetime",
          "required": false,
          "columnType": "TIMESTAMP"
        }
      ],
      "relationships": [],
      "methods": [
        "canDecrypt",
        "canEncrypt",
        "isActive",
        "isCompromised",
        "isRetired",
        "markCompromised",
        "needsRotation",
        "retire"
      ],
      "methodSignatures": [
        {
          "name": "canDecrypt",
          "returns": "boolean"
        },
        {
          "name": "canEncrypt",
          "returns": "boolean"
        },
        {
          "name": "isActive",
          "returns": "boolean"
        },
        {
          "name": "isCompromised",
          "returns": "boolean"
        },
        {
          "name": "isRetired",
          "returns": "boolean"
        },
        {
          "name": "markCompromised",
          "returns": "void"
        },
        {
          "name": "needsRotation",
          "returns": "boolean"
        },
        {
          "name": "retire",
          "returns": "void"
        }
      ],
      "surfaces": [
        {
          "kind": "cli",
          "name": "tenantkey_list",
          "operation": "list",
          "objectName": "@happyvertical/smrt-secrets:TenantKey"
        },
        {
          "kind": "cli",
          "name": "tenantkey_get",
          "operation": "get",
          "objectName": "@happyvertical/smrt-secrets:TenantKey"
        }
      ],
      "relationshipFeatures": [
        "uuidColumns"
      ],
      "tags": [],
      "risks": []
    }
  ],
  "surfaces": [
    {
      "kind": "cli",
      "name": "secret_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-secrets:Secret"
    },
    {
      "kind": "cli",
      "name": "secretauditlog_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-secrets:SecretAuditLog"
    },
    {
      "kind": "cli",
      "name": "tenantkey_list",
      "operation": "list",
      "objectName": "@happyvertical/smrt-secrets:TenantKey"
    },
    {
      "kind": "cli",
      "name": "tenantkey_get",
      "operation": "get",
      "objectName": "@happyvertical/smrt-secrets:TenantKey"
    }
  ],
  "prompts": [],
  "relationshipsV2": {
    "foreignKeyFields": 1,
    "crossPackageRefFields": 1,
    "junctionCollections": 0,
    "hierarchicalObjects": 0,
    "polymorphicAssociations": 0,
    "uuidColumns": 10
  },
  "agentDoc": "# @happyvertical/smrt-secrets\n\nEnvelope encryption for per-tenant secret storage with key rotation and audit logging.\n\n## Encryption Architecture\n\n```\nSecret value → encrypted with TDEK (per-tenant Data Encryption Key)\n  TDEK → wrapped with AMK (Application Master Key, from env SMRT_SECRET_MASTER_KEY)\n```\n\n## Models\n\n- **Secret**: `encryptedValue` (JSON envelope), `category`, `status` (active/disabled/expired), `expiresAt`, `accessCount`, `lastAccessedAt`. **No API/MCP exposure** (security). CLI: list-only.\n- **TenantKey**: per-tenant TDEK in wrapped form. Status: active/rotating/retired/compromised. **Not tenant-scoped itself** — it tracks keys FOR tenants.\n- **SecretAuditLog**: action (create/read/update/delete/rotate_key/disable/enable), result (success/failure/denied), user/IP tracking. CLI: list-only.\n\n## SecretService\n\n| Method | Behavior |\n|--------|----------|\n| `store(name, value, options)` | Encrypt + save. Upsert if exists. Uses `context=tenantId` for per-tenant uniqueness. |\n| `retrieve(name)` | Decrypt + audit + increment accessCount |\n| `diagnoseTenantSecretKeyDrift(tenantId)` | Report active secret/key drift without exposing values. Checks `secrets`, SDK `tenant_encryption_keys`, and SMRT `tenant_keys`. |\n| `repairTenantSecretKeyDrift(tenantId, opts)` | Explicit repair path for unrecoverable drift. Use `dryRun` first; destructive cleanup requires `confirmDeleteUnrecoverableData: true`. |\n| `rotateKey()` | Create new TDEK, mark old as retired |\n| `reencryptAll()` | Decrypt with old TDEK, re-encrypt with new — **must call separately after rotateKey()** |\n| `disable(name)` / `enable(name)` | Toggle status |\n\n## Gotchas\n\n- **Key rotation doesn't auto-re-encrypt**: call `reencryptAll()` separately after `rotateKey()`\n- **retrieve() increments accessCount**: every read is tracked\n- **TenantKey NOT tenant-scoped**: it stores keys for tenants but isn't filtered by tenant context\n- **Two key tables**: `tenant_encryption_keys` is the lower-level SDK table used by `SecretService` encryption; `tenant_keys` is the SMRT model table. Drift diagnosis reports both so an empty `tenant_keys` view is not confused with missing SDK key material.\n- **Repair is destructive only by confirmation**: `repairTenantSecretKeyDrift()` deletes unrecoverable encrypted rows only when `confirmDeleteUnrecoverableData: true` is explicit. Do not silently discard encrypted secrets.\n- **Expired secrets filtered by default**: pass `includeExpired: true` to list them\n- **TenantKeyCollection.cleanupRetiredKeys()**: hard-deletes after 90 days\n- **Audit logging optional but default**: failures logged to console, not thrown\n\n## Known exceptions to monorepo standards\n\nPer `docs/content/standards.md §7`, tenant-aware models should normally apply\n`@TenantScoped({ mode: 'optional' })` from `@happyvertical/smrt-tenancy`. The three\nmodels in this package deviate intentionally; each `@smrt(...)` block carries an\ninline comment pointing back to this section.\n\n- **`Secret` (`src/models/Secret.ts`)** — uses the inline `tenantScoped: true` form\n  on `@smrt()` instead of the `@TenantScoped` decorator. `SecretService.store()`\n  performs manual scoping by populating `context = tenantId` on each row, so the\n  `(slug, context)` upsert key from the base `SmrtObject` is what isolates secret\n  names per tenant. Switching to the decorator without rethinking the upsert key\n  would surface false-positive name collisions across tenants.\n- **`TenantKey` (`src/models/TenantKey.ts`)** — deliberately NOT tenant-scoped at\n  all. The row carries a `tenantId` column because each TDEK belongs to a tenant,\n  but key-rotation tooling, AMK rewrap jobs, and super-admin audits must query\n  across tenants; the tenancy interceptor would silently filter rows those flows\n  rely on.\n- **`SecretAuditLog` (`src/models/SecretAuditLog.ts`)** — uses the inline\n  `tenantScoped: true` form rather than the decorator. Audit reads run in mixed\n  contexts (tenant-scoped reports vs. super-admin compliance review). Cross-\n  tenant audit queries should be wrapped in `withSuperAdminBypass()` from\n  `@happyvertical/smrt-tenancy` at the call site — there are no such cross-\n  tenant call sites in this package today, but consumers building compliance\n  tooling should adopt that pattern explicitly rather than relying on\n  decorator-implicit filtering.\n"
}