diff auth/auth_store.c @ 264:04fee26ecce0

add authenticated JRPG conversation platform Add reusable auth/session storage, owned conversation recovery, guest quotas, admin workflows, URL-routed conversation UI, mobile frame support, and parallel browser acceptance. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 07:34:12 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/auth/auth_store.c	Fri Aug 07 07:34:12 2026 -0700
@@ -0,0 +1,2818 @@
+#include "auth/auth_store.h"
+
+#include "deita/deita.h"
+
+#include <openssl/crypto.h>
+
+#include <fcntl.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+struct Auth_Store {
+  Deita_Connection *p_connection;
+  pthread_mutex_t   mutex;
+};
+
+/* ------------------------------------------------------------------ */
+/* Migration SQL                                                        */
+/* ------------------------------------------------------------------ */
+
+static const char *k_migration_v1 =
+    "CREATE TABLE IF NOT EXISTS users ("
+    "  id TEXT PRIMARY KEY,"
+    "  username TEXT NOT NULL,"
+    "  normalized_username TEXT NOT NULL UNIQUE,"
+    "  password_hash TEXT NOT NULL,"
+    "  role TEXT NOT NULL CHECK(role IN ('admin','member')),"
+    "  status TEXT NOT NULL DEFAULT 'active'"
+    "    CHECK(status IN ('active','disabled')),"
+    "  must_change_password INTEGER NOT NULL DEFAULT 0,"
+    "  password_changed_at INTEGER NOT NULL DEFAULT 0,"
+    "  created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),"
+    "  updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))"
+    ");"
+    "CREATE TABLE IF NOT EXISTS auth_sessions ("
+    "  token_digest TEXT PRIMARY KEY,"
+    "  csrf_digest TEXT NOT NULL,"
+    "  user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,"
+    "  created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),"
+    "  last_seen_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),"
+    "  idle_expires_at INTEGER NOT NULL,"
+    "  absolute_expires_at INTEGER NOT NULL,"
+    "  password_changed_at_snapshot INTEGER NOT NULL DEFAULT 0,"
+    "  revoked_at INTEGER"
+    ");"
+    "CREATE TABLE IF NOT EXISTS guest_identities ("
+    "  id TEXT PRIMARY KEY,"
+    "  ip_binding_digest TEXT NOT NULL,"
+    "  created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),"
+    "  last_seen_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),"
+    "  expires_at INTEGER NOT NULL"
+    ");"
+    "CREATE TABLE IF NOT EXISTS guest_usage ("
+    "  guest_id TEXT NOT NULL"
+    "    REFERENCES guest_identities(id) ON DELETE CASCADE,"
+    "  window_start INTEGER NOT NULL,"
+    "  count INTEGER NOT NULL DEFAULT 0,"
+    "  PRIMARY KEY (guest_id, window_start)"
+    ");"
+    "CREATE TABLE IF NOT EXISTS guest_usage_reservations ("
+    "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
+    "  guest_id TEXT NOT NULL"
+    "    REFERENCES guest_identities(id) ON DELETE CASCADE,"
+    "  reserved_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),"
+    "  expires_at INTEGER NOT NULL"
+    ");"
+    "CREATE TABLE IF NOT EXISTS admin_audit_log ("
+    "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
+    "  actor_user_id TEXT,"
+    "  action TEXT NOT NULL,"
+    "  target_user_id TEXT,"
+    "  detail TEXT,"
+    "  created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))"
+    ");"
+    "CREATE INDEX IF NOT EXISTS idx_users_normalized"
+    "  ON users(normalized_username);"
+    "CREATE INDEX IF NOT EXISTS idx_sessions_user"
+    "  ON auth_sessions(user_id);"
+    "CREATE INDEX IF NOT EXISTS idx_sessions_expiry"
+    "  ON auth_sessions(absolute_expires_at) WHERE revoked_at IS NULL;"
+    "CREATE INDEX IF NOT EXISTS idx_guest_expiry"
+    "  ON guest_identities(expires_at);"
+    "CREATE INDEX IF NOT EXISTS idx_audit_created"
+    "  ON admin_audit_log(created_at DESC);";
+
+/* ------------------------------------------------------------------ */
+/* Internal helpers                                                     */
+/* ------------------------------------------------------------------ */
+
+static void auth__copy_text_fixed(char *dest, size_t size, const char *text)
+{
+  const char *src = text ? text : "";
+  size_t      n   = strlen(src);
+  if (n >= size)
+    n = size - 1;
+  memcpy(dest, src, n);
+  dest[n] = '\0';
+}
+
+static boolean auth__generate_uuid(char output[37])
+{
+  uint8   bytes[16];
+  int     fd     = open("/dev/urandom", O_RDONLY);
+  size_t  offset = 0;
+  ssize_t amount;
+
+  if (fd < 0)
+    return FALSE;
+  while (offset < sizeof(bytes))
+  {
+    amount = read(fd, bytes + offset, sizeof(bytes) - offset);
+    if (amount <= 0)
+    {
+      close(fd);
+      return FALSE;
+    }
+    offset += (size_t)amount;
+  }
+  close(fd);
+
+  bytes[6] = (uint8)((bytes[6] & 0x0f) | 0x40);
+  bytes[8] = (uint8)((bytes[8] & 0x3f) | 0x80);
+  snprintf(
+      output, 37,
+      "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+      bytes[0],  bytes[1],  bytes[2],  bytes[3],
+      bytes[4],  bytes[5],  bytes[6],  bytes[7],
+      bytes[8],  bytes[9],  bytes[10], bytes[11],
+      bytes[12], bytes[13], bytes[14], bytes[15]);
+  return TRUE;
+}
+
+static Auth_Store_Result auth__rollback(Auth_Store *p_store,
+                                        Auth_Store_Result result)
+{
+  Deita_Query_Execute_Update(p_store->p_connection, "ROLLBACK");
+  return result;
+}
+
+/* Forward declaration — implementation is in the guest quota section. */
+static void auth__i64_str(char *buf, size_t size, int64 value);
+
+static boolean auth__insert_audit_log_locked(
+    Auth_Store *p_store,
+    const char *actor_user_id,
+    const char *action,
+    const char *target_user_id,
+    const char *detail)
+{
+  /* actor_user_id and detail may be NULL — represented as empty string */
+  const char *actor  = actor_user_id  ? actor_user_id  : "";
+  const char *tgt    = target_user_id ? target_user_id : "";
+  const char *det    = detail         ? detail         : "";
+  const char *params[] = {actor, action, tgt, det};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "INSERT INTO admin_audit_log"
+          "  (actor_user_id, action, target_user_id, detail)"
+          "  VALUES (?, ?, ?, ?)",
+          4, params) < 0)
+    return FALSE;
+  /* Trim to the most recent 10 000 entries. */
+  Deita_Query_Execute_Update(
+      p_store->p_connection,
+      "DELETE FROM admin_audit_log"
+      "  WHERE id <= (SELECT MAX(id) FROM admin_audit_log) - 10000");
+  return TRUE;
+}
+
+static boolean auth__digest_is_valid(const char *digest)
+{
+  if (!digest)
+    return FALSE;
+  for (size_t i = 0; i < AUTH_CRYPTO_TOKEN_DIGEST_SIZE - 1; i++)
+  {
+    uint8 c = (uint8)digest[i];
+    if (c == '\0' ||
+        !((c >= '0' && c <= '9') ||
+          (c >= 'a' && c <= 'f') ||
+          (c >= 'A' && c <= 'F')))
+      return FALSE;
+  }
+  return digest[AUTH_CRYPTO_TOKEN_DIGEST_SIZE - 1] == '\0';
+}
+
+static boolean auth__session_arguments_are_valid(
+    const char *token_digest,
+    const char *csrf_digest,
+    int64       idle_ttl_secs,
+    int64       absolute_ttl_secs,
+    int64       current_unix)
+{
+  if (!auth__digest_is_valid(token_digest) ||
+      !auth__digest_is_valid(csrf_digest) ||
+      current_unix < 0 || idle_ttl_secs <= 0 || absolute_ttl_secs <= 0 ||
+      idle_ttl_secs > absolute_ttl_secs)
+    return FALSE;
+  if (current_unix > (int64)LLONG_MAX - idle_ttl_secs ||
+      current_unix > (int64)LLONG_MAX - absolute_ttl_secs)
+    return FALSE;
+  return TRUE;
+}
+
+static void auth__fill_session_record(
+    Auth_Session_Record *p_record,
+    const char          *user_id,
+    int64                idle_ttl_secs,
+    int64                absolute_ttl_secs,
+    int64                current_unix,
+    int64                password_changed_at)
+{
+  memset(p_record, 0, sizeof(*p_record));
+  auth__copy_text_fixed(p_record->user_id, sizeof(p_record->user_id), user_id);
+  p_record->created_at = current_unix;
+  p_record->last_seen_at = current_unix;
+  p_record->idle_expires_at = current_unix + idle_ttl_secs;
+  p_record->absolute_expires_at = current_unix + absolute_ttl_secs;
+  p_record->password_changed_at_snapshot = password_changed_at;
+}
+
+static Auth_Store_Result auth__insert_session_locked(
+    Auth_Store          *p_store,
+    const char          *user_id,
+    const char          *token_digest,
+    const char          *csrf_digest,
+    int64                idle_ttl_secs,
+    int64                absolute_ttl_secs,
+    int64                current_unix,
+    int64                password_changed_at)
+{
+  char created_str[32], idle_exp_str[32], abs_exp_str[32], snap_str[32];
+  snprintf(created_str, sizeof(created_str), "%lld", (long long)current_unix);
+  snprintf(idle_exp_str, sizeof(idle_exp_str), "%lld",
+           (long long)(current_unix + idle_ttl_secs));
+  snprintf(abs_exp_str, sizeof(abs_exp_str), "%lld",
+           (long long)(current_unix + absolute_ttl_secs));
+  snprintf(snap_str, sizeof(snap_str), "%lld",
+           (long long)password_changed_at);
+
+  const char *params[] = {
+    token_digest, csrf_digest, user_id,
+    created_str, created_str, idle_exp_str, abs_exp_str, snap_str
+  };
+  int32 result = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "INSERT INTO auth_sessions"
+      "  (token_digest, csrf_digest, user_id,"
+      "   created_at, last_seen_at, idle_expires_at,"
+      "   absolute_expires_at, password_changed_at_snapshot)"
+      "  VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+      8, params);
+  return result < 0 ? AUTH_STORE_CONFLICT : AUTH_STORE_OK;
+}
+
+/* ------------------------------------------------------------------ */
+/* Migration system                                                     */
+/* ------------------------------------------------------------------ */
+
+static boolean auth__apply_migration(Auth_Store *p_store,
+                                     int32       version,
+                                     const char *sql)
+{
+  char version_str[32];
+  snprintf(version_str, sizeof(version_str), "%d", (int)version);
+
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN EXCLUSIVE") < 0)
+    return FALSE;
+
+  /* Check if already applied. */
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(1024);
+  if (!p_arena)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    return FALSE;
+  }
+  const char       *check_params[] = {version_str};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT version FROM auth_schema_migrations WHERE version = ?",
+      1, check_params, p_arena);
+  boolean already = p_result && Deita_Result_Set_Next(p_result);
+  if (p_result)
+    Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+
+  if (already)
+  {
+    if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+    {
+      auth__rollback(p_store, AUTH_STORE_ERROR);
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /* Apply the migration. */
+  if (Deita_Query_Execute_Update(p_store->p_connection, sql) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    return FALSE;
+  }
+
+  const char *ins_params[] = {version_str};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "INSERT INTO auth_schema_migrations (version) VALUES (?)",
+          1, ins_params) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    return FALSE;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/*
+ * Migration v2: extend guest quota tables.
+ * - Backfills turns_used from the legacy count column before any new schema
+ *   is applied, so existing usage is preserved (req 1).
+ * - Adds turns_used, output_tokens_used, output_tokens_reserved to guest_usage.
+ * - Replaces guest_usage_reservations with a richer schema keyed by request_id.
+ * The old reservations table is ephemeral (in-flight requests only), so
+ * dropping and recreating it is safe; legacy reservations had no token-amount
+ * column so output_tokens_reserved stays 0 after the replace.
+ */
+static const char *k_migration_v2 =
+    "ALTER TABLE guest_usage"
+    "  ADD COLUMN turns_used INTEGER NOT NULL DEFAULT 0;"
+    "ALTER TABLE guest_usage"
+    "  ADD COLUMN output_tokens_used INTEGER NOT NULL DEFAULT 0;"
+    "ALTER TABLE guest_usage"
+    "  ADD COLUMN output_tokens_reserved INTEGER NOT NULL DEFAULT 0;"
+    /* Backfill turns_used from the legacy request-count column. */
+    "UPDATE guest_usage SET turns_used = count WHERE count > 0;"
+    "DROP TABLE IF EXISTS guest_usage_reservations;"
+    "CREATE TABLE IF NOT EXISTS guest_usage_reservations ("
+    "  request_id TEXT PRIMARY KEY,"
+    "  guest_id TEXT NOT NULL"
+    "    REFERENCES guest_identities(id) ON DELETE CASCADE,"
+    "  window_start INTEGER NOT NULL,"
+    "  output_tokens_reserved INTEGER NOT NULL DEFAULT 0,"
+    "  reserved_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),"
+    "  expires_at INTEGER NOT NULL"
+    ");"
+    "CREATE INDEX IF NOT EXISTS idx_reservations_guest"
+    "  ON guest_usage_reservations(guest_id);"
+    "CREATE INDEX IF NOT EXISTS idx_reservations_expiry"
+    "  ON guest_usage_reservations(expires_at);"
+    "CREATE INDEX IF NOT EXISTS idx_guest_usage_guest"
+    "  ON guest_usage(guest_id);";
+
+static boolean auth__run_migrations(Auth_Store *p_store)
+{
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection,
+          "CREATE TABLE IF NOT EXISTS auth_schema_migrations ("
+          "  version INTEGER PRIMARY KEY,"
+          "  applied_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))"
+          ")") < 0)
+    return FALSE;
+
+  if (!auth__apply_migration(p_store, 1, k_migration_v1))
+    return FALSE;
+  return auth__apply_migration(p_store, 2, k_migration_v2);
+}
+
+/* ------------------------------------------------------------------ */
+/* Store lifecycle                                                      */
+/* ------------------------------------------------------------------ */
+
+/*
+ * Reap expired reservations with the mutex already held.
+ * Run atomically in a nested SAVEPOINT to avoid interfering with any outer
+ * transaction (callers sometimes hold BEGIN IMMEDIATE).
+ */
+static void auth__reap_expired_reservations_locked(
+    Auth_Store *p_store,
+    int64       current_unix)
+{
+  char now_str[32];
+  auth__i64_str(now_str, sizeof(now_str), current_unix);
+
+  /* Use a savepoint so we can run inside or outside a transaction. */
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection,
+          "SAVEPOINT reap_expired") < 0)
+    return;
+
+  const char *upd_params[] = {now_str, now_str};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "UPDATE guest_usage"
+          "  SET output_tokens_reserved = MAX(0, output_tokens_reserved - ("
+          "    SELECT COALESCE(SUM(r.output_tokens_reserved), 0)"
+          "    FROM guest_usage_reservations r"
+          "    WHERE r.guest_id = guest_usage.guest_id"
+          "      AND r.window_start = guest_usage.window_start"
+          "      AND r.expires_at < ?"
+          "  ))"
+          "  WHERE output_tokens_reserved > 0"
+          "    AND EXISTS ("
+          "      SELECT 1 FROM guest_usage_reservations r2"
+          "      WHERE r2.guest_id = guest_usage.guest_id"
+          "        AND r2.expires_at < ?)",
+          2, upd_params) < 0)
+  {
+    Deita_Query_Execute_Update(p_store->p_connection,
+                               "ROLLBACK TO reap_expired");
+    Deita_Query_Execute_Update(p_store->p_connection, "RELEASE reap_expired");
+    return;
+  }
+
+  const char *del_params[] = {now_str};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "DELETE FROM guest_usage_reservations WHERE expires_at < ?",
+          1, del_params) < 0)
+  {
+    Deita_Query_Execute_Update(p_store->p_connection,
+                               "ROLLBACK TO reap_expired");
+    Deita_Query_Execute_Update(p_store->p_connection, "RELEASE reap_expired");
+    return;
+  }
+
+  Deita_Query_Execute_Update(p_store->p_connection, "RELEASE reap_expired");
+}
+
+Auth_Store *Auth_Store_Create(const char *database_path)
+{
+  if (!database_path)
+    return NULL;
+
+  Auth_Store *p_store = calloc(1, sizeof(*p_store));
+  if (!p_store)
+    return NULL;
+
+  p_store->p_connection = Deita_Connection_Create(
+      DEITA_DATABASE_TYPE_SQLITE3, database_path);
+  if (!p_store->p_connection ||
+      !Deita_Connection_Is_Open(p_store->p_connection))
+  {
+    if (p_store->p_connection)
+      Deita_Connection_Close(p_store->p_connection);
+    free(p_store);
+    return NULL;
+  }
+
+  if (pthread_mutex_init(&p_store->mutex, NULL) != 0)
+  {
+    Deita_Connection_Close(p_store->p_connection);
+    free(p_store);
+    return NULL;
+  }
+
+  /* Connection-level settings — must be re-applied on every open. */
+  if (Deita_Query_Execute_Update(p_store->p_connection,
+          "PRAGMA foreign_keys = ON;"
+          "PRAGMA journal_mode = WAL;") < 0)
+  {
+    Auth_Store_Destroy(p_store);
+    return NULL;
+  }
+
+  if (!auth__run_migrations(p_store))
+  {
+    Auth_Store_Destroy(p_store);
+    return NULL;
+  }
+
+  /* Reap any expired reservations left over from a previous run. */
+  pthread_mutex_lock(&p_store->mutex);
+  auth__reap_expired_reservations_locked(p_store, (int64)time(NULL));
+  pthread_mutex_unlock(&p_store->mutex);
+
+  return p_store;
+}
+
+void Auth_Store_Destroy(Auth_Store *p_store)
+{
+  if (!p_store)
+    return;
+  if (p_store->p_connection)
+    Deita_Connection_Close(p_store->p_connection);
+  pthread_mutex_destroy(&p_store->mutex);
+  free(p_store);
+}
+
+/* ------------------------------------------------------------------ */
+/* Username utilities                                                   */
+/* ------------------------------------------------------------------ */
+
+boolean Auth_Store_Normalize_Username(
+    const char *username,
+    char       *normalized,
+    size_t      capacity)
+{
+  if (!username || !normalized || capacity == 0)
+    return FALSE;
+
+  size_t len   = strlen(username);
+  size_t start = 0;
+  size_t end   = len;
+
+  while (start < len && username[start] == ' ')
+    start++;
+  while (end > start && username[end - 1] == ' ')
+    end--;
+
+  size_t norm_len = end - start;
+  if (norm_len < AUTH_STORE_USERNAME_MIN ||
+      norm_len > AUTH_STORE_USERNAME_MAX)
+    return FALSE;
+  if (capacity <= norm_len)
+    return FALSE;
+
+  for (size_t i = 0; i < norm_len; i++)
+  {
+    uint8 c = (uint8)username[start + i];
+    if (c >= 'A' && c <= 'Z')
+      c = (uint8)(c - 'A' + 'a');
+    else if ((c >= 'a' && c <= 'z') ||
+             (c >= '0' && c <= '9') ||
+             c == '_' || c == '-' || c == '.')
+      ; /* valid as-is */
+    else
+      return FALSE;
+    normalized[i] = (char)c;
+  }
+  normalized[norm_len] = '\0';
+  return TRUE;
+}
+
+boolean Auth_Store_Validate_Username(const char *normalized_username)
+{
+  if (!normalized_username)
+    return FALSE;
+  size_t i = 0;
+  while (normalized_username[i] != '\0')
+  {
+    if (i >= AUTH_STORE_USERNAME_MAX)
+      return FALSE;
+    uint8 c = (uint8)normalized_username[i];
+    if (!((c >= 'a' && c <= 'z') ||
+          (c >= '0' && c <= '9') ||
+          c == '_' || c == '-' || c == '.'))
+      return FALSE;
+    i++;
+  }
+  return i >= AUTH_STORE_USERNAME_MIN;
+}
+
+/* ------------------------------------------------------------------ */
+/* Internal: populate Auth_User_Record from an open result set         */
+/* ------------------------------------------------------------------ */
+
+/*
+ * Columns expected (0-based):
+ *   0 id, 1 username, 2 normalized_username, 3 role, 4 status,
+ *   5 must_change_password, 6 password_changed_at, 7 created_at, 8 updated_at
+ */
+static void auth__read_user_record(Auth_User_Record *r,
+                                   Deita_Result_Set *p)
+{
+  auth__copy_text_fixed(r->id,                 sizeof(r->id),
+                        Deita_Result_Set_Get_Text(p, 0));
+  auth__copy_text_fixed(r->username,           sizeof(r->username),
+                        Deita_Result_Set_Get_Text(p, 1));
+  auth__copy_text_fixed(r->normalized_username,sizeof(r->normalized_username),
+                        Deita_Result_Set_Get_Text(p, 2));
+  auth__copy_text_fixed(r->role,               sizeof(r->role),
+                        Deita_Result_Set_Get_Text(p, 3));
+  auth__copy_text_fixed(r->status,             sizeof(r->status),
+                        Deita_Result_Set_Get_Text(p, 4));
+  r->must_change_password = Deita_Result_Set_Get_Integer(p, 5) ? TRUE : FALSE;
+  r->password_changed_at  = Deita_Result_Set_Get_Integer(p, 6);
+  r->created_at           = Deita_Result_Set_Get_Integer(p, 7);
+  r->updated_at           = Deita_Result_Set_Get_Integer(p, 8);
+}
+
+/* ------------------------------------------------------------------ */
+/* User management                                                      */
+/* ------------------------------------------------------------------ */
+
+Auth_Store_Result Auth_Store_Create_User(
+    Auth_Store *p_store,
+    const char *username,
+    const char *encoded_hash,
+    const char *role,
+    boolean     must_change_password,
+    char        output_id[37])
+{
+  if (!p_store || !username || !encoded_hash || !role || !output_id)
+    return AUTH_STORE_INVALID_ARG;
+  if (encoded_hash[0] == '\0')
+    return AUTH_STORE_INVALID_ARG;
+  if (strcmp(role, "admin") != 0 && strcmp(role, "member") != 0)
+    return AUTH_STORE_INVALID_ARG;
+
+  char normalized[AUTH_STORE_USERNAME_MAX + 1];
+  if (!Auth_Store_Normalize_Username(
+          username, normalized, sizeof(normalized)))
+    return AUTH_STORE_INVALID_ARG;
+
+  if (!auth__generate_uuid(output_id))
+    return AUTH_STORE_ERROR;
+
+  const char *mcp_str = must_change_password ? "1" : "0";
+  const char *params[] = {
+    output_id, username, normalized, encoded_hash, role, mcp_str
+  };
+
+  pthread_mutex_lock(&p_store->mutex);
+  int32 result = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "INSERT INTO users"
+      "  (id, username, normalized_username, password_hash, role,"
+      "   must_change_password)"
+      "  VALUES (?, ?, ?, ?, ?, ?)",
+      6, params);
+  pthread_mutex_unlock(&p_store->mutex);
+
+  if (result < 0)
+    return AUTH_STORE_CONFLICT; /* most likely UNIQUE constraint on norm_name */
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Create_User_Audited(
+    Auth_Store *p_store,
+    const char *username,
+    const char *encoded_hash,
+    const char *role,
+    boolean     must_change_password,
+    const char *actor_user_id,
+    char        output_id[37])
+{
+  if (!p_store || !username || !encoded_hash || !role || !output_id)
+    return AUTH_STORE_INVALID_ARG;
+  if (encoded_hash[0] == '\0')
+    return AUTH_STORE_INVALID_ARG;
+  if (strcmp(role, "admin") != 0 && strcmp(role, "member") != 0)
+    return AUTH_STORE_INVALID_ARG;
+
+  char normalized[AUTH_STORE_USERNAME_MAX + 1];
+  if (!Auth_Store_Normalize_Username(
+          username, normalized, sizeof(normalized)))
+    return AUTH_STORE_INVALID_ARG;
+  if (!auth__generate_uuid(output_id))
+    return AUTH_STORE_ERROR;
+
+  const char *mcp_str = must_change_password ? "1" : "0";
+  const char *params[] = {
+    output_id, username, normalized, encoded_hash, role, mcp_str
+  };
+
+  pthread_mutex_lock(&p_store->mutex);
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  int32 result = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "INSERT INTO users"
+      "  (id, username, normalized_username, password_hash, role,"
+      "   must_change_password)"
+      "  VALUES (?, ?, ?, ?, ?, ?)",
+      6, params);
+  if (result < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_CONFLICT;
+  }
+
+  if (!auth__insert_audit_log_locked(
+          p_store, actor_user_id, "admin_user_created", output_id, role))
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Bootstrap_Admin(
+    Auth_Store               *p_store,
+    const char               *username,
+    const char               *encoded_hash,
+    Auth_Store_Bootstrap_Result *p_bootstrap_result,
+    char                      output_id[37])
+{
+  if (!p_store || !username || !encoded_hash || !p_bootstrap_result)
+    return AUTH_STORE_INVALID_ARG;
+  if (encoded_hash[0] == '\0')
+    return AUTH_STORE_INVALID_ARG;
+
+  char normalized[AUTH_STORE_USERNAME_MAX + 1];
+  if (!Auth_Store_Normalize_Username(
+          username, normalized, sizeof(normalized)))
+    return AUTH_STORE_INVALID_ARG;
+
+  char id_buf[37];
+  if (!auth__generate_uuid(id_buf))
+    return AUTH_STORE_ERROR;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* Check for any existing admin (active or disabled). */
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  Deita_Result_Set *p_result = Deita_Query_Execute(
+      p_store->p_connection,
+      "SELECT id FROM users WHERE role = 'admin' LIMIT 1",
+      p_arena);
+  boolean admin_exists = p_result && Deita_Result_Set_Next(p_result);
+  char existing_id[37] = {0};
+  if (admin_exists && p_result)
+    auth__copy_text_fixed(existing_id, sizeof(existing_id),
+                          Deita_Result_Set_Get_Text(p_result, 0));
+  if (p_result)
+    Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+
+  if (admin_exists)
+  {
+    if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+    {
+      auth__rollback(p_store, AUTH_STORE_ERROR);
+      pthread_mutex_unlock(&p_store->mutex);
+      return AUTH_STORE_ERROR;
+    }
+    pthread_mutex_unlock(&p_store->mutex);
+    *p_bootstrap_result = AUTH_STORE_BOOTSTRAP_ALREADY_PRESENT;
+    if (output_id)
+      auth__copy_text_fixed(output_id, 37, existing_id);
+    return AUTH_STORE_OK;
+  }
+
+  const char *params[] = {
+    id_buf, username, normalized, encoded_hash, "admin"
+  };
+  int32 ins = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "INSERT INTO users"
+      "  (id, username, normalized_username, password_hash, role)"
+      "  VALUES (?, ?, ?, ?, ?)",
+      5, params);
+  if (ins < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (!auth__insert_audit_log_locked(
+          p_store, NULL, "bootstrap_admin_created", id_buf, NULL))
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  *p_bootstrap_result = AUTH_STORE_BOOTSTRAP_CREATED;
+  if (output_id)
+    auth__copy_text_fixed(output_id, 37, id_buf);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Find_User_By_Username(
+    Auth_Store            *p_store,
+    const char            *username,
+    Auth_User_Auth_Record *p_record)
+{
+  if (!p_store || !username || !p_record)
+    return AUTH_STORE_INVALID_ARG;
+
+  char normalized[AUTH_STORE_USERNAME_MAX + 1];
+  if (!Auth_Store_Normalize_Username(
+          username, normalized, sizeof(normalized)))
+    return AUTH_STORE_NOT_FOUND;
+
+  memset(p_record, 0, sizeof(*p_record));
+
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+    return AUTH_STORE_ERROR;
+
+  const char       *params[] = {normalized};
+  pthread_mutex_lock(&p_store->mutex);
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT id, username, normalized_username, role, status,"
+      "       must_change_password, password_changed_at,"
+      "       created_at, updated_at, password_hash"
+      "  FROM users WHERE normalized_username = ?",
+      1, params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_NOT_FOUND;
+  }
+  auth__read_user_record(&p_record->user, p_result);
+  auth__copy_text_fixed(p_record->password_hash,
+                        sizeof(p_record->password_hash),
+                        Deita_Result_Set_Get_Text(p_result, 9));
+  Deita_Result_Set_Free(p_result);
+  pthread_mutex_unlock(&p_store->mutex);
+  Dowa_Arena_Free(p_arena);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Get_User(
+    Auth_Store       *p_store,
+    const char       *user_id,
+    Auth_User_Record *p_record)
+{
+  if (!p_store || !user_id || !p_record)
+    return AUTH_STORE_INVALID_ARG;
+
+  memset(p_record, 0, sizeof(*p_record));
+
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+    return AUTH_STORE_ERROR;
+
+  const char       *params[] = {user_id};
+  pthread_mutex_lock(&p_store->mutex);
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT id, username, normalized_username, role, status,"
+      "       must_change_password, password_changed_at,"
+      "       created_at, updated_at"
+      "  FROM users WHERE id = ?",
+      1, params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_NOT_FOUND;
+  }
+  auth__read_user_record(p_record, p_result);
+  Deita_Result_Set_Free(p_result);
+  pthread_mutex_unlock(&p_store->mutex);
+  Dowa_Arena_Free(p_arena);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_List_Users(
+    Auth_Store        *p_store,
+    Auth_User_Record **pp_records,
+    Dowa_Arena        *p_arena)
+{
+  if (!p_store || !pp_records || !p_arena)
+    return AUTH_STORE_INVALID_ARG;
+
+  *pp_records = NULL;
+
+  Dowa_Arena *p_local = Dowa_Arena_Create(2048);
+  if (!p_local)
+    return AUTH_STORE_ERROR;
+
+  pthread_mutex_lock(&p_store->mutex);
+  Deita_Result_Set *p_result = Deita_Query_Execute(
+      p_store->p_connection,
+      "SELECT id, username, normalized_username, role, status,"
+      "       must_change_password, password_changed_at,"
+      "       created_at, updated_at"
+      "  FROM users ORDER BY created_at",
+      p_local);
+  if (!p_result)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_local);
+    return AUTH_STORE_ERROR;
+  }
+
+  Auth_User_Record *records = NULL;
+  while (Deita_Result_Set_Next(p_result))
+  {
+    Auth_User_Record record;
+    memset(&record, 0, sizeof(record));
+    auth__read_user_record(&record, p_result);
+    Dowa_Array_Push_Arena(records, record, p_arena);
+  }
+  boolean err = Deita_Result_Set_Has_Error(p_result);
+  Deita_Result_Set_Free(p_result);
+  pthread_mutex_unlock(&p_store->mutex);
+  Dowa_Arena_Free(p_local);
+
+  if (err)
+    return AUTH_STORE_ERROR;
+
+  *pp_records = records;
+  return AUTH_STORE_OK;
+}
+
+static Auth_Store_Result auth__update_user_status(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *new_status,
+    const char *actor_user_id,
+    boolean     revoke_sessions)
+{
+  if (!p_store || !user_id || !new_status)
+    return AUTH_STORE_INVALID_ARG;
+  if (strcmp(new_status, "active") != 0 &&
+      strcmp(new_status, "disabled") != 0)
+    return AUTH_STORE_INVALID_ARG;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* Fetch current role and status. */
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+  const char       *sel_params[] = {user_id};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT role, status FROM users WHERE id = ?",
+      1, sel_params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_arena);
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_NOT_FOUND;
+  }
+  char cur_role[8], cur_status[9];
+  auth__copy_text_fixed(cur_role,   sizeof(cur_role),
+                        Deita_Result_Set_Get_Text(p_result, 0));
+  auth__copy_text_fixed(cur_status, sizeof(cur_status),
+                        Deita_Result_Set_Get_Text(p_result, 1));
+  Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+
+  /* Last-admin protection: prevent disabling the final active admin. */
+  if (strcmp(new_status, "disabled") == 0 &&
+      strcmp(cur_role, "admin")      == 0 &&
+      strcmp(cur_status, "active")   == 0)
+  {
+    Dowa_Arena *p_count_arena = Dowa_Arena_Create(1024);
+    if (!p_count_arena)
+    {
+      auth__rollback(p_store, AUTH_STORE_ERROR);
+      pthread_mutex_unlock(&p_store->mutex);
+      return AUTH_STORE_ERROR;
+    }
+    p_result = Deita_Query_Execute(
+        p_store->p_connection,
+        "SELECT COUNT(*) FROM users WHERE role = 'admin' AND status = 'active'",
+        p_count_arena);
+    int64 count = 0;
+    if (p_result && Deita_Result_Set_Next(p_result))
+      count = Deita_Result_Set_Get_Integer(p_result, 0);
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_count_arena);
+
+    if (count <= 1)
+    {
+      auth__rollback(p_store, AUTH_STORE_ERROR);
+      pthread_mutex_unlock(&p_store->mutex);
+      return AUTH_STORE_LAST_ADMIN;
+    }
+  }
+
+  const char *upd_params[] = {new_status, user_id};
+  int32 upd = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "UPDATE users SET status = ?, updated_at = strftime('%s','now')"
+      "  WHERE id = ?",
+      2, upd_params);
+  if (upd <= 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return upd < 0 ? AUTH_STORE_ERROR : AUTH_STORE_NOT_FOUND;
+  }
+
+  if (revoke_sessions)
+  {
+    const char *rev_params[] = {user_id};
+    if (Deita_Query_Execute_Update_Prepared(
+            p_store->p_connection,
+            "UPDATE auth_sessions SET revoked_at = strftime('%s','now')"
+            "  WHERE user_id = ? AND revoked_at IS NULL",
+            1, rev_params) < 0)
+    {
+      auth__rollback(p_store, AUTH_STORE_ERROR);
+      pthread_mutex_unlock(&p_store->mutex);
+      return AUTH_STORE_ERROR;
+    }
+  }
+
+  char detail[64];
+  snprintf(detail, sizeof(detail), "status->%s", new_status);
+  if (!auth__insert_audit_log_locked(
+          p_store, actor_user_id, "user_status_updated", user_id, detail))
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Update_User_Status(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *new_status,
+    const char *actor_user_id)
+{
+  return auth__update_user_status(
+      p_store, user_id, new_status, actor_user_id, FALSE);
+}
+
+Auth_Store_Result Auth_Store_Enable_User(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *actor_user_id)
+{
+  return auth__update_user_status(
+      p_store, user_id, "active", actor_user_id, FALSE);
+}
+
+Auth_Store_Result Auth_Store_Disable_User_And_Revoke_Sessions(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *actor_user_id)
+{
+  return auth__update_user_status(
+      p_store, user_id, "disabled", actor_user_id, TRUE);
+}
+
+static Auth_Store_Result auth__update_user_role(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *new_role,
+    const char *actor_user_id,
+    boolean     revoke_sessions)
+{
+  if (!p_store || !user_id || !new_role)
+    return AUTH_STORE_INVALID_ARG;
+  if (strcmp(new_role, "admin") != 0 && strcmp(new_role, "member") != 0)
+    return AUTH_STORE_INVALID_ARG;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+  const char       *sel_params[] = {user_id};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT role, status FROM users WHERE id = ?",
+      1, sel_params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_arena);
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_NOT_FOUND;
+  }
+  char cur_role[8], cur_status[9];
+  auth__copy_text_fixed(cur_role,   sizeof(cur_role),
+                        Deita_Result_Set_Get_Text(p_result, 0));
+  auth__copy_text_fixed(cur_status, sizeof(cur_status),
+                        Deita_Result_Set_Get_Text(p_result, 1));
+  Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+
+  /* Last-admin protection: prevent demoting the final active admin. */
+  if (strcmp(new_role,    "member") == 0 &&
+      strcmp(cur_role,    "admin")  == 0 &&
+      strcmp(cur_status,  "active") == 0)
+  {
+    Dowa_Arena *p_count_arena = Dowa_Arena_Create(1024);
+    if (!p_count_arena)
+    {
+      auth__rollback(p_store, AUTH_STORE_ERROR);
+      pthread_mutex_unlock(&p_store->mutex);
+      return AUTH_STORE_ERROR;
+    }
+    p_result = Deita_Query_Execute(
+        p_store->p_connection,
+        "SELECT COUNT(*) FROM users WHERE role = 'admin' AND status = 'active'",
+        p_count_arena);
+    int64 count = 0;
+    if (p_result && Deita_Result_Set_Next(p_result))
+      count = Deita_Result_Set_Get_Integer(p_result, 0);
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_count_arena);
+
+    if (count <= 1)
+    {
+      auth__rollback(p_store, AUTH_STORE_ERROR);
+      pthread_mutex_unlock(&p_store->mutex);
+      return AUTH_STORE_LAST_ADMIN;
+    }
+  }
+
+  const char *upd_params[] = {new_role, user_id};
+  int32 upd = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "UPDATE users SET role = ?, updated_at = strftime('%s','now')"
+      "  WHERE id = ?",
+      2, upd_params);
+  if (upd <= 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return upd < 0 ? AUTH_STORE_ERROR : AUTH_STORE_NOT_FOUND;
+  }
+
+  if (revoke_sessions)
+  {
+    const char *rev_params[] = {user_id};
+    if (Deita_Query_Execute_Update_Prepared(
+            p_store->p_connection,
+            "UPDATE auth_sessions SET revoked_at = strftime('%s','now')"
+            "  WHERE user_id = ? AND revoked_at IS NULL",
+            1, rev_params) < 0)
+    {
+      auth__rollback(p_store, AUTH_STORE_ERROR);
+      pthread_mutex_unlock(&p_store->mutex);
+      return AUTH_STORE_ERROR;
+    }
+  }
+
+  char detail[64];
+  snprintf(detail, sizeof(detail), "role->%s", new_role);
+  if (!auth__insert_audit_log_locked(
+          p_store, actor_user_id, "user_role_updated", user_id, detail))
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Update_User_Role(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *new_role,
+    const char *actor_user_id)
+{
+  return auth__update_user_role(
+      p_store, user_id, new_role, actor_user_id, FALSE);
+}
+
+Auth_Store_Result Auth_Store_Update_Role_And_Revoke_Sessions(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *new_role,
+    const char *actor_user_id)
+{
+  return auth__update_user_role(
+      p_store, user_id, new_role, actor_user_id, TRUE);
+}
+
+Auth_Store_Result Auth_Store_Set_Must_Change_Password(
+    Auth_Store *p_store,
+    const char *user_id,
+    boolean     value,
+    const char *actor_user_id)
+{
+  if (!p_store || !user_id)
+    return AUTH_STORE_INVALID_ARG;
+
+  const char *val_str    = value ? "1" : "0";
+  const char *params[]   = {val_str, user_id};
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  int32 upd = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "UPDATE users"
+      "  SET must_change_password = ?, updated_at = strftime('%s','now')"
+      "  WHERE id = ?",
+      2, params);
+  if (upd <= 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return upd < 0 ? AUTH_STORE_ERROR : AUTH_STORE_NOT_FOUND;
+  }
+
+  if (!auth__insert_audit_log_locked(
+          p_store, actor_user_id,
+          value ? "must_change_password_set" : "must_change_password_cleared",
+          user_id, NULL))
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Update_Password(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *new_encoded_hash,
+    boolean     revoke_other_sessions,
+    const char *keep_token_digest)
+{
+  if (!p_store || !user_id || !new_encoded_hash)
+    return AUTH_STORE_INVALID_ARG;
+  if (new_encoded_hash[0] == '\0')
+    return AUTH_STORE_INVALID_ARG;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  const char *upd_params[] = {new_encoded_hash, user_id};
+  int32 upd = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "UPDATE users"
+      "  SET password_hash = ?,"
+      "      password_changed_at = strftime('%s','now'),"
+      "      must_change_password = 0,"
+      "      updated_at = strftime('%s','now')"
+      "  WHERE id = ?",
+      2, upd_params);
+  if (upd <= 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return upd < 0 ? AUTH_STORE_ERROR : AUTH_STORE_NOT_FOUND;
+  }
+
+  if (revoke_other_sessions)
+  {
+    int32 rev;
+    if (keep_token_digest && keep_token_digest[0] != '\0')
+    {
+      const char *rev_params[] = {user_id, keep_token_digest};
+      rev = Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "UPDATE auth_sessions"
+          "  SET revoked_at = strftime('%s','now')"
+          "  WHERE user_id = ? AND token_digest != ? AND revoked_at IS NULL",
+          2, rev_params);
+    }
+    else
+    {
+      const char *rev_params[] = {user_id};
+      rev = Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "UPDATE auth_sessions"
+          "  SET revoked_at = strftime('%s','now')"
+          "  WHERE user_id = ? AND revoked_at IS NULL",
+          1, rev_params);
+    }
+    if (rev < 0)
+    {
+      auth__rollback(p_store, AUTH_STORE_ERROR);
+      pthread_mutex_unlock(&p_store->mutex);
+      return AUTH_STORE_ERROR;
+    }
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  return AUTH_STORE_OK;
+}
+
+/* ------------------------------------------------------------------ */
+/* Session management                                                   */
+/* ------------------------------------------------------------------ */
+
+Auth_Store_Result Auth_Store_Create_Session(
+    Auth_Store          *p_store,
+    const char          *user_id,
+    const char          *token_digest,
+    const char          *csrf_digest,
+    int64                idle_ttl_secs,
+    int64                absolute_ttl_secs,
+    int64                current_unix,
+    Auth_Session_Record *p_record)
+{
+  if (!p_store || !user_id || !token_digest || !csrf_digest || !p_record)
+    return AUTH_STORE_INVALID_ARG;
+  if (!auth__session_arguments_are_valid(
+          token_digest, csrf_digest, idle_ttl_secs,
+          absolute_ttl_secs, current_unix))
+    return AUTH_STORE_INVALID_ARG;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* Verify user is active and read password_changed_at snapshot. */
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+  const char       *sel_params[] = {user_id};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT status, password_changed_at FROM users WHERE id = ?",
+      1, sel_params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_arena);
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_NOT_FOUND;
+  }
+  char  cur_status[9];
+  int64 pca;
+  auth__copy_text_fixed(cur_status, sizeof(cur_status),
+                        Deita_Result_Set_Get_Text(p_result, 0));
+  pca = Deita_Result_Set_Get_Integer(p_result, 1);
+  Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+
+  if (strcmp(cur_status, "disabled") == 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_USER_DISABLED;
+  }
+
+  Auth_Store_Result insert_result = auth__insert_session_locked(
+      p_store, user_id, token_digest, csrf_digest,
+      idle_ttl_secs, absolute_ttl_secs, current_unix, pca);
+  if (insert_result != AUTH_STORE_OK)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return insert_result;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+
+  auth__fill_session_record(
+      p_record, user_id, idle_ttl_secs, absolute_ttl_secs, current_unix, pca);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Create_Session_CAS(
+    Auth_Store          *p_store,
+    const char          *user_id,
+    const char          *expected_password_hash,
+    const char          *token_digest,
+    const char          *csrf_digest,
+    int64                idle_ttl_secs,
+    int64                absolute_ttl_secs,
+    int64                current_unix,
+    Auth_Session_Record *p_record)
+{
+  if (!p_store || !user_id || !expected_password_hash ||
+      expected_password_hash[0] == '\0' || !p_record)
+    return AUTH_STORE_INVALID_ARG;
+  if (!auth__session_arguments_are_valid(
+          token_digest, csrf_digest, idle_ttl_secs,
+          absolute_ttl_secs, current_unix))
+    return AUTH_STORE_INVALID_ARG;
+
+  char              current_hash[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE] = {0};
+  char              current_status[9] = {0};
+  int64             password_changed_at = 0;
+  Auth_Store_Result result = AUTH_STORE_ERROR;
+
+  pthread_mutex_lock(&p_store->mutex);
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    OPENSSL_cleanse(current_hash, sizeof(current_hash));
+    return AUTH_STORE_ERROR;
+  }
+
+  Dowa_Arena *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto create_session_cas_done;
+  }
+
+  const char *select_params[] = {user_id};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT status, password_changed_at, password_hash"
+      "  FROM users WHERE id = ?",
+      1, select_params, p_arena);
+  if (!p_result)
+  {
+    Dowa_Arena_Free(p_arena);
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto create_session_cas_done;
+  }
+  if (!Deita_Result_Set_Next(p_result))
+  {
+    boolean query_error = Deita_Result_Set_Has_Error(p_result);
+    Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_arena);
+    result = auth__rollback(
+        p_store, query_error ? AUTH_STORE_ERROR : AUTH_STORE_NOT_FOUND);
+    goto create_session_cas_done;
+  }
+
+  auth__copy_text_fixed(
+      current_status, sizeof(current_status),
+      Deita_Result_Set_Get_Text(p_result, 0));
+  password_changed_at = Deita_Result_Set_Get_Integer(p_result, 1);
+  auth__copy_text_fixed(
+      current_hash, sizeof(current_hash),
+      Deita_Result_Set_Get_Text(p_result, 2));
+  Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+
+  if (strcmp(current_status, "disabled") == 0)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_USER_DISABLED);
+    goto create_session_cas_done;
+  }
+  if (strcmp(current_hash, expected_password_hash) != 0)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_STALE_PASSWORD);
+    goto create_session_cas_done;
+  }
+
+  result = auth__insert_session_locked(
+      p_store, user_id, token_digest, csrf_digest,
+      idle_ttl_secs, absolute_ttl_secs, current_unix, password_changed_at);
+  if (result != AUTH_STORE_OK)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto create_session_cas_done;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto create_session_cas_done;
+  }
+
+  auth__fill_session_record(
+      p_record, user_id, idle_ttl_secs, absolute_ttl_secs,
+      current_unix, password_changed_at);
+  result = AUTH_STORE_OK;
+
+create_session_cas_done:
+  OPENSSL_cleanse(current_hash, sizeof(current_hash));
+  pthread_mutex_unlock(&p_store->mutex);
+  return result;
+}
+
+Auth_Store_Result Auth_Store_Find_Session(
+    Auth_Store          *p_store,
+    const char          *token_digest,
+    int64                current_unix,
+    Auth_Session_Record *p_session,
+    Auth_User_Record    *p_user)
+{
+  if (!p_store || !token_digest || !p_session || !p_user)
+    return AUTH_STORE_INVALID_ARG;
+
+  memset(p_session, 0, sizeof(*p_session));
+  memset(p_user,    0, sizeof(*p_user));
+
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(4096);
+  if (!p_arena)
+    return AUTH_STORE_ERROR;
+
+  const char       *params[] = {token_digest};
+  pthread_mutex_lock(&p_store->mutex);
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT"
+      "  s.user_id, s.created_at, s.last_seen_at,"
+      "  s.idle_expires_at, s.absolute_expires_at,"
+      "  s.password_changed_at_snapshot, s.revoked_at,"
+      "  u.id, u.username, u.normalized_username, u.role, u.status,"
+      "  u.must_change_password, u.password_changed_at,"
+      "  u.created_at, u.updated_at"
+      "  FROM auth_sessions s"
+      "  JOIN users u ON s.user_id = u.id"
+      "  WHERE s.token_digest = ?",
+      1, params, p_arena);
+
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_NOT_FOUND;
+  }
+
+  /* Read session fields. */
+  auth__copy_text_fixed(p_session->user_id, sizeof(p_session->user_id),
+                        Deita_Result_Set_Get_Text(p_result, 0));
+  p_session->created_at    = Deita_Result_Set_Get_Integer(p_result, 1);
+  p_session->last_seen_at  = Deita_Result_Set_Get_Integer(p_result, 2);
+  p_session->idle_expires_at       = Deita_Result_Set_Get_Integer(p_result, 3);
+  p_session->absolute_expires_at   = Deita_Result_Set_Get_Integer(p_result, 4);
+  p_session->password_changed_at_snapshot =
+      Deita_Result_Set_Get_Integer(p_result, 5);
+  boolean is_revoked =
+      (Deita_Result_Set_Get_Column_Type(p_result, 6) != DEITA_COLUMN_TYPE_NULL);
+
+  /* Read user fields (columns 7-15). */
+  /* Reuse auth__read_user_record after shifting: pass a pointer with offset */
+  Auth_User_Record tmp_user;
+  memset(&tmp_user, 0, sizeof(tmp_user));
+  auth__copy_text_fixed(tmp_user.id,                 sizeof(tmp_user.id),
+                        Deita_Result_Set_Get_Text(p_result, 7));
+  auth__copy_text_fixed(tmp_user.username,           sizeof(tmp_user.username),
+                        Deita_Result_Set_Get_Text(p_result, 8));
+  auth__copy_text_fixed(tmp_user.normalized_username,
+                        sizeof(tmp_user.normalized_username),
+                        Deita_Result_Set_Get_Text(p_result, 9));
+  auth__copy_text_fixed(tmp_user.role,   sizeof(tmp_user.role),
+                        Deita_Result_Set_Get_Text(p_result, 10));
+  auth__copy_text_fixed(tmp_user.status, sizeof(tmp_user.status),
+                        Deita_Result_Set_Get_Text(p_result, 11));
+  tmp_user.must_change_password  = Deita_Result_Set_Get_Integer(p_result, 12) ?
+                                    TRUE : FALSE;
+  tmp_user.password_changed_at   = Deita_Result_Set_Get_Integer(p_result, 13);
+  tmp_user.created_at            = Deita_Result_Set_Get_Integer(p_result, 14);
+  tmp_user.updated_at            = Deita_Result_Set_Get_Integer(p_result, 15);
+
+  Deita_Result_Set_Free(p_result);
+  pthread_mutex_unlock(&p_store->mutex);
+  Dowa_Arena_Free(p_arena);
+
+  /* Apply validity checks in order of specificity. */
+  if (is_revoked)
+    return AUTH_STORE_REVOKED;
+
+  if (current_unix >= p_session->idle_expires_at ||
+      current_unix >= p_session->absolute_expires_at)
+    return AUTH_STORE_EXPIRED;
+
+  if (strcmp(tmp_user.status, "disabled") == 0)
+    return AUTH_STORE_USER_DISABLED;
+
+  if (tmp_user.password_changed_at != p_session->password_changed_at_snapshot)
+    return AUTH_STORE_STALE_PASSWORD;
+
+  *p_user = tmp_user;
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Touch_Session(
+    Auth_Store *p_store,
+    const char *token_digest,
+    int64       current_unix,
+    int64       idle_ttl_secs)
+{
+  if (!p_store || !token_digest)
+    return AUTH_STORE_INVALID_ARG;
+
+  char last_seen_str[32], idle_exp_str[32];
+  snprintf(last_seen_str, sizeof(last_seen_str), "%lld", (long long)current_unix);
+  snprintf(idle_exp_str,  sizeof(idle_exp_str),  "%lld",
+           (long long)(current_unix + idle_ttl_secs));
+
+  const char *params[] = {last_seen_str, idle_exp_str, token_digest};
+
+  pthread_mutex_lock(&p_store->mutex);
+  int32 result = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "UPDATE auth_sessions"
+      "  SET last_seen_at = ?, idle_expires_at = ?"
+      "  WHERE token_digest = ? AND revoked_at IS NULL",
+      3, params);
+  pthread_mutex_unlock(&p_store->mutex);
+
+  if (result < 0)
+    return AUTH_STORE_ERROR;
+  return result == 0 ? AUTH_STORE_NOT_FOUND : AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Revoke_Session(
+    Auth_Store *p_store,
+    const char *token_digest)
+{
+  if (!p_store || !token_digest)
+    return AUTH_STORE_INVALID_ARG;
+
+  const char *params[] = {token_digest};
+
+  pthread_mutex_lock(&p_store->mutex);
+  int32 result = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "UPDATE auth_sessions SET revoked_at = strftime('%s','now')"
+      "  WHERE token_digest = ?",
+      1, params);
+  pthread_mutex_unlock(&p_store->mutex);
+
+  if (result < 0)
+    return AUTH_STORE_ERROR;
+  return result == 0 ? AUTH_STORE_NOT_FOUND : AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Revoke_All_Sessions(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *except_token_digest)
+{
+  if (!p_store || !user_id)
+    return AUTH_STORE_INVALID_ARG;
+
+  pthread_mutex_lock(&p_store->mutex);
+  int32 result;
+  if (except_token_digest && except_token_digest[0] != '\0')
+  {
+    const char *params[] = {user_id, except_token_digest};
+    result = Deita_Query_Execute_Update_Prepared(
+        p_store->p_connection,
+        "UPDATE auth_sessions SET revoked_at = strftime('%s','now')"
+        "  WHERE user_id = ? AND token_digest != ? AND revoked_at IS NULL",
+        2, params);
+  }
+  else
+  {
+    const char *params[] = {user_id};
+    result = Deita_Query_Execute_Update_Prepared(
+        p_store->p_connection,
+        "UPDATE auth_sessions SET revoked_at = strftime('%s','now')"
+        "  WHERE user_id = ? AND revoked_at IS NULL",
+        1, params);
+  }
+  pthread_mutex_unlock(&p_store->mutex);
+
+  return result < 0 ? AUTH_STORE_ERROR : AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Revoke_All_Sessions_Audited(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *except_token_digest,
+    const char *actor_user_id)
+{
+  if (!p_store || !user_id)
+    return AUTH_STORE_INVALID_ARG;
+  if (except_token_digest && except_token_digest[0] != '\0' &&
+      !auth__digest_is_valid(except_token_digest))
+    return AUTH_STORE_INVALID_ARG;
+
+  pthread_mutex_lock(&p_store->mutex);
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  Dowa_Arena *p_arena = Dowa_Arena_Create(1024);
+  if (!p_arena)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  const char *find_params[] = {user_id};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT 1 FROM users WHERE id = ?",
+      1, find_params, p_arena);
+  if (!p_result)
+  {
+    Dowa_Arena_Free(p_arena);
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+  if (!Deita_Result_Set_Next(p_result))
+  {
+    boolean query_error = Deita_Result_Set_Has_Error(p_result);
+    Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_arena);
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return query_error ? AUTH_STORE_ERROR : AUTH_STORE_NOT_FOUND;
+  }
+  Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+
+  int32 revoke_result;
+  if (except_token_digest && except_token_digest[0] != '\0')
+  {
+    const char *params[] = {user_id, except_token_digest};
+    revoke_result = Deita_Query_Execute_Update_Prepared(
+        p_store->p_connection,
+        "UPDATE auth_sessions SET revoked_at = strftime('%s','now')"
+        "  WHERE user_id = ? AND token_digest != ? AND revoked_at IS NULL",
+        2, params);
+  }
+  else
+  {
+    const char *params[] = {user_id};
+    revoke_result = Deita_Query_Execute_Update_Prepared(
+        p_store->p_connection,
+        "UPDATE auth_sessions SET revoked_at = strftime('%s','now')"
+        "  WHERE user_id = ? AND revoked_at IS NULL",
+        1, params);
+  }
+  if (revoke_result < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (!auth__insert_audit_log_locked(
+          p_store, actor_user_id, "admin_sessions_revoked", user_id, NULL))
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Rotate_Session(
+    Auth_Store          *p_store,
+    const char          *user_id,
+    const char          *old_token_digest,
+    const char          *new_token_digest,
+    const char          *new_csrf_digest,
+    int64                idle_ttl_secs,
+    int64                absolute_ttl_secs,
+    int64                current_unix,
+    Auth_Session_Record *p_record)
+{
+  if (!p_store || !user_id || !old_token_digest || !new_token_digest ||
+      !new_csrf_digest || !p_record || !auth__digest_is_valid(old_token_digest))
+    return AUTH_STORE_INVALID_ARG;
+  if (!auth__session_arguments_are_valid(
+          new_token_digest, new_csrf_digest, idle_ttl_secs,
+          absolute_ttl_secs, current_unix))
+    return AUTH_STORE_INVALID_ARG;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* Verify user is active and read password_changed_at snapshot. */
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+  const char       *sel_params[] = {user_id};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT status, password_changed_at FROM users WHERE id = ?",
+      1, sel_params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_arena);
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_NOT_FOUND;
+  }
+  char  cur_status[9];
+  int64 pca;
+  auth__copy_text_fixed(cur_status, sizeof(cur_status),
+                        Deita_Result_Set_Get_Text(p_result, 0));
+  pca = Deita_Result_Set_Get_Integer(p_result, 1);
+  Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+
+  if (strcmp(cur_status, "disabled") == 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_USER_DISABLED;
+  }
+
+  /* Insert new session. */
+  Auth_Store_Result insert_result = auth__insert_session_locked(
+      p_store, user_id, new_token_digest, new_csrf_digest,
+      idle_ttl_secs, absolute_ttl_secs, current_unix, pca);
+  if (insert_result != AUTH_STORE_OK)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return insert_result;
+  }
+
+  /* Revoke old session (idempotent: ignore if already revoked). */
+  const char *rev_params[] = {old_token_digest};
+  int32 revoke_result = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "UPDATE auth_sessions SET revoked_at = strftime('%s','now')"
+      "  WHERE token_digest = ? AND revoked_at IS NULL",
+      1, rev_params);
+  if (revoke_result < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+
+  auth__fill_session_record(
+      p_record, user_id, idle_ttl_secs, absolute_ttl_secs, current_unix, pca);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Self_Change_Password(
+    Auth_Store          *p_store,
+    const char          *user_id,
+    const char          *old_encoded_hash,
+    const char          *new_encoded_hash,
+    const char          *new_token_digest,
+    const char          *new_csrf_digest,
+    int64                idle_ttl_secs,
+    int64                absolute_ttl_secs,
+    int64                current_unix,
+    Auth_Session_Record *p_record)
+{
+  if (!p_store || !user_id || !old_encoded_hash || !new_encoded_hash ||
+      old_encoded_hash[0] == '\0' || new_encoded_hash[0] == '\0' || !p_record)
+    return AUTH_STORE_INVALID_ARG;
+  if (!auth__session_arguments_are_valid(
+          new_token_digest, new_csrf_digest, idle_ttl_secs,
+          absolute_ttl_secs, current_unix))
+    return AUTH_STORE_INVALID_ARG;
+
+  char current_hash[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE] = {0};
+  char reread_hash[AUTH_CRYPTO_PASSWORD_HASH_ENCODED_SIZE] = {0};
+  char status[9] = {0};
+  char timestamp[32];
+  snprintf(timestamp, sizeof(timestamp), "%lld", (long long)current_unix);
+  Auth_Store_Result result = AUTH_STORE_ERROR;
+
+  pthread_mutex_lock(&p_store->mutex);
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    OPENSSL_cleanse(current_hash, sizeof(current_hash));
+    OPENSSL_cleanse(reread_hash, sizeof(reread_hash));
+    return AUTH_STORE_ERROR;
+  }
+
+  Dowa_Arena *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto self_change_done;
+  }
+
+  const char *select_params[] = {user_id};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT status, password_hash FROM users WHERE id = ?",
+      1, select_params, p_arena);
+  if (!p_result)
+  {
+    Dowa_Arena_Free(p_arena);
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto self_change_done;
+  }
+  if (!Deita_Result_Set_Next(p_result))
+  {
+    boolean query_error = Deita_Result_Set_Has_Error(p_result);
+    Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_arena);
+    result = auth__rollback(
+        p_store, query_error ? AUTH_STORE_ERROR : AUTH_STORE_NOT_FOUND);
+    goto self_change_done;
+  }
+  auth__copy_text_fixed(
+      status, sizeof(status), Deita_Result_Set_Get_Text(p_result, 0));
+  auth__copy_text_fixed(
+      current_hash, sizeof(current_hash),
+      Deita_Result_Set_Get_Text(p_result, 1));
+  Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+
+  if (strcmp(status, "disabled") == 0)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_USER_DISABLED);
+    goto self_change_done;
+  }
+  if (strcmp(current_hash, old_encoded_hash) != 0)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_STALE_PASSWORD);
+    goto self_change_done;
+  }
+
+  const char *update_params[] = {
+    new_encoded_hash, timestamp, timestamp, user_id, old_encoded_hash
+  };
+  int32 update_result = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "UPDATE users"
+      "  SET password_hash = ?, password_changed_at = ?,"
+      "      must_change_password = 0, updated_at = ?"
+      "  WHERE id = ? AND password_hash = ?",
+      5, update_params);
+  if (update_result < 0)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto self_change_done;
+  }
+  if (update_result == 0)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_STALE_PASSWORD);
+    goto self_change_done;
+  }
+
+  p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto self_change_done;
+  }
+  p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT status, password_changed_at, password_hash"
+      "  FROM users WHERE id = ?",
+      1, select_params, p_arena);
+  if (!p_result)
+  {
+    Dowa_Arena_Free(p_arena);
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto self_change_done;
+  }
+  if (!Deita_Result_Set_Next(p_result))
+  {
+    Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_arena);
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto self_change_done;
+  }
+  auth__copy_text_fixed(
+      status, sizeof(status), Deita_Result_Set_Get_Text(p_result, 0));
+  int64 password_changed_at = Deita_Result_Set_Get_Integer(p_result, 1);
+  auth__copy_text_fixed(
+      reread_hash, sizeof(reread_hash),
+      Deita_Result_Set_Get_Text(p_result, 2));
+  Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+  if (strcmp(status, "active") != 0 ||
+      password_changed_at != current_unix ||
+      strcmp(reread_hash, new_encoded_hash) != 0)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto self_change_done;
+  }
+
+  const char *revoke_params[] = {timestamp, user_id};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "UPDATE auth_sessions SET revoked_at = ?"
+          "  WHERE user_id = ? AND revoked_at IS NULL",
+          2, revoke_params) < 0)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto self_change_done;
+  }
+
+  result = auth__insert_session_locked(
+      p_store, user_id, new_token_digest, new_csrf_digest,
+      idle_ttl_secs, absolute_ttl_secs, current_unix, password_changed_at);
+  if (result != AUTH_STORE_OK)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto self_change_done;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    result = auth__rollback(p_store, AUTH_STORE_ERROR);
+    goto self_change_done;
+  }
+
+  auth__fill_session_record(
+      p_record, user_id, idle_ttl_secs, absolute_ttl_secs,
+      current_unix, password_changed_at);
+  result = AUTH_STORE_OK;
+
+self_change_done:
+  OPENSSL_cleanse(current_hash, sizeof(current_hash));
+  OPENSSL_cleanse(reread_hash, sizeof(reread_hash));
+  pthread_mutex_unlock(&p_store->mutex);
+  return result;
+}
+
+/* ------------------------------------------------------------------ */
+/* Guest identity                                                       */
+/* ------------------------------------------------------------------ */
+
+Auth_Store_Result Auth_Store_Upsert_Guest_Identity(
+    Auth_Store                *p_store,
+    const char                *guest_id,
+    const char                *ip_binding_digest,
+    int64                      expires_at,
+    Auth_Guest_Identity_Record *p_record)
+{
+  if (!p_store || !guest_id || !ip_binding_digest || !p_record)
+    return AUTH_STORE_INVALID_ARG;
+
+  char exp_str[32];
+  snprintf(exp_str, sizeof(exp_str), "%lld", (long long)expires_at);
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* INSERT OR IGNORE so usage data is not cascade-deleted on upsert. */
+  const char *ins_params[] = {guest_id, ip_binding_digest, exp_str};
+  Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "INSERT OR IGNORE INTO guest_identities (id, ip_binding_digest, expires_at)"
+      "  VALUES (?, ?, ?)",
+      3, ins_params);
+
+  /* Always refresh last_seen_at and expires_at. */
+  const char *upd_params[] = {exp_str, guest_id};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "UPDATE guest_identities"
+          "  SET last_seen_at = strftime('%s','now'), expires_at = ?"
+          "  WHERE id = ?",
+          2, upd_params) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* Read back the current row. */
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+  const char       *sel_params[] = {guest_id};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT id, created_at, last_seen_at, expires_at"
+      "  FROM guest_identities WHERE id = ?",
+      1, sel_params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    Dowa_Arena_Free(p_arena);
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+  auth__copy_text_fixed(p_record->id, sizeof(p_record->id),
+                        Deita_Result_Set_Get_Text(p_result, 0));
+  p_record->created_at   = Deita_Result_Set_Get_Integer(p_result, 1);
+  p_record->last_seen_at = Deita_Result_Set_Get_Integer(p_result, 2);
+  p_record->expires_at   = Deita_Result_Set_Get_Integer(p_result, 3);
+  Deita_Result_Set_Free(p_result);
+  Dowa_Arena_Free(p_arena);
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Find_Guest_Identity(
+    Auth_Store                *p_store,
+    const char                *guest_id,
+    int64                      current_unix,
+    Auth_Guest_Identity_Record *p_record)
+{
+  if (!p_store || !guest_id || !p_record)
+    return AUTH_STORE_INVALID_ARG;
+
+  memset(p_record, 0, sizeof(*p_record));
+
+  Dowa_Arena       *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+    return AUTH_STORE_ERROR;
+
+  const char       *params[] = {guest_id};
+  pthread_mutex_lock(&p_store->mutex);
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT id, created_at, last_seen_at, expires_at"
+      "  FROM guest_identities WHERE id = ?",
+      1, params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result)
+      Deita_Result_Set_Free(p_result);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_NOT_FOUND;
+  }
+  auth__copy_text_fixed(p_record->id, sizeof(p_record->id),
+                        Deita_Result_Set_Get_Text(p_result, 0));
+  p_record->created_at   = Deita_Result_Set_Get_Integer(p_result, 1);
+  p_record->last_seen_at = Deita_Result_Set_Get_Integer(p_result, 2);
+  p_record->expires_at   = Deita_Result_Set_Get_Integer(p_result, 3);
+  Deita_Result_Set_Free(p_result);
+  pthread_mutex_unlock(&p_store->mutex);
+  Dowa_Arena_Free(p_arena);
+
+  if (current_unix >= p_record->expires_at)
+    return AUTH_STORE_EXPIRED;
+
+  return AUTH_STORE_OK;
+}
+
+/* ------------------------------------------------------------------ */
+/* Guest quota                                                          */
+/* ------------------------------------------------------------------ */
+
+static void auth__i64_str(char *buf, size_t size, int64 value)
+{
+  snprintf(buf, size, "%lld", (long long)value);
+}
+
+Auth_Store_Result Auth_Store_Guest_Get_Usage(
+    Auth_Store             *p_store,
+    const char             *guest_id,
+    int64                   window_start,
+    Auth_Store_Guest_Usage *p_usage)
+{
+  if (!p_store || !guest_id || !p_usage)
+    return AUTH_STORE_INVALID_ARG;
+
+  memset(p_usage, 0, sizeof(*p_usage));
+
+  char ws_str[32];
+  auth__i64_str(ws_str, sizeof(ws_str), window_start);
+
+  Dowa_Arena *p_arena = Dowa_Arena_Create(2048);
+  if (!p_arena)
+    return AUTH_STORE_ERROR;
+
+  const char       *params[] = {guest_id, ws_str};
+  pthread_mutex_lock(&p_store->mutex);
+  auth__reap_expired_reservations_locked(p_store, (int64)time(NULL));
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT turns_used, output_tokens_used, output_tokens_reserved"
+      "  FROM guest_usage WHERE guest_id = ? AND window_start = ?",
+      2, params, p_arena);
+  if (p_result && Deita_Result_Set_Next(p_result))
+  {
+    p_usage->turns_used             = Deita_Result_Set_Get_Integer(p_result, 0);
+    p_usage->output_tokens_used     = Deita_Result_Set_Get_Integer(p_result, 1);
+    p_usage->output_tokens_reserved = Deita_Result_Set_Get_Integer(p_result, 2);
+  }
+  if (p_result)
+    Deita_Result_Set_Free(p_result);
+  pthread_mutex_unlock(&p_store->mutex);
+  Dowa_Arena_Free(p_arena);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Guest_Quota_Result Auth_Store_Guest_Reserve(
+    Auth_Store *p_store,
+    const char *guest_id,
+    const char *request_id,
+    int64       window_start,
+    int64       max_output_tokens,
+    int64       turns_limit,
+    int64       tokens_limit,
+    int64       reservation_expires)
+{
+  if (!p_store || !guest_id || !request_id ||
+      max_output_tokens <= 0 || turns_limit <= 0 || tokens_limit <= 0)
+    return AUTH_STORE_GUEST_QUOTA_ERROR;
+
+  char ws_str[32], exp_str[32], tok_str[32];
+  auth__i64_str(ws_str,  sizeof(ws_str),  window_start);
+  auth__i64_str(exp_str, sizeof(exp_str), reservation_expires);
+  auth__i64_str(tok_str, sizeof(tok_str), max_output_tokens);
+
+  Dowa_Arena *p_arena = Dowa_Arena_Create(4096);
+  if (!p_arena)
+    return AUTH_STORE_GUEST_QUOTA_ERROR;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_GUEST_QUOTA_ERROR;
+  }
+  auth__reap_expired_reservations_locked(p_store, (int64)time(NULL));
+
+  /* Ensure usage row exists for this window. */
+  const char *ins_params[] = {guest_id, ws_str};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "INSERT OR IGNORE INTO guest_usage"
+          "  (guest_id, window_start, count)"
+          "  VALUES (?, ?, 0)",
+          2, ins_params) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_GUEST_QUOTA_ERROR;
+  }
+
+  /* Read current usage. */
+  const char       *sel_params[] = {guest_id, ws_str};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT turns_used, output_tokens_used, output_tokens_reserved"
+      "  FROM guest_usage WHERE guest_id = ? AND window_start = ?",
+      2, sel_params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    if (p_result) Deita_Result_Set_Free(p_result);
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_GUEST_QUOTA_ERROR;
+  }
+  int64 turns_used    = Deita_Result_Set_Get_Integer(p_result, 0);
+  int64 tokens_used   = Deita_Result_Set_Get_Integer(p_result, 1);
+  int64 tokens_resvd  = Deita_Result_Set_Get_Integer(p_result, 2);
+  Deita_Result_Set_Free(p_result);
+
+  /* Check turn limit. */
+  if (turns_used >= turns_limit)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_GUEST_QUOTA_TURNS_EXHAUSTED;
+  }
+
+  /* Check token limit: used + reserved + new_reservation <= limit. */
+  if (tokens_used + tokens_resvd + max_output_tokens > tokens_limit)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_GUEST_QUOTA_TOKENS_EXHAUSTED;
+  }
+
+  /* Update usage: charge turn, add token reservation. */
+  const char *upd_params[] = {tok_str, guest_id, ws_str};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "UPDATE guest_usage"
+          "  SET turns_used = turns_used + 1,"
+          "      output_tokens_reserved = output_tokens_reserved + ?,"
+          "      count = count + 1"
+          "  WHERE guest_id = ? AND window_start = ?",
+          3, upd_params) <= 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_GUEST_QUOTA_ERROR;
+  }
+
+  /* Insert reservation row. */
+  const char *res_params[] = {request_id, guest_id, ws_str, tok_str, exp_str};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "INSERT INTO guest_usage_reservations"
+          "  (request_id, guest_id, window_start, output_tokens_reserved, expires_at)"
+          "  VALUES (?, ?, ?, ?, ?)",
+          5, res_params) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_GUEST_QUOTA_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_GUEST_QUOTA_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  Dowa_Arena_Free(p_arena);
+  return AUTH_STORE_GUEST_QUOTA_OK;
+}
+
+Auth_Store_Result Auth_Store_Guest_Reconcile(
+    Auth_Store *p_store,
+    const char *request_id,
+    int64       actual_output_tokens)
+{
+  if (!p_store || !request_id)
+    return AUTH_STORE_INVALID_ARG;
+  if (actual_output_tokens < 0)
+    actual_output_tokens = 0;
+
+  Dowa_Arena *p_arena = Dowa_Arena_Create(4096);
+  if (!p_arena)
+    return AUTH_STORE_ERROR;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* Fetch reservation. */
+  const char       *sel_params[] = {request_id};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT guest_id, window_start, output_tokens_reserved"
+      "  FROM guest_usage_reservations WHERE request_id = ?",
+      1, sel_params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    /* Idempotent: reservation already gone. */
+    if (p_result) Deita_Result_Set_Free(p_result);
+    Deita_Query_Execute_Update(p_store->p_connection, "COMMIT");
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_OK;
+  }
+
+  char guest_id[37];
+  auth__copy_text_fixed(guest_id, sizeof(guest_id),
+                        Deita_Result_Set_Get_Text(p_result, 0));
+  int64 window_start       = Deita_Result_Set_Get_Integer(p_result, 1);
+  int64 tokens_reserved    = Deita_Result_Set_Get_Integer(p_result, 2);
+  Deita_Result_Set_Free(p_result);
+
+  /* Charge provider-reported usage even when it exceeds the reservation. */
+  int64 to_charge = actual_output_tokens;
+
+  char ws_str[32], charge_str[32], res_str[32];
+  auth__i64_str(ws_str,     sizeof(ws_str),     window_start);
+  auth__i64_str(charge_str, sizeof(charge_str), to_charge);
+  auth__i64_str(res_str,    sizeof(res_str),    tokens_reserved);
+
+  /* Update usage: add actual tokens, subtract reservation. */
+  const char *upd_params[] = {charge_str, res_str, guest_id, ws_str};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "UPDATE guest_usage"
+          "  SET output_tokens_used = output_tokens_used + ?,"
+          "      output_tokens_reserved = MAX(0, output_tokens_reserved - ?)"
+          "  WHERE guest_id = ? AND window_start = ?",
+          4, upd_params) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* Delete reservation. */
+  const char *del_params[] = {request_id};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "DELETE FROM guest_usage_reservations WHERE request_id = ?",
+          1, del_params) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  Dowa_Arena_Free(p_arena);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Guest_Release(
+    Auth_Store *p_store,
+    const char *request_id)
+{
+  if (!p_store || !request_id)
+    return AUTH_STORE_INVALID_ARG;
+
+  Dowa_Arena *p_arena = Dowa_Arena_Create(4096);
+  if (!p_arena)
+    return AUTH_STORE_ERROR;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* Fetch reservation. */
+  const char       *sel_params[] = {request_id};
+  Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(
+      p_store->p_connection,
+      "SELECT guest_id, window_start, output_tokens_reserved"
+      "  FROM guest_usage_reservations WHERE request_id = ?",
+      1, sel_params, p_arena);
+  if (!p_result || !Deita_Result_Set_Next(p_result))
+  {
+    /* Idempotent: reservation already gone. */
+    if (p_result) Deita_Result_Set_Free(p_result);
+    Deita_Query_Execute_Update(p_store->p_connection, "COMMIT");
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_OK;
+  }
+
+  char guest_id[37];
+  auth__copy_text_fixed(guest_id, sizeof(guest_id),
+                        Deita_Result_Set_Get_Text(p_result, 0));
+  int64 window_start    = Deita_Result_Set_Get_Integer(p_result, 1);
+  int64 tokens_reserved = Deita_Result_Set_Get_Integer(p_result, 2);
+  Deita_Result_Set_Free(p_result);
+
+  char ws_str[32], res_str[32];
+  auth__i64_str(ws_str,  sizeof(ws_str),  window_start);
+  auth__i64_str(res_str, sizeof(res_str), tokens_reserved);
+
+  /* Release token reservation; turns_used is unchanged (turn already charged). */
+  const char *upd_params[] = {res_str, guest_id, ws_str};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "UPDATE guest_usage"
+          "  SET output_tokens_reserved = MAX(0, output_tokens_reserved - ?)"
+          "  WHERE guest_id = ? AND window_start = ?",
+          3, upd_params) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* Delete reservation. */
+  const char *del_params[] = {request_id};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "DELETE FROM guest_usage_reservations WHERE request_id = ?",
+          1, del_params) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    Dowa_Arena_Free(p_arena);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  Dowa_Arena_Free(p_arena);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Guest_Clear_Reservations(
+    Auth_Store *p_store,
+    const char *guest_id)
+{
+  if (!p_store || !guest_id)
+    return AUTH_STORE_INVALID_ARG;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  /*
+   * Subtract each window's reserved tokens from the usage row.
+   * The correlated subquery aggregates reservations per window.
+   */
+  const char *upd_params[] = {guest_id, guest_id};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "UPDATE guest_usage"
+          "  SET output_tokens_reserved = MAX(0, output_tokens_reserved - ("
+          "    SELECT COALESCE(SUM(r.output_tokens_reserved), 0)"
+          "    FROM guest_usage_reservations r"
+          "    WHERE r.guest_id = ? AND r.window_start = guest_usage.window_start"
+          "  ))"
+          "  WHERE guest_id = ?",
+          2, upd_params) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  /* Delete all reservations for this guest. */
+  const char *del_params[] = {guest_id};
+  if (Deita_Query_Execute_Update_Prepared(
+          p_store->p_connection,
+          "DELETE FROM guest_usage_reservations WHERE guest_id = ?",
+          1, del_params) < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  return AUTH_STORE_OK;
+}
+
+Auth_Store_Result Auth_Store_Guest_Reap_Expired(
+    Auth_Store *p_store,
+    int64       current_unix)
+{
+  if (!p_store)
+    return AUTH_STORE_INVALID_ARG;
+  pthread_mutex_lock(&p_store->mutex);
+  auth__reap_expired_reservations_locked(p_store, current_unix);
+  pthread_mutex_unlock(&p_store->mutex);
+  return AUTH_STORE_OK;
+}
+
+/* ------------------------------------------------------------------ */
+/* Audit log (public)                                                   */
+/* ------------------------------------------------------------------ */
+
+Auth_Store_Result Auth_Store_Insert_Audit_Log(
+    Auth_Store *p_store,
+    const char *actor_user_id,
+    const char *action,
+    const char *target_user_id,
+    const char *detail)
+{
+  if (!p_store || !action || action[0] == '\0')
+    return AUTH_STORE_INVALID_ARG;
+
+  pthread_mutex_lock(&p_store->mutex);
+  boolean inserted = auth__insert_audit_log_locked(
+      p_store, actor_user_id, action, target_user_id, detail);
+  pthread_mutex_unlock(&p_store->mutex);
+  return inserted ? AUTH_STORE_OK : AUTH_STORE_ERROR;
+}
+
+/* ------------------------------------------------------------------ */
+/* Admin password reset                                                 */
+/* ------------------------------------------------------------------ */
+
+Auth_Store_Result Auth_Store_Admin_Reset_Password(
+    Auth_Store *p_store,
+    const char *user_id,
+    const char *new_encoded_hash,
+    const char *actor_user_id)
+{
+  if (!p_store || !user_id || !new_encoded_hash)
+    return AUTH_STORE_INVALID_ARG;
+  if (new_encoded_hash[0] == '\0')
+    return AUTH_STORE_INVALID_ARG;
+
+  pthread_mutex_lock(&p_store->mutex);
+
+  if (Deita_Query_Execute_Update(
+          p_store->p_connection, "BEGIN IMMEDIATE") < 0)
+  {
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  const char *upd_params[] = {new_encoded_hash, user_id};
+  int32 upd = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "UPDATE users"
+      "  SET password_hash = ?,"
+      "      password_changed_at = strftime('%s','now'),"
+      "      must_change_password = 1,"
+      "      updated_at = strftime('%s','now')"
+      "  WHERE id = ?",
+      2, upd_params);
+  if (upd <= 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return upd < 0 ? AUTH_STORE_ERROR : AUTH_STORE_NOT_FOUND;
+  }
+
+  const char *rev_params[] = {user_id};
+  int32 rev = Deita_Query_Execute_Update_Prepared(
+      p_store->p_connection,
+      "UPDATE auth_sessions"
+      "  SET revoked_at = strftime('%s','now')"
+      "  WHERE user_id = ? AND revoked_at IS NULL",
+      1, rev_params);
+  if (rev < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (!auth__insert_audit_log_locked(
+          p_store, actor_user_id, "admin_temp_password_reset", user_id, NULL))
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  if (Deita_Query_Execute_Update(p_store->p_connection, "COMMIT") < 0)
+  {
+    auth__rollback(p_store, AUTH_STORE_ERROR);
+    pthread_mutex_unlock(&p_store->mutex);
+    return AUTH_STORE_ERROR;
+  }
+
+  pthread_mutex_unlock(&p_store->mutex);
+  return AUTH_STORE_OK;
+}