Adds deerflow/persistence/api_key/{__init__,model}.py with ApiKeyRow:
- id: UUID36 PK
- service_account_id: FK service_accounts ON DELETE CASCADE
- key_prefix: String(16), UNIQUE — global uniqueness preserved even
after revoke so audit logs never reference an ambiguous prefix
- key_hash: String(128) sha-256 hex (plaintext returned only once at
create time)
- name / scopes / rate_limit_rpm / expires_at / last_used_at /
revoked_at — all nullable or default-providing
- created_at: UTC
- Index idx_api_keys_sa (service_account_id) — list keys for an SA
- Index idx_api_keys_active (key_prefix) WHERE revoked_at IS NULL —
partial index, dual-dialect via sqlite_where + postgresql_where,
shrinks the hot-path lookup index by excluding revoked keys
T8.4 tests:
- test_unique_key_prefix_enforced: column-level UNIQUE blocks two
rows from sharing key_prefix (active or revoked alike)
- test_active_index_declares_both_dialect_where_clauses: schema
introspection confirms idx_api_keys_active has both
`dialect_options.sqlite.where` and `dialect_options.postgresql.where`
set to `revoked_at IS NULL` — guards against accidental loss of the
dual-driver hint when the Index is edited later
- test_cascade_on_service_account_delete: deleting the parent SA
removes all api_keys rows
Registered in deerflow/persistence/models/__init__.py.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>