use super::{
    AedbInstance, CommitFinality, CommitTelemetryEvent, LifecycleEvent, LifecycleHook,
    QueryBatchItem, QueryCommitTelemetryHook, QueryTelemetryEvent, REACTIVE_ACK_CACHE_MAX_ENTRIES,
    ReactiveCheckpointAckCacheKey, ReactiveCheckpointAckState, ReadOnlySqlAdapter, RecoveryCache,
    RemoteBackupAdapter, SYSTEM_CALLER_ID,
};
use crate::PredicateEvaluationPath;
use crate::catalog::schema::{ColumnDef, IndexType};
use crate::catalog::types::{ColumnType, Row, Value};
use crate::catalog::{DdlOperation, ResourceType};
use crate::commit::action::{ActionCommitOutcome, ActionEnvelopeRequest};
use crate::commit::tx::{
    IdempotencyKey, ReadAssertion, TransactionEnvelope, WriteClass, WriteIntent,
};
use crate::commit::validation::{
    KvIntegerAmount, KvIntegerMissingPolicy, KvIntegerUnderflowPolicy, KvU64MissingPolicy,
    KvU64OverflowPolicy, KvU64UnderflowPolicy, KvU256MissingPolicy, KvU256UnderflowPolicy,
    MAX_COUNTER_SHARDS, Mutation,
};
use crate::config::{AedbConfig, DurabilityMode, RecoveryMode, StorageMode};
use crate::error::{AedbError, AedbErrorCode, ResourceType as ErrorResourceType};
use crate::permission::{CallerContext, Permission};
use crate::query::error::QueryError;
use crate::query::plan::{ConsistencyMode, Expr, Query, QueryOptions};
use crate::query::planner::ExecutionStage;
use std::fs;
use std::ops::Bound;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};
use tempfile::tempdir;

fn mk_recovery_view(seq: u64) -> crate::snapshot::reader::SnapshotReadView {
    crate::snapshot::reader::SnapshotReadView {
        keyspace: Arc::new(crate::storage::keyspace::Keyspace::default().snapshot()),
        catalog: Arc::new(crate::catalog::Catalog::default()),
        seq,
    }
}

struct RecordingLifecycleHook {
    events: Arc<std::sync::Mutex<Vec<LifecycleEvent>>>,
}

impl LifecycleHook for RecordingLifecycleHook {
    fn on_event(&self, event: &LifecycleEvent) {
        self.events
            .lock()
            .expect("recording hook mutex poisoned")
            .push(event.clone());
    }
}

#[test]
fn recovery_cache_refresh_keeps_recent_entry() {
    let mut cache = RecoveryCache::default();
    for seq in 1..=16 {
        cache.put(seq, mk_recovery_view(seq));
    }
    assert!(
        cache.get(1).is_some(),
        "first entry should exist before refresh"
    );

    cache.put(17, mk_recovery_view(17));

    assert!(
        cache.get(1).is_some(),
        "refreshing an entry must protect it from immediate eviction"
    );
    assert!(
        cache.get(2).is_none(),
        "oldest non-refreshed entry should be evicted first"
    );
}

#[test]
fn recovery_cache_prunes_expired_entries() {
    let mut cache = RecoveryCache::default();
    cache.put(7, mk_recovery_view(7));
    {
        let entry = cache.entries.get_mut(&7).expect("entry");
        entry.created = Instant::now() - super::RECOVERY_CACHE_TTL - Duration::from_secs(1);
    }
    cache.prune_expired();
    assert!(cache.get(7).is_none(), "expired entry must be removed");
}

#[tokio::test]
async fn api_open_commit_query_shutdown() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1), Value::Text("reader".into())],
        },
    })
    .await
    .expect("insert");

    let result = db
        .query(
            "p",
            "app",
            Query::select(&["*"])
                .from("users")
                .where_(Expr::Eq("id".into(), Value::Integer(1))),
        )
        .await
        .expect("query");
    assert_eq!(result.rows.len(), 1);

    db.create_scope("p", "private").await.expect("create scope");
    let scopes = db.list_scopes("p").await.expect("list scopes");
    assert!(scopes.contains(&"app".to_string()));
    assert!(scopes.contains(&"private".to_string()));
    db.drop_scope("p", "private").await.expect("drop scope");
    db.shutdown().await.expect("shutdown");
}

#[tokio::test]
async fn permissions_enforced_at_preflight_commit_and_query() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::TableWrite {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
    }))
    .await
    .expect("grant write");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
    }))
    .await
    .expect("grant read");

    let caller = CallerContext::new("alice");
    let insert = Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1), Value::Text("alice".into())],
        },
    };
    db.preflight_as(&caller, insert.clone())
        .await
        .expect("preflight allowed");
    db.commit_as(caller.clone(), insert)
        .await
        .expect("commit allowed");

    db.commit(Mutation::Ddl(DdlOperation::RevokePermission {
        actor_id: None,
        caller_id: "alice".into(),
        permission: Permission::TableWrite {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
    }))
    .await
    .expect("revoke");

    let denied = db
        .preflight_as(
            &caller,
            Mutation::Upsert {
                project_id: "p".into(),
                scope_id: "app".into(),
                table_name: "users".into(),
                primary_key: vec![Value::Integer(2)],
                row: Row {
                    values: vec![Value::Integer(2), Value::Text("x".into())],
                },
            },
        )
        .await
        .expect_err("preflight denied");
    assert!(matches!(
        denied,
        crate::error::AedbError::PermissionDenied(_)
    ));

    let q = db
        .query_with_options_as(
            Some(&caller),
            "p",
            "app",
            Query::select(&["*"]).from("users").limit(10),
            crate::query::plan::QueryOptions::default(),
        )
        .await
        .expect("query with read perm");
    assert_eq!(q.rows.len(), 1);
}

#[tokio::test]
async fn insert_rejects_duplicate_primary_key() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");

    db.insert(
        "p",
        "app",
        "users",
        vec![Value::Integer(1)],
        Row {
            values: vec![Value::Integer(1), Value::Text("alice".into())],
        },
    )
    .await
    .expect("first insert");

    let duplicate = db
        .insert(
            "p",
            "app",
            "users",
            vec![Value::Integer(1)],
            Row {
                values: vec![Value::Integer(1), Value::Text("alice-2".into())],
            },
        )
        .await
        .expect_err("duplicate insert must fail");
    let duplicate_text = duplicate.to_string();
    assert!(
        duplicate_text.contains("duplicate primary key"),
        "unexpected duplicate insert error: {duplicate:?}"
    );
}

#[tokio::test]
async fn insert_batch_rejects_duplicate_primary_key() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");

    db.insert_batch(
        "p",
        "app",
        "users",
        vec![
            Row {
                values: vec![Value::Integer(1), Value::Text("alice".into())],
            },
            Row {
                values: vec![Value::Integer(2), Value::Text("bob".into())],
            },
        ],
    )
    .await
    .expect("seed batch");

    let duplicate = db
        .insert_batch(
            "p",
            "app",
            "users",
            vec![
                Row {
                    values: vec![Value::Integer(3), Value::Text("charlie".into())],
                },
                Row {
                    values: vec![Value::Integer(1), Value::Text("alice-2".into())],
                },
            ],
        )
        .await
        .expect_err("duplicate insert_batch must fail");
    let duplicate_text = duplicate.to_string();
    assert!(
        duplicate_text.contains("duplicate primary key"),
        "unexpected duplicate insert_batch error: {duplicate:?}"
    );
}

#[tokio::test]
async fn join_queries_require_permissions_for_all_referenced_tables() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project p");
    db.create_project("_global").await.expect("project global");

    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("users table");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "_global".into(),
        scope_id: "app".into(),
        table_name: "profiles".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "email".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("profiles table");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
    }))
    .await
    .expect("grant base read");
    let caller = CallerContext::new("alice");
    let query = Query::select(&["u.id"])
        .from("users")
        .alias("u")
        .left_join("_global.profiles", "u.id", "id")
        .with_last_join_alias("g")
        .limit(10);
    let denied = db
        .query_with_options_as(
            Some(&caller),
            "p",
            "app",
            query.clone(),
            crate::query::plan::QueryOptions::default(),
        )
        .await
        .expect_err("join should require global table read");
    assert!(matches!(
        denied,
        crate::query::error::QueryError::PermissionDenied { .. }
    ));

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::TableRead {
            project_id: "_global".into(),
            scope_id: "app".into(),
            table_name: "profiles".into(),
        },
    }))
    .await
    .expect("grant global read");
    let _ = db
        .query_with_options_as(
            Some(&caller),
            "p",
            "app",
            query,
            crate::query::plan::QueryOptions::default(),
        )
        .await
        .expect("join allowed");
}

#[tokio::test]
async fn permission_revocation_between_preflight_and_commit_is_caught() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::TableWrite {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
    }))
    .await
    .expect("grant");
    let caller = CallerContext::new("alice");
    let mutation = Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1), Value::Text("alice".into())],
        },
    };
    db.preflight_as(&caller, mutation.clone())
        .await
        .expect("preflight ok");
    db.commit(Mutation::Ddl(DdlOperation::RevokePermission {
        actor_id: None,
        caller_id: "alice".into(),
        permission: Permission::TableWrite {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
    }))
    .await
    .expect("revoke");
    let err = db
        .commit_as(caller, mutation)
        .await
        .expect_err("denied at commit");
    assert!(matches!(err, crate::error::AedbError::PermissionDenied(_)));
}

#[tokio::test]
async fn async_projection_index_reports_materialized_seq() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");

    let r = db
        .commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            primary_key: vec![Value::Integer(1)],
            row: Row {
                values: vec![Value::Integer(1), Value::Text("alice".into())],
            },
        })
        .await
        .expect("insert");
    db.commit(Mutation::Ddl(DdlOperation::CreateAsyncIndex {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        index_name: "users_view".into(),
        if_not_exists: false,
        projected_columns: vec!["id".into(), "name".into()],
    }))
    .await
    .expect("create async index");

    let opts = QueryOptions {
        async_index: Some("users_view".into()),
        allow_full_scan: true,
        ..QueryOptions::default()
    };

    let mut observed = None;
    for _ in 0..20 {
        let q = db
            .query_with_options(
                "p",
                "app",
                Query::select(&["*"]).from("users"),
                opts.clone(),
            )
            .await
            .expect("query async");
        observed = q.materialized_seq;
        if let Some(m) = q.materialized_seq
            && m >= r.commit_seq
        {
            assert_eq!(q.rows.len(), 1);
            return;
        }
        tokio::time::sleep(std::time::Duration::from_millis(20)).await;
    }
    panic!("async index did not catch up, last materialized_seq={observed:?}");
}

#[tokio::test]
async fn kv_query_apis_enforce_permissions_and_paginate() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let caller = CallerContext::new("alice");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: None,
        },
    }))
    .await
    .expect("grant kv read");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::KvWrite {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: None,
        },
    }))
    .await
    .expect("grant kv write");

    for (k, v) in [("user:1", "alice"), ("user:2", "bob"), ("user:3", "carol")] {
        db.commit_as(
            caller.clone(),
            Mutation::KvSet {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: k.as_bytes().to_vec(),
                value: v.as_bytes().to_vec(),
            },
        )
        .await
        .expect("kv set");
    }

    let one = db
        .kv_get("p", "app", b"user:1", ConsistencyMode::AtLatest, &caller)
        .await
        .expect("kv_get")
        .expect("exists");
    assert_eq!(one.value, b"alice".to_vec());

    let first = db
        .kv_scan_prefix(
            "p",
            "app",
            b"user:",
            2,
            None,
            ConsistencyMode::AtLatest,
            &caller,
        )
        .await
        .expect("scan 1");
    assert_eq!(first.entries.len(), 2);
    assert!(first.truncated);

    let second = db
        .kv_scan_prefix(
            "p",
            "app",
            b"user:",
            2,
            first.cursor.clone(),
            ConsistencyMode::AtLatest,
            &caller,
        )
        .await
        .expect("scan 2");
    assert_eq!(second.entries.len(), 1);
    assert!(!second.truncated);

    let ranged = db
        .kv_scan_range(
            "p",
            "app",
            Bound::Included(b"user:1".to_vec()),
            Bound::Included(b"user:2".to_vec()),
            10,
            None,
            ConsistencyMode::AtLatest,
            &caller,
        )
        .await
        .expect("scan range");
    assert_eq!(ranged.entries.len(), 2);
}

#[tokio::test]
async fn kv_permissions_scope_reads_and_writes_with_prefix_filters() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    let caller = CallerContext::new("alice");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: Some(b"bank:user:42:".to_vec()),
        },
    }))
    .await
    .expect("grant kv prefix read");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::KvWrite {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: Some(b"bank:user:42:".to_vec()),
        },
    }))
    .await
    .expect("grant kv prefix write");

    db.commit_as(
        caller.clone(),
        Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: b"bank:user:42:balance".to_vec(),
            value: b"100".to_vec(),
        },
    )
    .await
    .expect("allowed prefix write");

    let err = db
        .commit_as(
            caller.clone(),
            Mutation::KvSet {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: b"bank:user:99:balance".to_vec(),
                value: b"100".to_vec(),
            },
        )
        .await
        .expect_err("write outside prefix must fail");
    assert!(matches!(err, AedbError::PermissionDenied(_)));

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"bank:user:99:balance".to_vec(),
        value: b"77".to_vec(),
    })
    .await
    .expect("seed other key");

    let scan = db
        .kv_scan_prefix(
            "p",
            "app",
            b"bank:user:",
            10,
            None,
            ConsistencyMode::AtLatest,
            &caller,
        )
        .await
        .expect("scan with broad prefix");
    assert_eq!(scan.entries.len(), 1);
    assert_eq!(scan.entries[0].0, b"bank:user:42:balance".to_vec());
}

#[tokio::test]
async fn kv_write_helpers_respect_scope_boundaries() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "private").await.expect("scope");

    db.kv_set("p", "app", b"counter".to_vec(), b"100".to_vec())
        .await
        .expect("write default scope");
    db.kv_set("p", "private", b"counter".to_vec(), b"7".to_vec())
        .await
        .expect("write private scope");

    let app_reader = CallerContext::new("app_reader");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: app_reader.caller_id.clone(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: None,
        },
    }))
    .await
    .expect("grant app read");

    let private_reader = CallerContext::new("private_reader");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: private_reader.caller_id.clone(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("private".into()),
            prefix: None,
        },
    }))
    .await
    .expect("grant private read");

    let app_value = db
        .kv_get(
            "p",
            "app",
            b"counter",
            ConsistencyMode::AtLatest,
            &app_reader,
        )
        .await
        .expect("app read")
        .expect("app key");
    assert_eq!(app_value.value, b"100".to_vec());

    let private_denied = db
        .kv_get(
            "p",
            "private",
            b"counter",
            ConsistencyMode::AtLatest,
            &app_reader,
        )
        .await
        .expect_err("app reader cannot read private scope");
    assert!(matches!(
        private_denied,
        crate::query::error::QueryError::PermissionDenied { .. }
    ));

    let private_value = db
        .kv_get(
            "p",
            "private",
            b"counter",
            ConsistencyMode::AtLatest,
            &private_reader,
        )
        .await
        .expect("private read")
        .expect("private key");
    assert_eq!(private_value.value, b"7".to_vec());
}

#[tokio::test]
async fn kv_set_many_atomic_writes_all_entries_in_one_commit() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let result = db
        .kv_set_many_atomic(
            "p",
            "app",
            vec![
                (b"batch:a".to_vec(), b"1".to_vec()),
                (b"batch:b".to_vec(), b"2".to_vec()),
                (b"batch:c".to_vec(), b"3".to_vec()),
            ],
        )
        .await
        .expect("batch kv set");

    let caller = CallerContext::new("reader");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: caller.caller_id.clone(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: Some(b"batch:".to_vec()),
        },
    }))
    .await
    .expect("grant read");

    for (key, value) in [
        (b"batch:a".as_slice(), b"1".to_vec()),
        (b"batch:b".as_slice(), b"2".to_vec()),
        (b"batch:c".as_slice(), b"3".to_vec()),
    ] {
        let entry = db
            .kv_get("p", "app", key, ConsistencyMode::AtLatest, &caller)
            .await
            .expect("read batch key")
            .expect("batch key exists");
        assert_eq!(entry.value, value);
        assert_eq!(entry.version, result.commit_seq);
    }

    let err = db
        .kv_set_many_atomic("p", "app", Vec::new())
        .await
        .expect_err("empty batch rejected");
    assert!(matches!(err, AedbError::Validation(_)));
}

#[tokio::test]
async fn kv_projection_table_materializes_kv_state() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.enable_kv_projection("p", "app")
        .await
        .expect("enable kv projection");

    db.kv_set("p", "app", b"user:1".to_vec(), b"alice".to_vec())
        .await
        .expect("kv set 1");
    db.kv_set("p", "app", b"user:2".to_vec(), b"bob".to_vec())
        .await
        .expect("kv set 2");

    let mut projected = None;
    for _ in 0..25 {
        let result = db
            .query(
                "p",
                "app",
                Query::select(&["*"])
                    .from(crate::catalog::KV_INDEX_TABLE)
                    .limit(10),
            )
            .await
            .expect("query projection table");
        if result.rows.len() == 2 {
            projected = Some(result.rows);
            break;
        }
        tokio::time::sleep(std::time::Duration::from_millis(20)).await;
    }

    let rows = projected.expect("projection rows");
    let mut saw_user1 = false;
    let mut saw_user2 = false;
    for row in rows {
        let project = row.values.first().expect("project_id");
        let scope = row.values.get(1).expect("scope_id");
        let key = row.values.get(2).expect("key");
        let value = row.values.get(3).expect("value");
        let commit_seq = row.values.get(4).expect("commit_seq");
        assert_eq!(project, &Value::Text("p".into()));
        assert_eq!(scope, &Value::Text("app".into()));
        assert!(matches!(commit_seq, Value::Integer(v) if *v > 0));
        match (key, value) {
            (Value::Blob(k), Value::Blob(v)) if k == b"user:1" && v == b"alice" => {
                saw_user1 = true;
            }
            (Value::Blob(k), Value::Blob(v)) if k == b"user:2" && v == b"bob" => {
                saw_user2 = true;
            }
            other => panic!("unexpected kv projection row: {other:?}"),
        }
    }
    assert!(saw_user1, "missing user:1 projection row");
    assert!(saw_user2, "missing user:2 projection row");
}

#[tokio::test]
async fn kv_projection_table_is_managed_and_read_only() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.enable_kv_projection("p", "app")
        .await
        .expect("enable projection");

    let err = db
        .commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: crate::catalog::KV_INDEX_TABLE.into(),
            primary_key: vec![Value::Blob(b"k".to_vec())],
            row: Row {
                values: vec![
                    Value::Text("p".into()),
                    Value::Text("app".into()),
                    Value::Blob(b"k".to_vec()),
                    Value::Blob(b"v".to_vec()),
                    Value::Integer(1),
                    Value::Timestamp(1),
                ],
            },
        })
        .await
        .expect_err("managed table writes must fail");
    assert!(matches!(err, AedbError::Validation(_)));
}

#[tokio::test]
async fn kv_query_apis_are_scope_aware() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "tenant1").await.expect("scope");

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "tenant1".into(),
        key: b"user:1".to_vec(),
        value: b"alice".to_vec(),
    })
    .await
    .expect("seed kv");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("tenant1".into()),
            prefix: None,
        },
    }))
    .await
    .expect("grant kv read");

    let caller = CallerContext::new("alice");
    let hit = db
        .kv_get(
            "p",
            "tenant1",
            b"user:1",
            ConsistencyMode::AtLatest,
            &caller,
        )
        .await
        .expect("read in granted scope")
        .expect("value exists");
    assert_eq!(hit.value, b"alice".to_vec());

    let denied = db
        .kv_get("p", "app", b"user:1", ConsistencyMode::AtLatest, &caller)
        .await
        .expect_err("read should be denied in ungranted scope");
    assert!(matches!(
        denied,
        crate::query::error::QueryError::PermissionDenied { .. }
    ));
}

#[tokio::test]
async fn scoped_admin_permissions_bound_grant_revoke() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "s1").await.expect("scope s1");
    db.create_scope("p", "s2").await.expect("scope s2");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "owner".into(),
        permission: Permission::ScopeAdmin {
            project_id: "p".into(),
            scope_id: "s1".into(),
        },
    }))
    .await
    .expect("grant scope admin");

    let owner = CallerContext::new("owner");
    db.commit_as(
        owner.clone(),
        Mutation::Ddl(DdlOperation::GrantPermission {
            actor_id: None,
            delegable: false,
            caller_id: "alice".into(),
            permission: Permission::KvRead {
                project_id: "p".into(),
                scope_id: Some("s1".into()),
                prefix: None,
            },
        }),
    )
    .await
    .expect("scope admin can grant in own scope");

    let denied = db
        .commit_as(
            owner.clone(),
            Mutation::Ddl(DdlOperation::GrantPermission {
                actor_id: None,
                delegable: false,
                caller_id: "alice".into(),
                permission: Permission::KvRead {
                    project_id: "p".into(),
                    scope_id: Some("s2".into()),
                    prefix: None,
                },
            }),
        )
        .await
        .expect_err("scope admin cannot grant in other scope");
    assert!(matches!(denied, AedbError::PermissionDenied(_)));

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "proj".into(),
        permission: Permission::ProjectAdmin {
            project_id: "p".into(),
        },
    }))
    .await
    .expect("grant project admin");
    let proj = CallerContext::new("proj");
    db.commit_as(
        proj,
        Mutation::Ddl(DdlOperation::GrantPermission {
            actor_id: None,
            delegable: false,
            caller_id: "alice".into(),
            permission: Permission::KvRead {
                project_id: "p".into(),
                scope_id: Some("s2".into()),
                prefix: None,
            },
        }),
    )
    .await
    .expect("project admin can grant across project scopes");
}

#[tokio::test]
async fn project_owner_and_delegable_grants_control_authz_delegation() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    db.commit(Mutation::Ddl(DdlOperation::CreateProject {
        project_id: "p".into(),
        owner_id: Some("owner".into()),
        if_not_exists: true,
    }))
    .await
    .expect("owner creates project");
    let owner = CallerContext::new("owner");

    let attacker = CallerContext::new("mallory");
    let denied = db
        .commit_as(
            attacker,
            Mutation::Ddl(DdlOperation::GrantPermission {
                caller_id: "alice".into(),
                permission: Permission::KvRead {
                    project_id: "p".into(),
                    scope_id: Some("app".into()),
                    prefix: None,
                },
                actor_id: None,
                delegable: false,
            }),
        )
        .await
        .expect_err("non-owner cannot grant");
    assert!(matches!(denied, AedbError::PermissionDenied(_)));

    db.commit_as(
        owner,
        Mutation::Ddl(DdlOperation::GrantPermission {
            caller_id: "alice".into(),
            permission: Permission::KvRead {
                project_id: "p".into(),
                scope_id: Some("app".into()),
                prefix: None,
            },
            actor_id: None,
            delegable: true,
        }),
    )
    .await
    .expect("owner grants delegable read");
    let (_, catalog, _) = db.executor.snapshot_state().await;
    assert!(catalog.has_delegable_grant(
        "alice",
        &Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: None,
        }
    ));

    let alice = CallerContext::new("alice");
    db.commit_as(
        alice.clone(),
        Mutation::Ddl(DdlOperation::GrantPermission {
            caller_id: "bob".into(),
            permission: Permission::KvRead {
                project_id: "p".into(),
                scope_id: Some("app".into()),
                prefix: None,
            },
            actor_id: None,
            delegable: false,
        }),
    )
    .await
    .expect("delegable grant allows onward delegation");

    let denied = db
        .commit_as(
            alice,
            Mutation::Ddl(DdlOperation::GrantPermission {
                caller_id: "bob".into(),
                permission: Permission::KvWrite {
                    project_id: "p".into(),
                    scope_id: Some("app".into()),
                    prefix: None,
                },
                actor_id: None,
                delegable: false,
            }),
        )
        .await
        .expect_err("delegable grant is permission-specific");
    assert!(matches!(denied, AedbError::PermissionDenied(_)));
}

#[tokio::test]
async fn permission_denied_messages_do_not_leak_table_names() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("prod").await.expect("project");
    create_table(
        &db,
        "prod",
        "app",
        "users",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;

    let err = db
        .commit_as(
            CallerContext::new("mallory"),
            Mutation::Upsert {
                project_id: "prod".into(),
                scope_id: "app".into(),
                table_name: "users".into(),
                primary_key: vec![Value::Integer(1)],
                row: Row::from_values(vec![Value::Integer(1)]),
            },
        )
        .await
        .expect_err("permission denied");
    let rendered = err.to_string();
    assert!(!rendered.contains("users"));
    assert!(!rendered.contains("prod"));
    assert!(!rendered.contains("app"));
}

#[tokio::test]
async fn kv_permission_overlap_revoke_precedence_is_explicit() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "private")
        .await
        .expect("private scope");

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"user:1".to_vec(),
        value: b"alice".to_vec(),
    })
    .await
    .expect("seed app user");
    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"order:1".to_vec(),
        value: b"100".to_vec(),
    })
    .await
    .expect("seed app order");
    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "private".into(),
        key: b"user:1".to_vec(),
        value: b"secret".to_vec(),
    })
    .await
    .expect("seed private user");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: None,
            prefix: None,
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant project wide read");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: Some(b"user:".to_vec()),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant narrow read");
    db.commit(Mutation::Ddl(DdlOperation::RevokePermission {
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: None,
            prefix: None,
        },
        actor_id: None,
    }))
    .await
    .expect("revoke broad read");

    let alice = CallerContext::new("alice");
    let allowed = db
        .kv_get("p", "app", b"user:1", ConsistencyMode::AtLatest, &alice)
        .await
        .expect("narrow grant still applies");
    assert!(allowed.is_some());

    let denied_key = db
        .kv_get("p", "app", b"order:1", ConsistencyMode::AtLatest, &alice)
        .await
        .expect_err("revoke broad grant should deny non-matching key");
    assert!(matches!(
        denied_key,
        crate::query::error::QueryError::PermissionDenied { .. }
    ));

    let denied_scope = db
        .kv_get("p", "private", b"user:1", ConsistencyMode::AtLatest, &alice)
        .await
        .expect_err("narrow app grant should not spill into private scope");
    assert!(matches!(
        denied_scope,
        crate::query::error::QueryError::PermissionDenied { .. }
    ));
}

#[tokio::test]
async fn grant_metadata_tracks_actor_and_system_source() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    db.commit(Mutation::Ddl(DdlOperation::CreateProject {
        project_id: "p".into(),
        owner_id: Some("owner".into()),
        if_not_exists: true,
    }))
    .await
    .expect("project");

    let owner = CallerContext::new("owner");
    db.commit_as(
        owner.clone(),
        Mutation::Ddl(DdlOperation::GrantPermission {
            caller_id: "bob".into(),
            permission: Permission::KvRead {
                project_id: "p".into(),
                scope_id: Some("app".into()),
                prefix: None,
            },
            actor_id: Some("mallory".into()),
            delegable: true,
        }),
    )
    .await
    .expect("owner grant with explicit actor");

    db.commit_as(
        owner,
        Mutation::Ddl(DdlOperation::GrantPermission {
            caller_id: "carol".into(),
            permission: Permission::KvRead {
                project_id: "p".into(),
                scope_id: Some("app".into()),
                prefix: None,
            },
            actor_id: None,
            delegable: false,
        }),
    )
    .await
    .expect("owner grant with implicit actor");

    let (_, catalog, _) = db.executor.snapshot_state().await;
    let bob_meta = catalog
        .permission_grants
        .get(&(
            "bob".to_string(),
            Permission::KvRead {
                project_id: "p".into(),
                scope_id: Some("app".into()),
                prefix: None,
            },
        ))
        .expect("bob metadata");
    assert_eq!(bob_meta.granted_by, "owner");
    assert!(bob_meta.delegable);

    let carol_meta = catalog
        .permission_grants
        .get(&(
            "carol".to_string(),
            Permission::KvRead {
                project_id: "p".into(),
                scope_id: Some("app".into()),
                prefix: None,
            },
        ))
        .expect("carol metadata");
    assert_eq!(carol_meta.granted_by, "owner");
    assert!(!carol_meta.delegable);
}

#[tokio::test]
async fn grant_and_revoke_authorization_matrix_is_enforced() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    db.commit(Mutation::Ddl(DdlOperation::CreateProject {
        project_id: "p".into(),
        owner_id: Some("owner".into()),
        if_not_exists: true,
    }))
    .await
    .expect("project");
    db.create_scope("p", "s1").await.expect("scope");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "s1".into(),
        table_name: "events".into(),
        owner_id: Some("table_owner".into()),
        if_not_exists: false,
        columns: vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "s1".into(),
        table_name: "events".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1)],
        },
    })
    .await
    .expect("seed row");

    let denied = db
        .commit_as(
            CallerContext::new("mallory"),
            Mutation::Ddl(DdlOperation::GrantPermission {
                caller_id: "pa".into(),
                permission: Permission::ProjectAdmin {
                    project_id: "p".into(),
                },
                actor_id: None,
                delegable: false,
            }),
        )
        .await
        .expect_err("non-owner cannot grant project admin");
    assert!(matches!(denied, AedbError::PermissionDenied(_)));

    db.commit_as(
        CallerContext::new("owner"),
        Mutation::Ddl(DdlOperation::GrantPermission {
            caller_id: "pa".into(),
            permission: Permission::ProjectAdmin {
                project_id: "p".into(),
            },
            actor_id: None,
            delegable: false,
        }),
    )
    .await
    .expect("owner grants project admin");

    db.commit_as(
        CallerContext::new("pa"),
        Mutation::Ddl(DdlOperation::GrantPermission {
            caller_id: "sa".into(),
            permission: Permission::ScopeAdmin {
                project_id: "p".into(),
                scope_id: "s1".into(),
            },
            actor_id: None,
            delegable: false,
        }),
    )
    .await
    .expect("project admin grants scope admin");

    let denied = db
        .commit_as(
            CallerContext::new("sa"),
            Mutation::Ddl(DdlOperation::GrantPermission {
                caller_id: "other".into(),
                permission: Permission::ProjectAdmin {
                    project_id: "p".into(),
                },
                actor_id: None,
                delegable: false,
            }),
        )
        .await
        .expect_err("scope admin cannot grant project admin");
    assert!(matches!(denied, AedbError::PermissionDenied(_)));

    db.commit_as(
        CallerContext::new("table_owner"),
        Mutation::Ddl(DdlOperation::GrantPermission {
            caller_id: "reader".into(),
            permission: Permission::TableRead {
                project_id: "p".into(),
                scope_id: "s1".into(),
                table_name: "events".into(),
            },
            actor_id: None,
            delegable: false,
        }),
    )
    .await
    .expect("table owner grants table read");

    let reader = CallerContext::new("reader");
    let query = db
        .query_with_options_as(
            Some(&reader),
            "p",
            "s1",
            Query::select(&["id"]).from("events").limit(10),
            QueryOptions::default(),
        )
        .await
        .expect("reader query");
    assert_eq!(query.rows.len(), 1);

    let denied = db
        .commit_as(
            CallerContext::new("mallory"),
            Mutation::Ddl(DdlOperation::RevokePermission {
                caller_id: "reader".into(),
                permission: Permission::TableRead {
                    project_id: "p".into(),
                    scope_id: "s1".into(),
                    table_name: "events".into(),
                },
                actor_id: None,
            }),
        )
        .await
        .expect_err("non-admin/non-owner cannot revoke");
    assert!(matches!(denied, AedbError::PermissionDenied(_)));

    db.commit_as(
        CallerContext::new("pa"),
        Mutation::Ddl(DdlOperation::RevokePermission {
            caller_id: "reader".into(),
            permission: Permission::TableRead {
                project_id: "p".into(),
                scope_id: "s1".into(),
                table_name: "events".into(),
            },
            actor_id: None,
        }),
    )
    .await
    .expect("project admin revokes table read");

    let denied_query = db
        .query_with_options_as(
            Some(&reader),
            "p",
            "s1",
            Query::select(&["id"]).from("events").limit(10),
            QueryOptions::default(),
        )
        .await
        .expect_err("reader should lose access after revoke");
    assert!(matches!(
        denied_query,
        crate::query::error::QueryError::PermissionDenied { .. }
    ));
}

#[tokio::test]
async fn kv_scan_all_scopes_requires_project_wide_read() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "s1").await.expect("scope s1");
    db.create_scope("p", "s2").await.expect("scope s2");

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "s1".into(),
        key: b"user:1".to_vec(),
        value: b"alice".to_vec(),
    })
    .await
    .expect("seed s1");
    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "s2".into(),
        key: b"user:2".to_vec(),
        value: b"bob".to_vec(),
    })
    .await
    .expect("seed s2");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: None,
            prefix: None,
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant project-wide read");

    let alice = CallerContext::new("alice");
    let entries = db
        .kv_scan_all_scopes("p", b"user:", 10, ConsistencyMode::AtLatest, &alice)
        .await
        .expect("scan all scopes");
    assert_eq!(entries.len(), 2);
    assert!(entries.iter().any(|e| e.scope_id == "s1"));
    assert!(entries.iter().any(|e| e.scope_id == "s2"));

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "bob".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("s1".into()),
            prefix: None,
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant scope-only read");
    let bob = CallerContext::new("bob");
    let denied = db
        .kv_scan_all_scopes("p", b"user:", 10, ConsistencyMode::AtLatest, &bob)
        .await
        .expect_err("scope-only grant cannot scan all scopes");
    assert!(matches!(
        denied,
        crate::query::error::QueryError::PermissionDenied { .. }
    ));
}

#[tokio::test]
async fn ownership_transfer_requires_owner_or_global_admin() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.commit(Mutation::Ddl(DdlOperation::CreateProject {
        project_id: "p".into(),
        owner_id: Some("owner".into()),
        if_not_exists: true,
    }))
    .await
    .expect("create project with owner");

    let mallory = CallerContext::new("mallory");
    let denied = db
        .commit_as(
            mallory,
            Mutation::Ddl(DdlOperation::TransferOwnership {
                resource_type: ResourceType::Project,
                project_id: "p".into(),
                scope_id: None,
                table_name: None,
                new_owner_id: "next".into(),
                actor_id: None,
            }),
        )
        .await
        .expect_err("non-owner transfer must fail");
    assert!(matches!(denied, AedbError::PermissionDenied(_)));

    let owner = CallerContext::new("owner");
    db.commit_as(
        owner,
        Mutation::Ddl(DdlOperation::TransferOwnership {
            resource_type: ResourceType::Project,
            project_id: "p".into(),
            scope_id: None,
            table_name: None,
            new_owner_id: "next".into(),
            actor_id: None,
        }),
    )
    .await
    .expect("owner transfer");

    let (_, catalog, _) = db.executor.snapshot_state().await;
    assert_eq!(
        catalog
            .projects
            .get("p")
            .and_then(|p| p.owner_id.as_deref()),
        Some("next")
    );
}

#[tokio::test]
async fn authz_grant_revoke_events_are_persisted_in_system_audit_table() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.commit(Mutation::Ddl(DdlOperation::CreateProject {
        project_id: "p".into(),
        owner_id: Some("owner".into()),
        if_not_exists: true,
    }))
    .await
    .expect("project");
    let owner = CallerContext::new("owner");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        owner_id: None,
        if_not_exists: false,
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "owner".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("users table");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: None,
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant");
    db.commit(Mutation::Ddl(DdlOperation::RevokePermission {
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: None,
        },
        actor_id: None,
    }))
    .await
    .expect("revoke");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "owner".into(),
        permission: Permission::TableDdl {
            project_id: "p".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant owner ddl");
    db.commit_as(
        owner.clone(),
        Mutation::Ddl(DdlOperation::SetReadPolicy {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            predicate: Expr::Eq("owner".into(), Value::Text("$caller_id".into())),
            actor_id: None,
        }),
    )
    .await
    .expect("set policy");
    db.commit_as(
        owner.clone(),
        Mutation::Ddl(DdlOperation::ClearReadPolicy {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            actor_id: None,
        }),
    )
    .await
    .expect("clear policy");
    db.commit_as(
        owner,
        Mutation::Ddl(DdlOperation::TransferOwnership {
            resource_type: ResourceType::Project,
            project_id: "p".into(),
            scope_id: None,
            table_name: None,
            new_owner_id: "next".into(),
            actor_id: None,
        }),
    )
    .await
    .expect("transfer policy");

    let audit = db
        .query(
            "_system",
            "app",
            Query::select(&["*"]).from("authz_audit").limit(20),
        )
        .await
        .expect("audit query");
    assert!(audit.rows.len() >= 2, "expected at least grant+revoke rows");
    let actions: Vec<String> = audit
        .rows
        .iter()
        .filter_map(|r| match r.values.get(2) {
            Some(Value::Text(v)) => Some(v.to_string()),
            _ => None,
        })
        .collect();
    assert!(actions.iter().any(|a| a == "grant"));
    assert!(actions.iter().any(|a| a == "revoke"));
    assert!(actions.iter().any(|a| a == "set_read_policy"));
    assert!(actions.iter().any(|a| a == "clear_read_policy"));
    assert!(actions.iter().any(|a| a == "transfer_ownership"));

    let owner_actor_actions: Vec<String> = audit
        .rows
        .iter()
        .filter_map(|r| match (r.values.get(2), r.values.get(3)) {
            (Some(Value::Text(action)), Some(Value::Text(actor))) if actor.as_str() == "owner" => {
                Some(action.to_string())
            }
            _ => None,
        })
        .collect();
    assert!(owner_actor_actions.iter().any(|a| a == "set_read_policy"));
    assert!(owner_actor_actions.iter().any(|a| a == "clear_read_policy"));
    assert!(
        owner_actor_actions
            .iter()
            .any(|a| a == "transfer_ownership")
    );
}

#[tokio::test]
async fn read_policies_filter_rows_for_caller_and_can_be_cleared() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        owner_id: None,
        if_not_exists: false,
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "owner".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1), Value::Text("reader".into())],
        },
    })
    .await
    .expect("row 1");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        primary_key: vec![Value::Integer(2)],
        row: Row {
            values: vec![Value::Integer(2), Value::Text("bob".into())],
        },
    })
    .await
    .expect("row 2");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant read");
    db.set_read_policy(
        "p",
        "app",
        "users",
        Expr::Eq("owner".into(), Value::Text("$caller_id".into())),
    )
    .await
    .expect("set read policy");

    let reader = CallerContext::new("reader");
    let filtered = db
        .query_with_options_as(
            Some(&reader),
            "p",
            "app",
            Query::select(&["*"]).from("users").limit(10),
            QueryOptions::default(),
        )
        .await
        .expect("filtered query");
    assert_eq!(filtered.rows.len(), 1);

    db.clear_read_policy("p", "app", "users")
        .await
        .expect("clear read policy");
    let unfiltered = db
        .query_with_options_as(
            Some(&reader),
            "p",
            "app",
            Query::select(&["*"]).from("users").limit(10),
            QueryOptions::default(),
        )
        .await
        .expect("unfiltered query");
    assert_eq!(unfiltered.rows.len(), 2);
}

#[tokio::test]
async fn read_policy_like_patterns_substitute_caller_id() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        owner_id: None,
        if_not_exists: false,
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "owner".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");
    for (id, owner) in [
        (1_i64, "reader-alpha"),
        (2, "reader-beta"),
        (3, "other-alpha"),
    ] {
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row::from_values(vec![Value::Integer(id), Value::Text(owner.into())]),
        })
        .await
        .expect("seed row");
    }
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant read");
    db.set_read_policy(
        "p",
        "app",
        "users",
        Expr::Like("owner".into(), "$caller_id%".into()),
    )
    .await
    .expect("set read policy");

    let rows = db
        .query_with_options_as(
            Some(&CallerContext::new("reader")),
            "p",
            "app",
            Query::select(&["*"]).from("users").limit(10),
            QueryOptions::default(),
        )
        .await
        .expect("filtered query")
        .rows;

    assert_eq!(rows.len(), 2);
}

#[tokio::test]
async fn read_policy_applies_to_joined_tables() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        owner_id: None,
        if_not_exists: false,
        columns: vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("users");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "profiles".into(),
        owner_id: None,
        if_not_exists: false,
        columns: vec![
            ColumnDef {
                name: "user_id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "owner".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["user_id".into()],
    }))
    .await
    .expect("profiles");

    for id in [1, 2] {
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row {
                values: vec![Value::Integer(id)],
            },
        })
        .await
        .expect("seed user");
    }
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "profiles".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1), Value::Text("reader".into())],
        },
    })
    .await
    .expect("profile 1");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "profiles".into(),
        primary_key: vec![Value::Integer(2)],
        row: Row {
            values: vec![Value::Integer(2), Value::Text("bob".into())],
        },
    })
    .await
    .expect("profile 2");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant users read");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "profiles".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant profiles read");
    db.set_read_policy(
        "p",
        "app",
        "profiles",
        Expr::Eq("owner".into(), Value::Text("$caller_id".into())),
    )
    .await
    .expect("set join policy");

    let reader = CallerContext::new("reader");
    let result = db
        .query_with_options_as(
            Some(&reader),
            "p",
            "app",
            Query::select(&["*"])
                .from("users")
                .alias("u")
                .inner_join("profiles", "u.id", "user_id")
                .with_last_join_alias("p"),
            QueryOptions::default(),
        )
        .await
        .expect("join query");
    assert_eq!(result.rows.len(), 1);
}

#[tokio::test]
async fn table_policy_bypass_permission_skips_row_policy_filtering() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        owner_id: None,
        if_not_exists: false,
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "owner".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("users");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1), Value::Text("reader".into())],
        },
    })
    .await
    .expect("seed user 1");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        primary_key: vec![Value::Integer(2)],
        row: Row {
            values: vec![Value::Integer(2), Value::Text("bob".into())],
        },
    })
    .await
    .expect("seed user 2");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant table read");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::PolicyBypass {
            project_id: "p".into(),
            table_name: Some("users".into()),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant policy bypass");
    db.set_read_policy(
        "p",
        "app",
        "users",
        Expr::Eq("owner".into(), Value::Text("$caller_id".into())),
    )
    .await
    .expect("set policy");

    let reader = CallerContext::new("reader");
    let result = db
        .query_with_options_as(
            Some(&reader),
            "p",
            "app",
            Query::select(&["*"]).from("users").limit(10),
            QueryOptions::default(),
        )
        .await
        .expect("query with policy bypass");
    assert_eq!(result.rows.len(), 2);
}

#[tokio::test]
async fn project_policy_bypass_permission_skips_joined_table_policies() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        owner_id: None,
        if_not_exists: false,
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "owner".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("users");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "profiles".into(),
        owner_id: None,
        if_not_exists: false,
        columns: vec![
            ColumnDef {
                name: "user_id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "owner".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["user_id".into()],
    }))
    .await
    .expect("profiles");

    for (id, owner) in [(1, "reader"), (2, "bob")] {
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row {
                values: vec![Value::Integer(id), Value::Text(owner.into())],
            },
        })
        .await
        .expect("seed user");
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "profiles".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row {
                values: vec![Value::Integer(id), Value::Text(owner.into())],
            },
        })
        .await
        .expect("seed profile");
    }

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant users read");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "profiles".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant profiles read");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::PolicyBypass {
            project_id: "p".into(),
            table_name: None,
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant project policy bypass");
    db.set_read_policy(
        "p",
        "app",
        "users",
        Expr::Eq("owner".into(), Value::Text("$caller_id".into())),
    )
    .await
    .expect("set users policy");
    db.set_read_policy(
        "p",
        "app",
        "profiles",
        Expr::Eq("owner".into(), Value::Text("$caller_id".into())),
    )
    .await
    .expect("set profiles policy");

    let reader = CallerContext::new("reader");
    let result = db
        .query_with_options_as(
            Some(&reader),
            "p",
            "app",
            Query::select(&["*"])
                .from("users")
                .alias("u")
                .inner_join("profiles", "u.id", "user_id")
                .with_last_join_alias("pr")
                .limit(10),
            QueryOptions::default(),
        )
        .await
        .expect("query with project policy bypass");
    assert_eq!(result.rows.len(), 2);
}

#[tokio::test]
async fn join_query_rejects_duplicate_aliases_to_prevent_policy_binding_ambiguity() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        owner_id: None,
        if_not_exists: false,
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "owner".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("users");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "profiles".into(),
        owner_id: None,
        if_not_exists: false,
        columns: vec![
            ColumnDef {
                name: "user_id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "owner".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["user_id".into()],
    }))
    .await
    .expect("profiles");

    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1), Value::Text("reader".into())],
        },
    })
    .await
    .expect("seed user");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "profiles".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1), Value::Text("bob".into())],
        },
    })
    .await
    .expect("seed profile");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant users read");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "reader".into(),
        permission: Permission::TableRead {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "profiles".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant profiles read");
    db.set_read_policy(
        "p",
        "app",
        "profiles",
        Expr::Eq("owner".into(), Value::Text("$caller_id".into())),
    )
    .await
    .expect("set policy");

    let reader = CallerContext::new("reader");
    let err = db
        .query_with_options_as(
            Some(&reader),
            "p",
            "app",
            Query::select(&["*"])
                .from("users")
                .alias("u")
                .inner_join("profiles", "u.id", "user_id")
                .with_last_join_alias("u"),
            QueryOptions::default(),
        )
        .await
        .expect_err("duplicate aliases should be rejected");
    assert!(matches!(err, QueryError::InvalidQuery { .. }));
}

#[tokio::test]
async fn production_profile_requires_hmac() {
    let dir = tempdir().expect("temp");
    let invalid = AedbConfig {
        recovery_mode: RecoveryMode::Strict,
        durability_mode: DurabilityMode::Full,
        hash_chain_required: true,
        manifest_hmac_key: None,
        ..AedbConfig::default()
    };
    let err = AedbInstance::open_production(invalid, dir.path())
        .err()
        .expect("must reject missing hmac");
    assert!(matches!(err, AedbError::InvalidConfig { .. }));

    let valid = AedbConfig::production([7u8; 32]);
    AedbInstance::open_production(valid, dir.path()).expect("open production");
}

#[tokio::test]
async fn secure_profile_requires_hardened_storage_settings() {
    let dir = tempdir().expect("temp");
    let weak = AedbConfig::default();
    let err = AedbInstance::open_secure(weak, dir.path())
        .err()
        .expect("must reject weak secure profile");
    assert!(matches!(err, AedbError::InvalidConfig { .. }));

    let hardened = AedbConfig::production([9u8; 32]);
    AedbInstance::open_secure(hardened, dir.path()).expect("open secure");
}

#[tokio::test]
async fn secure_profile_rejects_batch_durability() {
    let dir = tempdir().expect("temp");
    let mut weak = AedbConfig::production([9u8; 32]);
    weak.durability_mode = DurabilityMode::Batch;
    let err = AedbInstance::open_secure(weak, dir.path())
        .err()
        .expect("secure profile must reject batch durability");
    assert!(matches!(err, AedbError::InvalidConfig { .. }));
}

#[tokio::test]
async fn production_profile_rejects_batch_durability() {
    let dir = tempdir().expect("temp");
    let mut weak = AedbConfig::production([7u8; 32]);
    weak.durability_mode = DurabilityMode::Batch;
    let err = AedbInstance::open_production(weak, dir.path())
        .err()
        .expect("production profile must reject batch durability");
    assert!(matches!(err, AedbError::InvalidConfig { .. }));
}

#[tokio::test]
async fn production_profile_requires_disk_backed_kv_payloads() {
    let dir = tempdir().expect("temp");
    let mut weak = AedbConfig::production([7u8; 32]);
    weak.storage_mode = StorageMode::InMemory;
    let err = AedbInstance::open_production(weak, dir.path())
        .err()
        .expect("production profile must reject in-memory storage");
    assert!(matches!(err, AedbError::InvalidConfig { .. }));

    let dir = tempdir().expect("temp");
    let mut weak = AedbConfig::production([8u8; 32]);
    weak.persistent_value_inline_threshold_bytes = 1;
    let err = AedbInstance::open_production(weak, dir.path())
        .err()
        .expect("production profile must reject inline KV payloads");
    assert!(matches!(err, AedbError::InvalidConfig { .. }));

    let cfg = AedbConfig::production([9u8; 32]);
    assert_eq!(cfg.storage_mode, StorageMode::DiskBacked);
    assert_eq!(cfg.persistent_value_inline_threshold_bytes, 0);
}

#[tokio::test]
async fn secure_profile_rejects_short_hmac_key() {
    let dir = tempdir().expect("temp");
    let weak = AedbConfig::default()
        .with_hmac_key(vec![1, 2, 3, 4, 5, 6, 7, 8])
        .with_checkpoint_key([3u8; 32]);
    let err = AedbInstance::open_secure(weak, dir.path())
        .err()
        .expect("short hmac key must be rejected");
    assert!(matches!(err, AedbError::InvalidConfig { .. }));
}

#[test]
fn arcana_profile_rejects_short_hmac_key() {
    let weak = AedbConfig::default().with_hmac_key(vec![9u8; 16]);
    let err = crate::lib_helpers::validate_arcana_config(&weak)
        .expect_err("short hmac key must be rejected");
    assert!(matches!(err, AedbError::InvalidConfig { .. }));
}

#[test]
fn low_latency_profile_uses_batch_durability_with_strict_recovery() {
    let cfg = AedbConfig::low_latency([5u8; 32]);
    assert_eq!(cfg.durability_mode, DurabilityMode::Batch);
    assert_eq!(cfg.recovery_mode, RecoveryMode::Strict);
    assert!(cfg.hash_chain_required);
    assert!(cfg.batch_interval_ms > 0);
    assert!(cfg.batch_max_bytes > 0);
    assert!(cfg.manifest_hmac_key.is_some());
    assert_eq!(cfg.storage_mode, StorageMode::DiskBacked);
    assert_eq!(cfg.persistent_value_inline_threshold_bytes, 0);
}

#[test]
fn checkpoint_compression_level_is_validated() {
    let cfg = AedbConfig {
        checkpoint_compression_level: 23,
        ..AedbConfig::default()
    };
    let err = crate::lib_helpers::validate_config(&cfg)
        .expect_err("out-of-range compression level must be rejected");
    assert!(matches!(err, AedbError::InvalidConfig { .. }));

    let cfg = AedbConfig {
        checkpoint_compression_level: 1,
        ..AedbConfig::default()
    };
    crate::lib_helpers::validate_config(&cfg).expect("valid compression level");
}

#[tokio::test]
async fn envelope_mutation_count_cap_rejects_oversized_envelope() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_mutations_per_envelope: 4,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let too_many: Vec<Mutation> = (0..5u8)
        .map(|i| Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: vec![b'k', i],
            value: vec![i],
        })
        .collect();
    let err = db
        .commit_envelope(TransactionEnvelope {
            caller: None,
            idempotency_key: None,
            write_class: WriteClass::Standard,
            assertions: Vec::new(),
            read_set: crate::commit::tx::ReadSet::default(),
            write_intent: WriteIntent { mutations: too_many },
            base_seq: 0,
        })
        .await
        .expect_err("envelope mutation count cap must reject");
    match err {
        AedbError::Validation(msg) => {
            assert!(
                msg.contains("max_mutations_per_envelope") && msg.contains("mutations=5"),
                "informative limit message expected, got: {msg}"
            );
        }
        other => panic!("expected Validation error, got: {other:?}"),
    }

    let ok_batch: Vec<Mutation> = (0..4u8)
        .map(|i| Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: vec![b'k', i],
            value: vec![i],
        })
        .collect();
    db.commit_envelope(TransactionEnvelope {
        caller: None,
        idempotency_key: None,
        write_class: WriteClass::Standard,
        assertions: Vec::new(),
        read_set: crate::commit::tx::ReadSet::default(),
        write_intent: WriteIntent { mutations: ok_batch },
        base_seq: 0,
    })
    .await
    .expect("envelope within mutation cap commits");
}

#[tokio::test]
async fn envelope_assertion_count_cap_rejects_oversized_envelope() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_read_assertions_per_envelope: 2,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let too_many: Vec<ReadAssertion> = (0..3u8)
        .map(|i| ReadAssertion::KeyExists {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: vec![b'k', i],
            expected: false,
        })
        .collect();
    let err = db
        .commit_envelope(TransactionEnvelope {
            caller: None,
            idempotency_key: None,
            write_class: WriteClass::Standard,
            assertions: too_many,
            read_set: crate::commit::tx::ReadSet::default(),
            write_intent: WriteIntent {
                mutations: vec![Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"k".to_vec(),
                    value: b"v".to_vec(),
                }],
            },
            base_seq: 0,
        })
        .await
        .expect_err("envelope assertion count cap must reject");
    match err {
        AedbError::Validation(msg) => {
            assert!(
                msg.contains("max_read_assertions_per_envelope")
                    && msg.contains("assertions=3"),
                "informative limit message expected, got: {msg}"
            );
        }
        other => panic!("expected Validation error, got: {other:?}"),
    }
}

#[tokio::test]
async fn envelope_read_bytes_budget_passes_for_tiny_envelope() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_read_bytes_per_envelope: 1024,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.commit_envelope(TransactionEnvelope {
        caller: None,
        idempotency_key: None,
        write_class: WriteClass::Standard,
        assertions: vec![ReadAssertion::KeyExists {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: b"absent".to_vec(),
            expected: false,
        }],
        read_set: crate::commit::tx::ReadSet::default(),
        write_intent: WriteIntent {
            mutations: vec![Mutation::KvSet {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: b"k".to_vec(),
                value: b"v".to_vec(),
            }],
        },
        base_seq: 0,
    })
    .await
    .expect("tiny envelope must fit budget");
}

#[tokio::test]
async fn envelope_read_bytes_budget_rejects_large_scan() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_scan_rows: 1_000_000,
        max_read_bytes_per_envelope: 512,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "items".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "blob".into(),
                col_type: ColumnType::Blob,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("create table");

    for i in 0..64i64 {
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            primary_key: vec![Value::Integer(i)],
            row: Row::from_values(vec![Value::Integer(i), Value::Blob(vec![0xAB; 128])]),
        })
        .await
        .expect("seed row");
    }

    let err = db
        .commit_envelope(TransactionEnvelope {
            caller: None,
            idempotency_key: None,
            write_class: WriteClass::Standard,
            assertions: vec![ReadAssertion::CountCompare {
                project_id: "p".into(),
                scope_id: "app".into(),
                table_name: "items".into(),
                filter: None,
                op: crate::commit::validation::CompareOp::Gte,
                threshold: 0,
            }],
            read_set: crate::commit::tx::ReadSet::default(),
            write_intent: WriteIntent {
                mutations: vec![Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"_".to_vec(),
                    value: b"_".to_vec(),
                }],
            },
            base_seq: 0,
        })
        .await
        .expect_err("aggregate read byte budget must reject large scan");
    match err {
        AedbError::Validation(msg) => {
            assert!(
                msg.contains("max_read_bytes_per_envelope"),
                "informative limit message expected, got: {msg}"
            );
        }
        other => panic!("expected Validation error, got: {other:?}"),
    }
}

#[tokio::test]
async fn disk_backed_kv_values_spill_and_recover_after_reopen() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        storage_mode: StorageMode::DiskBacked,
        persistent_value_inline_threshold_bytes: 32,
        max_kv_value_bytes: 256 * 1024,
        max_transaction_bytes: 512 * 1024,
        max_memory_estimate_bytes: 8 * 1024,
        ..AedbConfig::default()
    };
    let value: Vec<u8> = (0..128 * 1024).map(|i| (i % 251) as u8).collect();

    let db = AedbInstance::open(config.clone(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.kv_set("p", "app", b"blob".to_vec(), value.clone())
        .await
        .expect("set large value");

    let memory_estimate = db.estimated_memory_bytes().await;
    assert!(
        memory_estimate < value.len() / 4,
        "large value should not remain inline in keyspace memory estimate: {memory_estimate}"
    );
    let metrics = db.operational_metrics().await;
    assert!(
        metrics.persistent_value_store_bytes > value.len() as u64,
        "value store metrics should include spilled payload bytes"
    );
    assert_eq!(
        metrics.persistent_value_hot_cache_capacity_bytes,
        config.persistent_value_hot_cache_bytes
    );
    assert!(
        metrics.persistent_value_hot_cache_bytes
            <= metrics.persistent_value_hot_cache_capacity_bytes
    );
    let value_file = dir.path().join("values.aedbdat");
    assert!(
        fs::metadata(&value_file)
            .expect("value store metadata")
            .len()
            > value.len() as u64,
        "value store should contain spilled payload bytes"
    );

    let got = db
        .kv_get_no_auth("p", "app", b"blob", ConsistencyMode::AtLatest)
        .await
        .expect("get")
        .expect("value");
    assert_eq!(got.value, value);
    db.shutdown().await.expect("shutdown");
    drop(db);

    let reopened = AedbInstance::open(config, dir.path()).expect("reopen");
    let recovered = reopened
        .kv_get_no_auth("p", "app", b"blob", ConsistencyMode::AtLatest)
        .await
        .expect("recovered get")
        .expect("recovered value");
    assert_eq!(recovered.value, value);
    reopened.shutdown().await.expect("shutdown reopened");
}

#[tokio::test]
async fn production_profile_spills_all_kv_payloads_by_default() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig::production([7u8; 32]);
    let db = AedbInstance::open(config.clone(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.kv_set("p", "app", b"tiny".to_vec(), b"x".to_vec())
        .await
        .expect("set tiny value");

    let metrics = db.operational_metrics().await;
    assert!(
        metrics.persistent_value_store_bytes > 8,
        "production config should spill even tiny KV payloads to values.aedbdat"
    );
    let got = db
        .kv_get_no_auth("p", "app", b"tiny", ConsistencyMode::AtLatest)
        .await
        .expect("get")
        .expect("value");
    assert_eq!(got.value, b"x");
    db.shutdown().await.expect("shutdown");
    drop(db);

    let reopened = AedbInstance::open(config, dir.path()).expect("reopen");
    let recovered = reopened
        .kv_get_no_auth("p", "app", b"tiny", ConsistencyMode::AtLatest)
        .await
        .expect("recovered get")
        .expect("recovered value");
    assert_eq!(recovered.value, b"x");
    reopened.shutdown().await.expect("shutdown reopened");
}

#[tokio::test]
async fn unused_kv_segment_reclaim_keeps_live_disk_state() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        storage_mode: StorageMode::DiskBacked,
        persistent_value_inline_threshold_bytes: 0,
        persistent_value_hot_cache_bytes: 0,
        kv_segment_block_cache_bytes: 16 * 1024,
        max_memory_estimate_bytes: 10 * 1024,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    for generation_number in 0..5u8 {
        let entries = (0..512u16)
            .map(|entry_number| {
                (
                    format!("k{entry_number:04}").into_bytes(),
                    vec![generation_number; 32],
                )
            })
            .collect::<Vec<_>>();
        db.kv_set_many_atomic("p", "app", entries)
            .await
            .expect("set generation");
    }
    let read_tx = db
        .begin_read_tx(ConsistencyMode::AtLatest)
        .await
        .expect("read tx");
    let read_tx_seq = read_tx.snapshot_seq();
    for generation_number in 5..10u8 {
        let entries = (0..512u16)
            .map(|entry_number| {
                (
                    format!("k{entry_number:04}").into_bytes(),
                    vec![generation_number; 32],
                )
            })
            .collect::<Vec<_>>();
        db.kv_set_many_atomic("p", "app", entries)
            .await
            .expect("set later generation");
    }

    let segment_dir = dir.path().join("kv_segments");
    let before_reclaim = fs::read_dir(&segment_dir)
        .expect("segment dir")
        .filter_map(|entry| entry.ok())
        .filter(|entry| entry.file_name().to_string_lossy().ends_with(".aedbkv"))
        .count();
    assert!(before_reclaim > 1);

    let reclaimed = db
        .reclaim_unused_kv_segments()
        .await
        .expect("reclaim unused segments");
    assert!(reclaimed > 0);
    let after_reclaim_with_read_tx = fs::read_dir(&segment_dir)
        .expect("segment dir")
        .filter_map(|entry| entry.ok())
        .filter(|entry| entry.file_name().to_string_lossy().ends_with(".aedbkv"))
        .count();
    assert!(after_reclaim_with_read_tx < before_reclaim);
    let old_key = b"k0000".to_vec();
    let old_value = db
        .kv_get_no_auth("p", "app", &old_key, ConsistencyMode::AtSeq(read_tx_seq))
        .await
        .expect("old read")
        .expect("old value");
    assert_eq!(old_value.value, vec![4u8; 32]);
    drop(read_tx);
    let reclaimed_after_drop = db
        .reclaim_unused_kv_segments()
        .await
        .expect("reclaim after read tx");
    assert!(reclaimed_after_drop > 0);
    let after_reclaim = fs::read_dir(&segment_dir)
        .expect("segment dir")
        .filter_map(|entry| entry.ok())
        .filter(|entry| entry.file_name().to_string_lossy().ends_with(".aedbkv"))
        .count();
    assert!(after_reclaim < after_reclaim_with_read_tx);

    for entry_number in [0u16, 1, 127, 255, 511] {
        let key = format!("k{entry_number:04}").into_bytes();
        let got = db
            .kv_get_no_auth("p", "app", &key, ConsistencyMode::AtLatest)
            .await
            .expect("read after reclaim")
            .expect("value");
        assert_eq!(got.value, vec![9u8; 32]);
    }
}

#[tokio::test]
async fn disk_backed_small_kv_values_spill_under_memory_pressure() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        storage_mode: StorageMode::DiskBacked,
        persistent_value_inline_threshold_bytes: 1024,
        persistent_value_hot_cache_bytes: 0,
        max_kv_value_bytes: 1024,
        max_transaction_bytes: 4096,
        max_memory_estimate_bytes: 2048,
        ..AedbConfig::default()
    };

    let db = AedbInstance::open(config.clone(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    for i in 0..32u8 {
        db.kv_set(
            "p",
            "app",
            format!("small:{i:02}").into_bytes(),
            vec![i; 256],
        )
        .await
        .expect("set small value");
    }

    let memory_estimate = db.estimated_memory_bytes().await;
    assert!(
        memory_estimate <= config.max_memory_estimate_bytes,
        "small KV payloads should spill before memory guard rejects: {memory_estimate}"
    );
    let metrics = db.operational_metrics().await;
    assert!(
        metrics.persistent_value_store_bytes > 8 + 16 * 256,
        "persistent value store should contain pressure-spilled small payloads"
    );
    for i in 0..32u8 {
        let key = format!("small:{i:02}");
        let got = db
            .kv_get_no_auth("p", "app", key.as_bytes(), ConsistencyMode::AtLatest)
            .await
            .expect("get")
            .expect("value");
        assert_eq!(got.value, vec![i; 256]);
    }
    db.shutdown().await.expect("shutdown");
    drop(db);

    let reopened = AedbInstance::open(config.clone(), dir.path()).expect("reopen");
    let recovered_memory_estimate = reopened.estimated_memory_bytes().await;
    assert!(
        recovered_memory_estimate <= config.max_memory_estimate_bytes,
        "recovery should re-spill small KV payloads under memory target: {recovered_memory_estimate}"
    );
    for i in 0..32u8 {
        let key = format!("small:{i:02}");
        let got = reopened
            .kv_get_no_auth("p", "app", key.as_bytes(), ConsistencyMode::AtLatest)
            .await
            .expect("recovered get")
            .expect("recovered value");
        assert_eq!(got.value, vec![i; 256]);
    }
    reopened.shutdown().await.expect("shutdown reopened");
}

#[tokio::test]
async fn disk_backed_kv_spilled_versions_remain_snapshot_consistent() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        storage_mode: StorageMode::DiskBacked,
        persistent_value_inline_threshold_bytes: 16,
        persistent_value_hot_cache_bytes: 0,
        max_kv_value_bytes: 256 * 1024,
        max_transaction_bytes: 512 * 1024,
        ..AedbConfig::default()
    };
    let first_value: Vec<u8> = (0..96 * 1024).map(|i| (i % 251) as u8).collect();
    let second_value: Vec<u8> = (0..96 * 1024).map(|i| 255 - (i % 251) as u8).collect();

    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    let first = db
        .kv_set("p", "app", b"blob".to_vec(), first_value.clone())
        .await
        .expect("first set");
    let second = db
        .kv_set("p", "app", b"blob".to_vec(), second_value.clone())
        .await
        .expect("second set");

    let at_first = db
        .kv_get_no_auth(
            "p",
            "app",
            b"blob",
            ConsistencyMode::AtSeq(first.commit_seq),
        )
        .await
        .expect("get first snapshot")
        .expect("first snapshot value");
    assert_eq!(at_first.value, first_value);

    let at_second = db
        .kv_get_no_auth(
            "p",
            "app",
            b"blob",
            ConsistencyMode::AtSeq(second.commit_seq),
        )
        .await
        .expect("get second snapshot")
        .expect("second snapshot value");
    assert_eq!(at_second.value, second_value);
    db.shutdown().await.expect("shutdown");
}

#[tokio::test]
async fn disk_backed_checkpoint_is_self_contained_when_value_file_is_missing() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        storage_mode: StorageMode::DiskBacked,
        persistent_value_inline_threshold_bytes: 16,
        persistent_value_hot_cache_bytes: 0,
        max_kv_value_bytes: 256 * 1024,
        max_transaction_bytes: 512 * 1024,
        ..AedbConfig::default()
    };
    let value: Vec<u8> = (0..128 * 1024).map(|i| (i % 199) as u8).collect();

    let db = AedbInstance::open(config.clone(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.kv_set("p", "app", b"blob".to_vec(), value.clone())
        .await
        .expect("set large value");
    db.checkpoint_now().await.expect("checkpoint");
    db.shutdown().await.expect("shutdown");
    drop(db);

    fs::remove_file(dir.path().join("values.aedbdat")).expect("remove value store");

    let reopened = AedbInstance::open(config, dir.path()).expect("reopen from checkpoint");
    let recovered = reopened
        .kv_get_no_auth("p", "app", b"blob", ConsistencyMode::AtLatest)
        .await
        .expect("recovered get")
        .expect("recovered value");
    assert_eq!(recovered.value, value);
    reopened.shutdown().await.expect("shutdown reopened");
}

#[tokio::test]
async fn secure_mode_requires_authenticated_apis() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open_production(AedbConfig::production([7u8; 32]), dir.path())
        .expect("open secure");

    let err = db
        .commit(Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "p".into(),
        }))
        .await
        .expect_err("anonymous commit must fail");
    assert!(matches!(err, AedbError::PermissionDenied(_)));

    db.commit_as(
        CallerContext::system_internal(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "p".into(),
        }),
    )
    .await
    .expect("system commit");

    let spoof_err = db
        .commit_as(
            CallerContext::new(SYSTEM_CALLER_ID),
            Mutation::Ddl(DdlOperation::CreateProject {
                owner_id: None,
                if_not_exists: true,
                project_id: "q".into(),
            }),
        )
        .await
        .expect_err("spoofed system caller must fail");
    assert!(matches!(spoof_err, AedbError::PermissionDenied(_)));

    let qerr = db
        .query("p", "app", Query::select(&["*"]).from("users").limit(1))
        .await
        .expect_err("anonymous query must fail");
    assert!(matches!(qerr, QueryError::PermissionDenied { .. }));

    let qerr_direct = db
        .query_with_options_as(
            None,
            "p",
            "app",
            Query::select(&["*"]).from("users").limit(1),
            QueryOptions::default(),
        )
        .await
        .expect_err("anonymous query_with_options_as must fail");
    assert!(matches!(qerr_direct, QueryError::PermissionDenied { .. }));

    let explain_err = db
        .explain_query_as(
            None,
            "p",
            "app",
            Query::select(&["*"]).from("users").limit(1),
            QueryOptions::default(),
        )
        .await
        .expect_err("anonymous explain_query_as must fail");
    assert!(matches!(explain_err, QueryError::PermissionDenied { .. }));
}

#[tokio::test]
async fn commit_as_rejects_empty_caller_id() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    let err = db
        .commit_as(
            CallerContext::new("   "),
            Mutation::Ddl(DdlOperation::CreateProject {
                owner_id: None,
                if_not_exists: true,
                project_id: "p".into(),
            }),
        )
        .await
        .expect_err("empty caller id should be rejected");
    assert!(matches!(err, AedbError::PermissionDenied(_)));
}

#[tokio::test]
async fn query_as_rejects_empty_caller_id() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    let caller = CallerContext::new("");
    let err = db
        .query_with_options_as(
            Some(&caller),
            "p",
            "app",
            Query::select(&["*"]).from("authz_audit").limit(1),
            QueryOptions::default(),
        )
        .await
        .expect_err("empty caller id should be rejected");
    assert!(matches!(err, QueryError::PermissionDenied { .. }));
}

#[tokio::test]
async fn query_with_options_as_rejects_reserved_system_caller() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open_production(AedbConfig::production([8u8; 32]), dir.path())
        .expect("open secure");
    db.commit_as(
        CallerContext::system_internal(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "p".into(),
        }),
    )
    .await
    .expect("create project");

    let caller = CallerContext::new("system");
    let err = db
        .query_with_options_as(
            Some(&caller),
            "p",
            "app",
            Query::select(&["*"]).from("__system_authz"),
            QueryOptions::default(),
        )
        .await
        .expect_err("reserved system caller should be rejected");
    assert!(matches!(err, QueryError::PermissionDenied { .. }));
}

#[tokio::test]
async fn query_no_auth_in_secure_mode_returns_structured_error() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open_production(AedbConfig::production([6u8; 32]), dir.path())
        .expect("open secure");
    let err = db
        .query_no_auth(
            "p",
            "app",
            Query::select(&["*"]).from("__system_authz"),
            QueryOptions::default(),
        )
        .await
        .expect_err("secure mode should reject query_no_auth");
    assert!(matches!(err, QueryError::PermissionDenied { .. }));
}

#[tokio::test]
async fn kv_no_auth_apis_in_secure_mode_return_structured_error() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open_production(AedbConfig::production([4u8; 32]), dir.path())
        .expect("open secure");

    let get_err = db
        .kv_get_no_auth("p", "app", b"k", ConsistencyMode::AtLatest)
        .await
        .expect_err("secure mode should reject kv_get_no_auth");
    assert!(matches!(get_err, QueryError::PermissionDenied { .. }));

    let scan_err = db
        .kv_scan_prefix_no_auth("p", "app", b"k", 10, ConsistencyMode::AtLatest)
        .await
        .expect_err("secure mode should reject kv_scan_prefix_no_auth");
    assert!(matches!(scan_err, QueryError::PermissionDenied { .. }));
}

#[tokio::test]
async fn postflight_assertions_require_read_permission_in_secure_mode() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open_production(AedbConfig::production([8u8; 32]), dir.path())
        .expect("open secure");
    let system = CallerContext::system_internal();
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "p".into(),
        }),
    )
    .await
    .expect("create project");
    db.commit_as(
        system,
        Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: b"secret".to_vec(),
            value: b"not-for-mallory".to_vec(),
        },
    )
    .await
    .expect("seed secret");

    let err = db
        .commit_as(
            CallerContext::new("mallory"),
            Mutation::PostflightCheck {
                assertions: vec![ReadAssertion::KeyCompare {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"secret".to_vec(),
                    op: crate::commit::validation::CompareOp::Eq,
                    threshold: b"guess".to_vec(),
                }],
            },
        )
        .await
        .expect_err("postflight assertion without read permission must fail before evaluation");
    assert!(matches!(err, AedbError::PermissionDenied(_)));
}

#[tokio::test]
async fn envelope_assertions_require_read_permission_in_secure_mode() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open_production(AedbConfig::production([12u8; 32]), dir.path())
        .expect("open secure");
    let system = CallerContext::system_internal();
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "p".into(),
        }),
    )
    .await
    .expect("create project");
    db.commit_as(
        system,
        Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: b"secret".to_vec(),
            value: b"not-for-mallory".to_vec(),
        },
    )
    .await
    .expect("seed secret");

    let err = db
        .commit_envelope(TransactionEnvelope {
            caller: Some(CallerContext::new("mallory")),
            idempotency_key: None,
            write_class: WriteClass::Standard,
            assertions: vec![ReadAssertion::KeyCompare {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: b"secret".to_vec(),
                op: crate::commit::validation::CompareOp::Eq,
                threshold: b"guess".to_vec(),
            }],
            read_set: crate::commit::tx::ReadSet::default(),
            write_intent: WriteIntent {
                mutations: Vec::new(),
            },
            base_seq: 0,
        })
        .await
        .expect_err("envelope assertion without read permission must fail before evaluation");
    assert!(matches!(err, AedbError::PermissionDenied(_)));
}

#[tokio::test]
async fn async_index_query_requires_table_read_permission() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open_production(AedbConfig::production([10u8; 32]), dir.path())
        .expect("open secure");
    let system = CallerContext::system_internal();
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "p".into(),
        }),
    )
    .await
    .expect("create project");
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateScope {
            project_id: "p".into(),
            scope_id: "app".into(),
            owner_id: None,
            if_not_exists: true,
        }),
    )
    .await
    .expect("create scope");
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateTable {
            owner_id: None,
            if_not_exists: false,
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            columns: vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "name".into(),
                    col_type: ColumnType::Text,
                    nullable: false,
                },
            ],
            primary_key: vec!["id".into()],
        }),
    )
    .await
    .expect("create table");
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateAsyncIndex {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            index_name: "users_view".into(),
            if_not_exists: false,
            projected_columns: vec!["id".into(), "name".into()],
        }),
    )
    .await
    .expect("create async index");
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::GrantPermission {
            caller_id: "alice".into(),
            permission: Permission::IndexRead {
                project_id: "p".into(),
                scope_id: "app".into(),
                table_name: "users".into(),
                index_name: "users_view".into(),
            },
            actor_id: Some("system".into()),
            delegable: false,
        }),
    )
    .await
    .expect("grant index read");

    let err = db
        .query_with_options_as(
            Some(&CallerContext::new("alice")),
            "p",
            "app",
            Query::select(&["*"]).from("users"),
            QueryOptions {
                async_index: Some("users_view".into()),
                allow_full_scan: true,
                ..QueryOptions::default()
            },
        )
        .await
        .expect_err("index read alone must not disclose async projection rows");
    assert!(matches!(err, QueryError::PermissionDenied { .. }));
}

#[tokio::test]
async fn event_stream_and_processor_lag_as_require_explicit_permissions() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open_production(AedbConfig::production([3u8; 32]), dir.path())
        .expect("open secure");

    let caller = CallerContext::new("reader_1");
    let stream_err = db
        .read_event_stream_as(&caller, None, 0, 10, ConsistencyMode::AtLatest)
        .await
        .expect_err("event stream read without permission must fail");
    assert!(matches!(stream_err, AedbError::PermissionDenied(_)));

    let lag_err = db
        .reactive_processor_lag_as(&caller, "points_processor", ConsistencyMode::AtLatest)
        .await
        .expect_err("reactive processor lag without permission must fail");
    assert!(matches!(lag_err, AedbError::PermissionDenied(_)));
}

#[tokio::test]
async fn existence_and_introspection_apis_report_catalog_state() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    db.create_project("p").await.expect("project");
    db.create_scope("p", "s1").await.expect("scope");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "s1".into(),
        table_name: "users".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");
    db.commit(Mutation::Ddl(DdlOperation::CreateIndex {
        project_id: "p".into(),
        scope_id: "s1".into(),
        table_name: "users".into(),
        index_name: "idx_users_name".into(),
        if_not_exists: false,
        columns: vec!["name".into()],
        index_type: crate::catalog::schema::IndexType::BTree,
        partial_filter: None,
    }))
    .await
    .expect("index");

    assert!(db.project_exists("p").await.expect("project_exists"));
    assert!(db.scope_exists("p", "s1").await.expect("scope_exists"));
    assert!(
        db.table_exists("p", "s1", "users")
            .await
            .expect("table_exists")
    );
    assert!(
        db.index_exists("p", "s1", "users", "idx_users_name")
            .await
            .expect("index_exists")
    );

    let projects = db.list_projects().await.expect("list projects");
    assert!(projects.iter().any(|p| p.project_id == "p"));

    let scopes = db.list_scopes_info("p").await.expect("list scopes");
    let scope = scopes
        .iter()
        .find(|s| s.scope_id == "s1")
        .expect("scope info");
    assert_eq!(scope.table_count, 1);

    let tables = db.list_tables_info("p", "s1").await.expect("list tables");
    let table = tables
        .iter()
        .find(|t| t.table_name == "users")
        .expect("table info");
    assert_eq!(table.column_count, 2);
    assert_eq!(table.index_count, 1);
}

#[tokio::test]
async fn no_auth_read_apis_work_in_non_secure_mode() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "cfg".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("create cfg table");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "cfg".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1), Value::Text("bootstrap".into())],
        },
    })
    .await
    .expect("upsert cfg row");
    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"config:bootstrap".to_vec(),
        value: b"v1".to_vec(),
    })
    .await
    .expect("set kv");

    let got = db
        .kv_get_no_auth("p", "app", b"config:bootstrap", ConsistencyMode::AtLatest)
        .await
        .expect("kv_get_no_auth")
        .expect("entry");
    assert_eq!(got.value, b"v1".to_vec());

    let scanned = db
        .kv_scan_prefix_no_auth("p", "app", b"config:", 10, ConsistencyMode::AtLatest)
        .await
        .expect("scan no auth");
    assert_eq!(scanned.len(), 1);

    let query_res = db
        .query_no_auth(
            "p",
            "app",
            Query::select(&["id", "name"]).from("cfg").limit(10),
            QueryOptions::default(),
        )
        .await
        .expect("query_no_auth");
    assert_eq!(query_res.rows.len(), 1);
}

#[tokio::test]
async fn ddl_if_not_exists_is_idempotent_and_reports_applied() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    let first = db
        .commit_ddl(DdlOperation::CreateProject {
            owner_id: None,
            project_id: "arcana".into(),
            if_not_exists: true,
        })
        .await
        .expect("create project first");
    assert!(first.applied);

    let second = db
        .commit_ddl(DdlOperation::CreateProject {
            owner_id: None,
            project_id: "arcana".into(),
            if_not_exists: true,
        })
        .await
        .expect("create project second");
    assert!(!second.applied);

    let scope_first = db
        .commit_ddl(DdlOperation::CreateScope {
            owner_id: None,
            project_id: "arcana".into(),
            scope_id: "app".into(),
            if_not_exists: true,
        })
        .await
        .expect("create scope first");
    assert!(!scope_first.applied, "default app scope already exists");

    let table_first = db
        .commit_ddl(DdlOperation::CreateTable {
            owner_id: None,
            if_not_exists: true,
            project_id: "arcana".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            columns: vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "name".into(),
                    col_type: ColumnType::Text,
                    nullable: false,
                },
            ],
            primary_key: vec!["id".into()],
        })
        .await
        .expect("create table first");
    assert!(table_first.applied);

    let table_second = db
        .commit_ddl(DdlOperation::CreateTable {
            owner_id: None,
            if_not_exists: true,
            project_id: "arcana".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            columns: vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "name".into(),
                    col_type: ColumnType::Text,
                    nullable: false,
                },
            ],
            primary_key: vec!["id".into()],
        })
        .await
        .expect("create table second");
    assert!(!table_second.applied);

    let err = db
        .commit(Mutation::Ddl(DdlOperation::CreateTable {
            owner_id: None,
            if_not_exists: false,
            project_id: "arcana".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            columns: vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "name".into(),
                    col_type: ColumnType::Text,
                    nullable: false,
                },
            ],
            primary_key: vec!["id".into()],
        }))
        .await
        .expect_err("duplicate create should error without if_not_exists");
    assert!(matches!(
        err,
        AedbError::AlreadyExists {
            resource_type: ErrorResourceType::Table,
            ..
        }
    ));

    let drop_noop = db
        .commit_ddl(DdlOperation::DropTable {
            project_id: "arcana".into(),
            scope_id: "app".into(),
            table_name: "does_not_exist".into(),
            if_exists: true,
        })
        .await
        .expect("drop noop with if_exists");
    assert!(!drop_noop.applied);

    let drop_err = db
        .commit(Mutation::Ddl(DdlOperation::DropTable {
            project_id: "arcana".into(),
            scope_id: "app".into(),
            table_name: "does_not_exist".into(),
            if_exists: false,
        }))
        .await
        .expect_err("drop missing should error when if_exists=false");
    assert!(matches!(
        drop_err,
        AedbError::NotFound {
            resource_type: ErrorResourceType::Table,
            ..
        }
    ));
}

#[tokio::test]
async fn commit_ddl_batch_is_atomic_and_reports_per_op_results() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");

    let failed = db
        .commit_ddl_batch(vec![
            DdlOperation::CreateTable {
                owner_id: None,
                if_not_exists: false,
                project_id: "arcana".into(),
                scope_id: "app".into(),
                table_name: "users".into(),
                columns: vec![
                    ColumnDef {
                        name: "id".into(),
                        col_type: ColumnType::Integer,
                        nullable: false,
                    },
                    ColumnDef {
                        name: "name".into(),
                        col_type: ColumnType::Text,
                        nullable: false,
                    },
                ],
                primary_key: vec!["id".into()],
            },
            DdlOperation::CreateTable {
                owner_id: None,
                if_not_exists: false,
                project_id: "arcana".into(),
                scope_id: "missing".into(),
                table_name: "invalid".into(),
                columns: vec![ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                }],
                primary_key: vec!["id".into()],
            },
        ])
        .await
        .expect_err("invalid second ddl should fail entire batch");
    assert!(matches!(
        failed,
        AedbError::NotFound {
            resource_type: ErrorResourceType::Scope,
            ..
        }
    ));
    assert!(
        !db.table_exists("arcana", "app", "users")
            .await
            .expect("users table exists check")
    );

    let first = db
        .commit_ddl_batch(vec![DdlOperation::CreateTable {
            owner_id: None,
            if_not_exists: true,
            project_id: "arcana".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            columns: vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "name".into(),
                    col_type: ColumnType::Text,
                    nullable: false,
                },
            ],
            primary_key: vec!["id".into()],
        }])
        .await
        .expect("first batch");
    assert_eq!(first.results.len(), 1);
    assert!(first.results[0].applied);
    assert!(
        db.table_exists("arcana", "app", "users")
            .await
            .expect("table exists")
    );

    let second = db
        .commit_ddl_batch(vec![DdlOperation::CreateTable {
            owner_id: None,
            if_not_exists: true,
            project_id: "arcana".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            columns: vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "name".into(),
                    col_type: ColumnType::Text,
                    nullable: false,
                },
            ],
            primary_key: vec!["id".into()],
        }])
        .await
        .expect("second batch");
    assert_eq!(second.results.len(), 1);
    assert!(!second.results[0].applied);
}

#[tokio::test]
async fn commit_ddl_batch_supports_dependent_ddl_ordering() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    let batch = db
        .commit_ddl_batch(vec![
            DdlOperation::CreateProject {
                owner_id: None,
                project_id: "arcana".into(),
                if_not_exists: true,
            },
            DdlOperation::CreateScope {
                owner_id: None,
                project_id: "arcana".into(),
                scope_id: "ops".into(),
                if_not_exists: true,
            },
            DdlOperation::CreateTable {
                owner_id: None,
                if_not_exists: true,
                project_id: "arcana".into(),
                scope_id: "ops".into(),
                table_name: "users".into(),
                columns: vec![
                    ColumnDef {
                        name: "id".into(),
                        col_type: ColumnType::Integer,
                        nullable: false,
                    },
                    ColumnDef {
                        name: "name".into(),
                        col_type: ColumnType::Text,
                        nullable: false,
                    },
                ],
                primary_key: vec!["id".into()],
            },
        ])
        .await
        .expect("dependent batch");

    assert_eq!(batch.results.len(), 3);
    assert!(batch.results.iter().all(|r| r.applied));
    assert!(
        db.project_exists("arcana")
            .await
            .expect("project exists after batch")
    );
    assert!(
        db.scope_exists("arcana", "ops")
            .await
            .expect("scope exists after batch")
    );
    assert!(
        db.table_exists("arcana", "ops", "users")
            .await
            .expect("table exists after batch")
    );
}

#[tokio::test]
async fn dependency_aware_ddl_batch_reorders_create_dependencies() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    let batch = db
        .commit_ddl_batch_dependency_aware(vec![
            DdlOperation::CreateIndex {
                project_id: "arcana".into(),
                scope_id: "ops".into(),
                table_name: "users".into(),
                index_name: "idx_users_name".into(),
                if_not_exists: true,
                columns: vec!["name".into()],
                index_type: crate::catalog::schema::IndexType::BTree,
                partial_filter: None,
            },
            DdlOperation::CreateTable {
                owner_id: None,
                if_not_exists: true,
                project_id: "arcana".into(),
                scope_id: "ops".into(),
                table_name: "users".into(),
                columns: vec![
                    ColumnDef {
                        name: "id".into(),
                        col_type: ColumnType::Integer,
                        nullable: false,
                    },
                    ColumnDef {
                        name: "name".into(),
                        col_type: ColumnType::Text,
                        nullable: false,
                    },
                ],
                primary_key: vec!["id".into()],
            },
            DdlOperation::CreateScope {
                owner_id: None,
                project_id: "arcana".into(),
                scope_id: "ops".into(),
                if_not_exists: true,
            },
            DdlOperation::CreateProject {
                owner_id: None,
                project_id: "arcana".into(),
                if_not_exists: true,
            },
        ])
        .await
        .expect("dependency-aware batch");

    assert_eq!(batch.results.len(), 4);
    assert!(
        db.index_exists("arcana", "ops", "users", "idx_users_name")
            .await
            .expect("index exists")
    );
}

#[tokio::test]
async fn ddl_errors_expose_stable_error_codes() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");

    let err = db
        .commit(Mutation::Ddl(DdlOperation::CreateIndex {
            project_id: "arcana".into(),
            scope_id: "app".into(),
            table_name: "missing_users".into(),
            index_name: "idx_missing".into(),
            if_not_exists: false,
            columns: vec!["name".into()],
            index_type: crate::catalog::schema::IndexType::BTree,
            partial_filter: None,
        }))
        .await
        .expect_err("create index on missing table should fail");

    assert_eq!(err.code(), AedbErrorCode::TableNotFound);
}

#[tokio::test]
async fn index_introspection_apis_return_index_definitions() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");
    db.commit_ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: true,
        project_id: "arcana".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    })
    .await
    .expect("create table");
    db.commit_ddl(DdlOperation::CreateIndex {
        project_id: "arcana".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        index_name: "idx_users_name".into(),
        if_not_exists: false,
        columns: vec!["name".into()],
        index_type: crate::catalog::schema::IndexType::BTree,
        partial_filter: None,
    })
    .await
    .expect("create index");

    let listed = db
        .list_indexes("arcana", "app", "users")
        .await
        .expect("list indexes");
    assert_eq!(listed.len(), 1);
    assert_eq!(listed[0].index_name, "idx_users_name");

    let described = db
        .describe_index("arcana", "app", "users", "idx_users_name")
        .await
        .expect("describe index");
    assert_eq!(described.columns, vec!["name".to_string()]);
}

#[tokio::test]
async fn lifecycle_hooks_receive_post_commit_events_for_applied_ddl() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    let events = Arc::new(std::sync::Mutex::new(Vec::new()));
    let hook: Arc<dyn LifecycleHook> = Arc::new(RecordingLifecycleHook {
        events: Arc::clone(&events),
    });
    db.add_lifecycle_hook(Arc::clone(&hook));

    db.commit_ddl(DdlOperation::CreateProject {
        owner_id: None,
        project_id: "arcana".into(),
        if_not_exists: true,
    })
    .await
    .expect("create project");
    db.commit_ddl(DdlOperation::CreateProject {
        owner_id: None,
        project_id: "arcana".into(),
        if_not_exists: true,
    })
    .await
    .expect("idempotent create");

    let deadline = Instant::now() + Duration::from_secs(2);
    while Instant::now() < deadline {
        if !events.lock().expect("events lock").is_empty() {
            break;
        }
        tokio::time::sleep(Duration::from_millis(10)).await;
    }
    let seen = events.lock().expect("events lock");
    assert_eq!(seen.len(), 1);
    assert!(matches!(
        &seen[0],
        LifecycleEvent::ProjectCreated { project_id, .. } if project_id == "arcana"
    ));
}

#[tokio::test]
async fn removing_last_lifecycle_hook_disables_dispatch() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    let events = Arc::new(std::sync::Mutex::new(Vec::new()));
    let hook: Arc<dyn LifecycleHook> = Arc::new(RecordingLifecycleHook {
        events: Arc::clone(&events),
    });
    db.add_lifecycle_hook(Arc::clone(&hook));
    db.remove_lifecycle_hook(&hook);

    db.commit_ddl(DdlOperation::CreateProject {
        owner_id: None,
        project_id: "arcana".into(),
        if_not_exists: true,
    })
    .await
    .expect("create project");
    tokio::time::sleep(Duration::from_millis(25)).await;

    assert!(events.lock().expect("events lock").is_empty());
}

#[tokio::test]
async fn lifecycle_outbox_persists_applied_events() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    let created = db
        .commit_ddl(DdlOperation::CreateProject {
            owner_id: None,
            project_id: "arcana".into(),
            if_not_exists: true,
        })
        .await
        .expect("create project");

    let outbox = db
        .query_no_auth(
            "_system",
            "app",
            Query::select(&["event_count", "events"])
                .from("lifecycle_outbox")
                .where_(Expr::Eq(
                    "commit_seq".into(),
                    Value::Integer(created.seq as i64),
                ))
                .limit(1),
            QueryOptions::default(),
        )
        .await
        .expect("query lifecycle outbox");
    assert_eq!(outbox.rows.len(), 1, "expected lifecycle outbox row");
    assert_eq!(outbox.rows[0].values[0], Value::Integer(1));
    let Value::Json(payload) = &outbox.rows[0].values[1] else {
        panic!("expected json payload");
    };
    let events: Vec<LifecycleEvent> =
        serde_json::from_str(payload.as_str()).expect("decode lifecycle payload");
    assert!(matches!(
        events.first(),
        Some(LifecycleEvent::ProjectCreated { project_id, seq })
            if project_id == "arcana" && *seq == created.seq
    ));
}

#[tokio::test]
async fn lifecycle_outbox_includes_app_emit_events() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");
    let committed = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "hand_1".into(),
            r#"{"user_id":"u1","wager":100,"pnl":-25}"#.into(),
        )
        .await
        .expect("emit");

    let outbox = db
        .query_no_auth(
            "_system",
            "app",
            Query::select(&["events"])
                .from("lifecycle_outbox")
                .where_(Expr::Eq(
                    "commit_seq".into(),
                    Value::Integer(committed.commit_seq as i64),
                ))
                .limit(1),
            QueryOptions::default(),
        )
        .await
        .expect("query lifecycle outbox");
    assert_eq!(outbox.rows.len(), 1, "expected lifecycle outbox row");
    let Value::Json(payload) = &outbox.rows[0].values[0] else {
        panic!("expected json payload");
    };
    let events: Vec<LifecycleEvent> =
        serde_json::from_str(payload.as_str()).expect("decode lifecycle payload");
    assert!(events.iter().any(|evt| matches!(
        evt,
        LifecycleEvent::AppEventEmitted {
            project_id,
            scope_id,
            topic,
            event_key,
            payload_json,
            ..
        } if project_id == "arcana"
            && scope_id == "app"
            && topic == "hand_settled"
            && event_key == "hand_1"
            && payload_json.contains("\"user_id\":\"u1\"")
    )));
}

#[tokio::test]
async fn event_outbox_and_reactive_processor_checkpoint_lag_work() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: Some("system".into()),
        if_not_exists: true,
        project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
        scope_id: "app".into(),
        table_name: "reactive_processor_checkpoints".into(),
        columns: vec![
            ColumnDef {
                name: "processor_name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
            ColumnDef {
                name: "checkpoint_seq".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "updated_at_micros".into(),
                col_type: ColumnType::Timestamp,
                nullable: false,
            },
        ],
        primary_key: vec!["processor_name".into()],
    }))
    .await
    .expect("create processor checkpoint table");

    let first = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "hand_1".into(),
            r#"{"user_id":"u1","wager":100,"pnl":-100}"#.into(),
        )
        .await
        .expect("emit first event");
    let second = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "hand_2".into(),
            r#"{"user_id":"u2","wager":75,"pnl":50}"#.into(),
        )
        .await
        .expect("emit second event");

    let page = db
        .read_event_stream(Some("hand_settled"), 0, 10, ConsistencyMode::AtLatest)
        .await
        .expect("read stream");
    assert_eq!(page.events.len(), 2);
    assert_eq!(page.events[0].event_key, "hand_1");
    assert_eq!(page.events[1].event_key, "hand_2");
    assert_eq!(page.next_commit_seq, Some(second.commit_seq));

    db.ack_reactive_processor_checkpoint("points_processor", first.commit_seq)
        .await
        .expect("ack checkpoint");
    let lag = db
        .reactive_processor_lag("points_processor", ConsistencyMode::AtLatest)
        .await
        .expect("lag");
    assert_eq!(lag.processor_name, "points_processor");
    assert_eq!(lag.checkpoint_seq, first.commit_seq);
    assert!(lag.head_seq >= second.commit_seq);
    assert_eq!(
        lag.lag_commits,
        lag.head_seq.saturating_sub(first.commit_seq)
    );
}

#[tokio::test]
async fn reactive_processor_checkpoint_ack_batches_by_watermark() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: Some("system".into()),
        if_not_exists: true,
        project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
        scope_id: "app".into(),
        table_name: "reactive_processor_checkpoints".into(),
        columns: vec![
            ColumnDef {
                name: "processor_name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
            ColumnDef {
                name: "checkpoint_seq".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "updated_at_micros".into(),
                col_type: ColumnType::Timestamp,
                nullable: false,
            },
        ],
        primary_key: vec!["processor_name".into()],
    }))
    .await
    .expect("create processor checkpoint table");

    let first = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "hand_1".into(),
            r#"{"user_id":"u1","wager":100,"pnl":-100}"#.into(),
        )
        .await
        .expect("emit first event");
    let second = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "hand_2".into(),
            r#"{"user_id":"u2","wager":75,"pnl":50}"#.into(),
        )
        .await
        .expect("emit second event");

    let persisted = db
        .ack_reactive_processor_checkpoint_batched("points_processor", first.commit_seq, 2)
        .await
        .expect("first batched ack");
    assert!(persisted.is_some(), "first ack should persist baseline");

    let deferred = db
        .ack_reactive_processor_checkpoint_batched("points_processor", first.commit_seq + 1, 2)
        .await
        .expect("deferred batched ack");
    assert!(deferred.is_none(), "ack below watermark should be deferred");

    let lag_after_defer = db
        .reactive_processor_lag("points_processor", ConsistencyMode::AtLatest)
        .await
        .expect("lag after deferred ack");
    assert_eq!(lag_after_defer.checkpoint_seq, first.commit_seq);

    let persisted_after_watermark = db
        .ack_reactive_processor_checkpoint_batched("points_processor", first.commit_seq + 2, 2)
        .await
        .expect("persist on watermark");
    assert!(
        persisted_after_watermark.is_some(),
        "watermark crossing should persist checkpoint"
    );

    let lag = db
        .reactive_processor_lag("points_processor", ConsistencyMode::AtLatest)
        .await
        .expect("lag");
    assert_eq!(lag.processor_name, "points_processor");
    assert_eq!(lag.checkpoint_seq, first.commit_seq + 2);
    assert!(lag.head_seq >= second.commit_seq);
}

#[tokio::test]
async fn reactive_processor_checkpoint_ack_rejects_regression() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: Some("system".into()),
        if_not_exists: true,
        project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
        scope_id: "app".into(),
        table_name: "reactive_processor_checkpoints".into(),
        columns: vec![
            ColumnDef {
                name: "processor_name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
            ColumnDef {
                name: "checkpoint_seq".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "updated_at_micros".into(),
                col_type: ColumnType::Timestamp,
                nullable: false,
            },
        ],
        primary_key: vec!["processor_name".into()],
    }))
    .await
    .expect("create processor checkpoint table");

    let first = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "hand_1".into(),
            r#"{"user_id":"u1","wager":100,"pnl":-100}"#.into(),
        )
        .await
        .expect("emit first event");
    let second = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "hand_2".into(),
            r#"{"user_id":"u2","wager":75,"pnl":50}"#.into(),
        )
        .await
        .expect("emit second event");

    db.ack_reactive_processor_checkpoint("points_processor", second.commit_seq)
        .await
        .expect("persist newer checkpoint");

    let err = db
        .ack_reactive_processor_checkpoint("points_processor", first.commit_seq)
        .await
        .expect_err("checkpoint regression must fail");
    assert!(matches!(err, AedbError::Validation(_)));

    let lag = db
        .reactive_processor_lag("points_processor", ConsistencyMode::AtLatest)
        .await
        .expect("lag");
    assert_eq!(lag.checkpoint_seq, second.commit_seq);
}

#[tokio::test]
async fn reactive_processor_checkpoint_batched_rejects_stale_persist_after_concurrent_advance() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: Some("system".into()),
        if_not_exists: true,
        project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
        scope_id: "app".into(),
        table_name: "reactive_processor_checkpoints".into(),
        columns: vec![
            ColumnDef {
                name: "processor_name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
            ColumnDef {
                name: "checkpoint_seq".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "updated_at_micros".into(),
                col_type: ColumnType::Timestamp,
                nullable: false,
            },
        ],
        primary_key: vec!["processor_name".into()],
    }))
    .await
    .expect("create processor checkpoint table");

    let first = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "hand_1".into(),
            r#"{"user_id":"u1","wager":100,"pnl":-100}"#.into(),
        )
        .await
        .expect("emit first event");
    let second = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "hand_2".into(),
            r#"{"user_id":"u2","wager":75,"pnl":50}"#.into(),
        )
        .await
        .expect("emit second event");

    let stale = db
        .build_reactive_processor_checkpoint_envelope("points_processor", first.commit_seq)
        .await
        .expect("stale envelope");

    db.ack_reactive_processor_checkpoint("points_processor", second.commit_seq)
        .await
        .expect("persist newer checkpoint");

    let err = db
        .commit_envelope_prevalidated_internal("stale_reactive_ack_test", stale)
        .await
        .expect_err("stale asserted ack must fail");
    assert!(matches!(err, AedbError::Conflict(_)));

    let lag = db
        .reactive_processor_lag("points_processor", ConsistencyMode::AtLatest)
        .await
        .expect("lag");
    assert_eq!(lag.checkpoint_seq, second.commit_seq);
}

#[tokio::test]
async fn reactive_processor_checkpoint_batched_as_isolated_by_caller() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");
    db.emit_event("arcana", "app", "bootstrap", "evt-1".into(), "{}".into())
        .await
        .expect("bootstrap system scope");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: Some("system".into()),
        if_not_exists: true,
        project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
        scope_id: "app".into(),
        table_name: "reactive_processor_checkpoints".into(),
        columns: vec![
            ColumnDef {
                name: "processor_name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
            ColumnDef {
                name: "checkpoint_seq".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "updated_at_micros".into(),
                col_type: ColumnType::Timestamp,
                nullable: false,
            },
        ],
        primary_key: vec!["processor_name".into()],
    }))
    .await
    .expect("create processor checkpoint table");
    db.ack_reactive_processor_checkpoint("bootstrap", 1)
        .await
        .expect("bootstrap processor checkpoint table");

    for caller in ["alice", "bob"] {
        db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
            actor_id: None,
            delegable: false,
            caller_id: caller.into(),
            permission: Permission::TableWrite {
                project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
                scope_id: "app".into(),
                table_name: "reactive_processor_checkpoints".into(),
            },
        }))
        .await
        .expect("grant system table write");
    }

    let first = db
        .ack_reactive_processor_checkpoint_batched_as(
            CallerContext::new("alice"),
            "shared_processor",
            42,
            100,
        )
        .await
        .expect("alice ack");
    assert!(first.is_some(), "alice baseline ack should persist");

    let second = db
        .ack_reactive_processor_checkpoint_batched_as(
            CallerContext::new("bob"),
            "shared_processor",
            42,
            100,
        )
        .await
        .expect("bob ack");
    assert!(
        second.is_some(),
        "bob baseline ack should persist independently"
    );
}

#[tokio::test]
async fn reactive_processor_checkpoint_batched_as_does_not_poison_cache_on_permission_failure() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");
    db.emit_event("arcana", "app", "bootstrap", "evt-1".into(), "{}".into())
        .await
        .expect("bootstrap system scope");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: Some("system".into()),
        if_not_exists: true,
        project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
        scope_id: "app".into(),
        table_name: "reactive_processor_checkpoints".into(),
        columns: vec![
            ColumnDef {
                name: "processor_name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
            ColumnDef {
                name: "checkpoint_seq".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "updated_at_micros".into(),
                col_type: ColumnType::Timestamp,
                nullable: false,
            },
        ],
        primary_key: vec!["processor_name".into()],
    }))
    .await
    .expect("create processor checkpoint table");
    db.ack_reactive_processor_checkpoint("bootstrap", 1)
        .await
        .expect("bootstrap processor checkpoint table");

    let denied = db
        .ack_reactive_processor_checkpoint_batched_as(
            CallerContext::new("mallory"),
            "perm_test_processor",
            100,
            100,
        )
        .await
        .expect_err("mallory should be denied before grant");
    assert!(matches!(denied, AedbError::PermissionDenied(_)));

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "mallory".into(),
        permission: Permission::TableWrite {
            project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
            scope_id: "app".into(),
            table_name: "reactive_processor_checkpoints".into(),
        },
    }))
    .await
    .expect("grant system table write");

    let persisted = db
        .ack_reactive_processor_checkpoint_batched_as(
            CallerContext::new("mallory"),
            "perm_test_processor",
            100,
            100,
        )
        .await
        .expect("mallory should succeed after grant");
    assert!(
        persisted.is_some(),
        "failed pre-grant attempt must not suppress later successful persist"
    );
}

#[tokio::test]
async fn reactive_processor_checkpoint_batched_cache_is_bounded() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");
    db.emit_event("arcana", "app", "bootstrap", "evt-1".into(), "{}".into())
        .await
        .expect("bootstrap system scope");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: Some("system".into()),
        if_not_exists: true,
        project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
        scope_id: "app".into(),
        table_name: "reactive_processor_checkpoints".into(),
        columns: vec![
            ColumnDef {
                name: "processor_name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
            ColumnDef {
                name: "checkpoint_seq".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "updated_at_micros".into(),
                col_type: ColumnType::Timestamp,
                nullable: false,
            },
        ],
        primary_key: vec!["processor_name".into()],
    }))
    .await
    .expect("create processor checkpoint table");
    {
        let mut cache = db.reactive_processor_ack_watermarks.lock();
        for i in 0..(REACTIVE_ACK_CACHE_MAX_ENTRIES + 200) {
            cache.insert(
                ReactiveCheckpointAckCacheKey {
                    processor_name: format!("cache-seed-{i}"),
                    caller_id: None,
                },
                ReactiveCheckpointAckState {
                    last_persisted_seq: i as u64,
                    last_touch_micros: i as u64,
                },
            );
        }
    }

    let persisted = db
        .ack_reactive_processor_checkpoint_batched("cache-boundary", 1, 1)
        .await
        .expect("ack should succeed");
    assert!(persisted.is_some());
    let cache_len = db.reactive_processor_ack_watermarks.lock().len();
    assert!(
        cache_len <= REACTIVE_ACK_CACHE_MAX_ENTRIES,
        "cache should be pruned to cap; got {}",
        cache_len
    );
}

#[tokio::test]
async fn reactive_processor_scheduler_runs_with_period_and_batch_limits() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");

    let seen_batches = Arc::new(std::sync::Mutex::new(Vec::<usize>::new()));
    let seen_batches_handler = Arc::clone(&seen_batches);
    db.start_reactive_processor(
        "sched_processor",
        super::ReactiveProcessorOptions {
            caller_id: None,
            topic_filter: Some("hand_settled".into()),
            run_on_interval: false,
            max_allowed_lag_commits: None,
            max_allowed_stall_ms: None,
            max_events_per_run: 2,
            max_bytes_per_run: 1_000_000,
            max_run_duration_ms: 250,
            run_interval_ms: 10,
            idle_backoff_ms: 10,
            checkpoint_watermark_commits: 1,
            max_retries: 3,
            retry_backoff_ms: 5,
        },
        move |_db, events| {
            let seen_batches_handler = Arc::clone(&seen_batches_handler);
            async move {
                seen_batches_handler
                    .lock()
                    .expect("batch lock")
                    .push(events.len());
                Ok(())
            }
        },
    )
    .await
    .expect("start processor");

    let mut last_seq = 0u64;
    for i in 0..5 {
        let commit = db
            .emit_event(
                "arcana",
                "app",
                "hand_settled",
                format!("hand-{i}"),
                format!(r#"{{"hand":"{i}"}}"#),
            )
            .await
            .expect("emit event");
        last_seq = last_seq.max(commit.commit_seq);
    }

    let deadline = Instant::now() + Duration::from_secs(3);
    loop {
        let lag = db
            .reactive_processor_lag("sched_processor", ConsistencyMode::AtLatest)
            .await
            .expect("lag");
        if lag.checkpoint_seq >= last_seq {
            break;
        }
        if Instant::now() >= deadline {
            panic!("processor did not catch up before deadline");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }

    let status = db
        .reactive_processor_runtime_status("sched_processor")
        .await
        .expect("runtime status");
    assert!(status.processed_events_total >= 5);
    let batches = seen_batches.lock().expect("batches").clone();
    assert!(!batches.is_empty());
    assert!(batches.iter().all(|size| *size <= 2));

    db.stop_reactive_processor("sched_processor")
        .await
        .expect("stop processor");
    assert!(
        db.reactive_processor_runtime_status("sched_processor")
            .await
            .is_none()
    );
}

#[tokio::test]
async fn reactive_processor_registry_persists_and_auto_resumes_on_handler_registration() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");

    let processed = Arc::new(std::sync::atomic::AtomicU64::new(0));
    let processed_first = Arc::clone(&processed);
    db.start_reactive_processor(
        "resume_processor",
        super::ReactiveProcessorOptions {
            topic_filter: Some("hand_settled".into()),
            checkpoint_watermark_commits: 1,
            ..super::ReactiveProcessorOptions::default()
        },
        move |_db, events| {
            let processed_first = Arc::clone(&processed_first);
            async move {
                processed_first
                    .fetch_add(events.len() as u64, std::sync::atomic::Ordering::Relaxed);
                Ok(())
            }
        },
    )
    .await
    .expect("start processor");

    let first = db
        .emit_event("arcana", "app", "hand_settled", "h1".into(), "{}".into())
        .await
        .expect("emit first");
    let deadline = Instant::now() + Duration::from_secs(2);
    loop {
        let lag = db
            .reactive_processor_lag("resume_processor", ConsistencyMode::AtLatest)
            .await
            .expect("lag");
        if lag.checkpoint_seq >= first.commit_seq {
            break;
        }
        if Instant::now() >= deadline {
            panic!("processor failed to checkpoint first event");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }
    db.shutdown().await.expect("graceful shutdown");
    drop(db);

    let db2 = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("reopen"));
    let processed_resume = Arc::clone(&processed);
    let resumed = db2
        .register_reactive_processor_handler("resume_processor", move |_db, events| {
            let processed = Arc::clone(&processed_resume);
            async move {
                processed.fetch_add(events.len() as u64, std::sync::atomic::Ordering::Relaxed);
                Ok(())
            }
        })
        .await
        .expect("register handler");
    assert!(resumed, "enabled processor should auto-resume");

    let second = db2
        .emit_event("arcana", "app", "hand_settled", "h2".into(), "{}".into())
        .await
        .expect("emit second");
    let deadline = Instant::now() + Duration::from_secs(2);
    loop {
        let lag = db2
            .reactive_processor_lag("resume_processor", ConsistencyMode::AtLatest)
            .await
            .expect("lag after resume");
        if lag.checkpoint_seq >= second.commit_seq {
            break;
        }
        if Instant::now() >= deadline {
            panic!("processor failed to checkpoint second event after resume");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }
    db2.stop_reactive_processor("resume_processor")
        .await
        .expect("stop resumed");
    assert!(
        processed.load(std::sync::atomic::Ordering::Relaxed) >= 2,
        "expected both events processed"
    );
}

#[tokio::test]
async fn reactive_processor_scheduler_retries_and_then_succeeds() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");

    let attempts = Arc::new(std::sync::atomic::AtomicU64::new(0));
    let attempts_handler = Arc::clone(&attempts);
    db.start_reactive_processor(
        "retry_processor",
        super::ReactiveProcessorOptions {
            caller_id: None,
            topic_filter: Some("hand_settled".into()),
            run_on_interval: false,
            max_allowed_lag_commits: None,
            max_allowed_stall_ms: None,
            checkpoint_watermark_commits: 1,
            max_events_per_run: 16,
            max_bytes_per_run: 1_000_000,
            max_run_duration_ms: 250,
            run_interval_ms: 10,
            idle_backoff_ms: 10,
            max_retries: 3,
            retry_backoff_ms: 10,
        },
        move |_db, _events| {
            let attempts_handler = Arc::clone(&attempts_handler);
            async move {
                let n = attempts_handler.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
                if n == 0 {
                    return Err(AedbError::Validation("injected retryable failure".into()));
                }
                Ok(())
            }
        },
    )
    .await
    .expect("start retry processor");

    let evt = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "retry-hand".into(),
            "{}".into(),
        )
        .await
        .expect("emit");

    let deadline = Instant::now() + Duration::from_secs(3);
    loop {
        let lag = db
            .reactive_processor_lag("retry_processor", ConsistencyMode::AtLatest)
            .await
            .expect("lag");
        if lag.checkpoint_seq >= evt.commit_seq {
            break;
        }
        if Instant::now() >= deadline {
            panic!("retry processor did not checkpoint");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }

    let status = db
        .reactive_processor_runtime_status("retry_processor")
        .await
        .expect("status");
    assert!(status.retries_total >= 1, "expected at least one retry");
    assert_eq!(status.dead_lettered_total, 0);
    db.stop_reactive_processor("retry_processor")
        .await
        .expect("stop");
}

#[tokio::test]
async fn reactive_processor_scheduler_dead_letters_after_retry_exhaustion() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");

    db.start_reactive_processor(
        "dlq_processor",
        super::ReactiveProcessorOptions {
            caller_id: None,
            topic_filter: Some("hand_settled".into()),
            run_on_interval: false,
            max_allowed_lag_commits: None,
            max_allowed_stall_ms: None,
            checkpoint_watermark_commits: 1,
            max_events_per_run: 16,
            max_bytes_per_run: 1_000_000,
            max_run_duration_ms: 250,
            run_interval_ms: 10,
            idle_backoff_ms: 10,
            max_retries: 1,
            retry_backoff_ms: 10,
        },
        move |_db, _events| async move { Err(AedbError::Validation("permanent fail".into())) },
    )
    .await
    .expect("start dlq processor");

    let evt = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "dlq-hand".into(),
            "{}".into(),
        )
        .await
        .expect("emit");

    let deadline = Instant::now() + Duration::from_secs(4);
    loop {
        let lag = db
            .reactive_processor_lag("dlq_processor", ConsistencyMode::AtLatest)
            .await
            .expect("lag");
        if lag.checkpoint_seq >= evt.commit_seq {
            break;
        }
        if Instant::now() >= deadline {
            panic!("dlq processor did not advance checkpoint");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }

    let dlq = db
        .query(
            crate::catalog::SYSTEM_PROJECT_ID,
            "app",
            Query::select(&["processor_name", "event_key", "attempts"])
                .from("reactive_processor_dead_letters")
                .where_(Expr::Eq(
                    "processor_name".into(),
                    Value::Text("dlq_processor".into()),
                ))
                .limit(10),
        )
        .await
        .expect("query dlq");
    assert_eq!(dlq.rows.len(), 1);
    assert_eq!(dlq.rows[0].values[1], Value::Text("dlq-hand".into()));
    assert_eq!(dlq.rows[0].values[2], Value::Integer(2));

    let status = db
        .reactive_processor_runtime_status("dlq_processor")
        .await
        .expect("status");
    assert!(status.dead_lettered_total >= 1);
    db.stop_reactive_processor("dlq_processor")
        .await
        .expect("stop");
}

#[tokio::test]
async fn reactive_processor_scheduler_requires_caller_id_in_secure_mode() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(
        AedbInstance::open_secure(AedbConfig::production([11u8; 32]), dir.path())
            .expect("open secure"),
    );

    let err = db
        .start_reactive_processor(
            "secure_proc",
            super::ReactiveProcessorOptions {
                caller_id: None,
                ..super::ReactiveProcessorOptions::default()
            },
            move |_db, _events| async move { Ok(()) },
        )
        .await
        .expect_err("secure mode should require caller_id");
    assert!(matches!(err, AedbError::PermissionDenied(_)));
}

#[tokio::test]
async fn reactive_processor_scheduler_uses_explicit_caller_permissions() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(
        AedbInstance::open_secure(AedbConfig::production([12u8; 32]), dir.path())
            .expect("open secure"),
    );
    let system = CallerContext::system_internal();
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "arcana".into(),
        }),
    )
    .await
    .expect("create project");
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateScope {
            owner_id: None,
            if_not_exists: true,
            project_id: "arcana".into(),
            scope_id: "app".into(),
        }),
    )
    .await
    .expect("create scope");
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::GrantPermission {
            actor_id: Some("system".into()),
            delegable: false,
            caller_id: "proc_sched".into(),
            permission: Permission::TableWrite {
                project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
                scope_id: "app".into(),
                table_name: "reactive_processor_registry".into(),
            },
        }),
    )
    .await
    .expect("grant registry write");

    let processed = Arc::new(std::sync::atomic::AtomicU64::new(0));
    let processed_handler = Arc::clone(&processed);
    db.start_reactive_processor(
        "secure_sched",
        super::ReactiveProcessorOptions {
            caller_id: Some("proc_sched".into()),
            topic_filter: Some("hand_settled".into()),
            checkpoint_watermark_commits: 1,
            run_interval_ms: 10,
            idle_backoff_ms: 10,
            max_retries: 0,
            ..super::ReactiveProcessorOptions::default()
        },
        move |_db, events| {
            let processed_handler = Arc::clone(&processed_handler);
            async move {
                processed_handler
                    .fetch_add(events.len() as u64, std::sync::atomic::Ordering::Relaxed);
                Ok(())
            }
        },
    )
    .await
    .expect("start processor");

    let deadline = Instant::now() + Duration::from_secs(2);
    loop {
        let status = db
            .reactive_processor_runtime_status("secure_sched")
            .await
            .expect("status");
        if status.failures_total > 0 {
            break;
        }
        if Instant::now() >= deadline {
            panic!("expected scheduler permission failures before grants");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }

    for permission in [
        Permission::TableRead {
            project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
            scope_id: "app".into(),
            table_name: "event_outbox".into(),
        },
        Permission::TableRead {
            project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
            scope_id: "app".into(),
            table_name: "reactive_processor_checkpoints".into(),
        },
        Permission::TableWrite {
            project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
            scope_id: "app".into(),
            table_name: "reactive_processor_checkpoints".into(),
        },
    ] {
        db.commit_as(
            system.clone(),
            Mutation::Ddl(DdlOperation::GrantPermission {
                actor_id: Some("system".into()),
                delegable: false,
                caller_id: "proc_sched".into(),
                permission,
            }),
        )
        .await
        .expect("grant processor permission");
    }

    let evt = db
        .emit_event_as(
            system,
            "arcana",
            "app",
            "hand_settled",
            "secure-hand".into(),
            "{}".into(),
        )
        .await
        .expect("emit secure event");
    let proc_caller = CallerContext::new("proc_sched");
    let deadline = Instant::now() + Duration::from_secs(3);
    loop {
        let lag = db
            .reactive_processor_lag_as(&proc_caller, "secure_sched", ConsistencyMode::AtLatest)
            .await
            .expect("lag");
        if lag.checkpoint_seq >= evt.commit_seq {
            break;
        }
        if Instant::now() >= deadline {
            panic!("processor failed to progress after grants");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }

    assert!(
        processed.load(std::sync::atomic::Ordering::Relaxed) >= 1,
        "expected at least one processed event"
    );
    db.stop_reactive_processor("secure_sched")
        .await
        .expect("stop processor");
}

#[tokio::test]
async fn reactive_processor_pause_resume_list_and_health_work() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");

    db.start_reactive_processor(
        "lifecycle_processor",
        super::ReactiveProcessorOptions {
            caller_id: None,
            topic_filter: Some("hand_settled".into()),
            checkpoint_watermark_commits: 1,
            run_interval_ms: 10,
            idle_backoff_ms: 10,
            ..super::ReactiveProcessorOptions::default()
        },
        move |_db, _events| async move { Ok(()) },
    )
    .await
    .expect("start");

    let first = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "life-1".into(),
            "{}".into(),
        )
        .await
        .expect("emit first");
    let deadline = Instant::now() + Duration::from_secs(3);
    loop {
        let lag = db
            .reactive_processor_lag("lifecycle_processor", ConsistencyMode::AtLatest)
            .await
            .expect("lag");
        if lag.checkpoint_seq >= first.commit_seq {
            break;
        }
        if Instant::now() >= deadline {
            panic!("lifecycle processor did not checkpoint first event");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }

    let listed = db
        .list_reactive_processors(ConsistencyMode::AtLatest)
        .await
        .expect("list processors");
    let row = listed
        .into_iter()
        .find(|r| r.processor_name == "lifecycle_processor")
        .expect("processor listed");
    assert!(row.enabled);
    assert!(row.running);

    let health = db
        .reactive_processor_health("lifecycle_processor", ConsistencyMode::AtLatest)
        .await
        .expect("health");
    assert!(health.running);
    assert!(health.enabled);
    assert!(health.processed_events_total >= 1);
    assert!(health.last_run_completed_micros.is_some());

    db.pause_reactive_processor("lifecycle_processor")
        .await
        .expect("pause");

    let listed = db
        .list_reactive_processors(ConsistencyMode::AtLatest)
        .await
        .expect("list processors paused");
    let row = listed
        .into_iter()
        .find(|r| r.processor_name == "lifecycle_processor")
        .expect("processor listed paused");
    assert!(!row.enabled);
    assert!(!row.running);

    let paused_health = db
        .reactive_processor_health("lifecycle_processor", ConsistencyMode::AtLatest)
        .await
        .expect("paused health");
    assert!(!paused_health.running);
    assert!(!paused_health.enabled);

    db.resume_reactive_processor("lifecycle_processor")
        .await
        .expect("resume");

    let second = db
        .emit_event(
            "arcana",
            "app",
            "hand_settled",
            "life-2".into(),
            "{}".into(),
        )
        .await
        .expect("emit second");
    let deadline = Instant::now() + Duration::from_secs(3);
    loop {
        let lag = db
            .reactive_processor_lag("lifecycle_processor", ConsistencyMode::AtLatest)
            .await
            .expect("lag resumed");
        if lag.checkpoint_seq >= second.commit_seq {
            break;
        }
        if Instant::now() >= deadline {
            panic!("lifecycle processor did not checkpoint second event");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }

    db.stop_reactive_processor("lifecycle_processor")
        .await
        .expect("stop");
}

#[tokio::test]
async fn reactive_processor_resume_requires_registered_handler() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");
    db.start_reactive_processor(
        "resume_requires_handler",
        super::ReactiveProcessorOptions {
            caller_id: None,
            topic_filter: Some("hand_settled".into()),
            ..super::ReactiveProcessorOptions::default()
        },
        move |_db, _events| async move { Ok(()) },
    )
    .await
    .expect("start");
    db.pause_reactive_processor("resume_requires_handler")
        .await
        .expect("pause");
    db.shutdown().await.expect("shutdown");
    drop(db);

    let db2 = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("reopen"));
    let err = db2
        .resume_reactive_processor("resume_requires_handler")
        .await
        .expect_err("resume without handler should fail");
    assert!(matches!(err, AedbError::Validation(_)));
}

#[tokio::test]
async fn reactive_processor_scheduler_can_run_periodically_without_events() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");

    let ticks = Arc::new(std::sync::atomic::AtomicU64::new(0));
    let ticks_handler = Arc::clone(&ticks);
    db.start_reactive_processor(
        "periodic_processor",
        super::ReactiveProcessorOptions {
            caller_id: None,
            topic_filter: Some("never_emitted".into()),
            run_on_interval: true,
            run_interval_ms: 10,
            idle_backoff_ms: 10,
            max_retries: 0,
            ..super::ReactiveProcessorOptions::default()
        },
        move |_db, events| {
            let ticks_handler = Arc::clone(&ticks_handler);
            async move {
                assert!(events.is_empty(), "periodic run should be empty-batch");
                ticks_handler.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
                Ok(())
            }
        },
    )
    .await
    .expect("start periodic processor");

    let deadline = Instant::now() + Duration::from_secs(2);
    loop {
        let n = ticks.load(std::sync::atomic::Ordering::Relaxed);
        if n >= 3 {
            break;
        }
        if Instant::now() >= deadline {
            panic!("periodic processor did not run enough ticks");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }

    db.stop_reactive_processor("periodic_processor")
        .await
        .expect("stop periodic");
}

#[tokio::test]
async fn reactive_processor_slo_status_and_enforcement_detect_breaches() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");

    db.start_reactive_processor(
        "slo_breach_processor",
        super::ReactiveProcessorOptions {
            caller_id: None,
            topic_filter: Some("hand_settled".into()),
            run_on_interval: false,
            max_allowed_lag_commits: Some(0),
            max_allowed_stall_ms: None,
            run_interval_ms: 60_000,
            idle_backoff_ms: 60_000,
            ..super::ReactiveProcessorOptions::default()
        },
        move |_db, _events| async move { Ok(()) },
    )
    .await
    .expect("start processor");

    db.emit_event(
        "arcana",
        "app",
        "hand_settled",
        "slo-hand".into(),
        "{}".into(),
    )
    .await
    .expect("emit");
    tokio::time::sleep(Duration::from_millis(20)).await;

    let status = db
        .reactive_processor_slo_status("slo_breach_processor", ConsistencyMode::AtLatest)
        .await
        .expect("slo status");
    assert!(status.breached, "expected lag SLO breach");
    assert!(!status.reasons.is_empty());

    let statuses = db
        .list_reactive_processor_slo_statuses(ConsistencyMode::AtLatest)
        .await
        .expect("list slo statuses");
    assert!(
        statuses
            .iter()
            .any(|s| s.processor_name == "slo_breach_processor" && s.breached)
    );

    let enforce = db
        .enforce_reactive_processor_slos(ConsistencyMode::AtLatest)
        .await;
    assert!(matches!(enforce, Err(AedbError::Unavailable { .. })));

    db.stop_reactive_processor("slo_breach_processor")
        .await
        .expect("stop");
}

#[tokio::test]
async fn reactive_processor_slo_status_healthy_when_within_thresholds() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("arcana").await.expect("project");
    db.create_scope("arcana", "app").await.expect("scope");

    let ticks = Arc::new(std::sync::atomic::AtomicU64::new(0));
    let ticks_handler = Arc::clone(&ticks);
    db.start_reactive_processor(
        "slo_ok_processor",
        super::ReactiveProcessorOptions {
            caller_id: None,
            topic_filter: Some("none".into()),
            run_on_interval: true,
            max_allowed_lag_commits: Some(10),
            max_allowed_stall_ms: Some(5_000),
            run_interval_ms: 10,
            idle_backoff_ms: 10,
            ..super::ReactiveProcessorOptions::default()
        },
        move |_db, _events| {
            let ticks_handler = Arc::clone(&ticks_handler);
            async move {
                ticks_handler.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
                Ok(())
            }
        },
    )
    .await
    .expect("start");

    let deadline = Instant::now() + Duration::from_secs(2);
    loop {
        let n = ticks.load(std::sync::atomic::Ordering::Relaxed);
        if n >= 2 {
            break;
        }
        if Instant::now() >= deadline {
            panic!("processor did not tick");
        }
        tokio::time::sleep(Duration::from_millis(20)).await;
    }

    let status = db
        .reactive_processor_slo_status("slo_ok_processor", ConsistencyMode::AtLatest)
        .await
        .expect("slo status");
    assert!(!status.breached, "expected no SLO breach");

    db.enforce_reactive_processor_slos(ConsistencyMode::AtLatest)
        .await
        .expect("slo enforce should pass");
    db.stop_reactive_processor("slo_ok_processor")
        .await
        .expect("stop");
}

#[tokio::test]
async fn idempotency_prunes_by_commit_window() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(
        AedbConfig {
            idempotency_window_commits: 1,
            ..AedbConfig::default()
        },
        dir.path(),
    )
    .expect("open");
    db.create_project("p").await.expect("project");

    let first = db
        .commit_envelope(TransactionEnvelope {
            caller: None,
            idempotency_key: Some(IdempotencyKey([1u8; 16])),
            write_class: WriteClass::Standard,
            assertions: Vec::new(),
            read_set: crate::commit::tx::ReadSet::default(),
            write_intent: WriteIntent {
                mutations: vec![Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"k".to_vec(),
                    value: b"v1".to_vec(),
                }],
            },
            base_seq: 0,
        })
        .await
        .expect("first commit");

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"other".to_vec(),
        value: b"v2".to_vec(),
    })
    .await
    .expect("advance seq");

    let retried = db
        .commit_envelope(TransactionEnvelope {
            caller: None,
            idempotency_key: Some(IdempotencyKey([1u8; 16])),
            write_class: WriteClass::Standard,
            assertions: Vec::new(),
            read_set: crate::commit::tx::ReadSet::default(),
            write_intent: WriteIntent {
                mutations: vec![Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"k".to_vec(),
                    value: b"v3".to_vec(),
                }],
            },
            base_seq: 0,
        })
        .await
        .expect("retried commit");
    assert!(
        retried.commit_seq > first.commit_seq,
        "idempotency record should expire by sequence window"
    );
}

#[test]
fn wal_segment_gc_reclaims_checkpoint_covered_segments() {
    let dir = tempdir().expect("temp");
    for seq in 1_u64..=4 {
        std::fs::write(
            dir.path().join(format!("segment_{seq:016}.aedbwal")),
            format!("segment-{seq}"),
        )
        .expect("write segment");
    }
    let manifest = crate::manifest::schema::Manifest {
        durable_seq: 50,
        visible_seq: 50,
        active_segment_seq: 4,
        checkpoints: vec![crate::checkpoint::writer::CheckpointMeta {
            filename: "checkpoint_0000000000000050.aedb.zst".into(),
            seq: 50,
            sha256_hex: "00".repeat(32),
            created_at_micros: 1,
            key_id: None,
        }],
        segments: Vec::new(),
    };
    crate::manifest::atomic::write_manifest_atomic_signed(&manifest, dir.path(), None)
        .expect("write manifest");
    let snapshot_manager = Arc::new(parking_lot::Mutex::new(
        crate::snapshot::gc::SnapshotManager::default(),
    ));

    let reclaimed =
        super::reclaim_eligible_wal_segments(dir.path(), &snapshot_manager, None).expect("gc");

    assert_eq!(reclaimed, 3);
    assert!(!dir.path().join("segment_0000000000000001.aedbwal").exists());
    assert!(!dir.path().join("segment_0000000000000002.aedbwal").exists());
    assert!(!dir.path().join("segment_0000000000000003.aedbwal").exists());
    assert!(dir.path().join("segment_0000000000000004.aedbwal").exists());
}

#[tokio::test]
async fn idempotency_prunes_by_time_window() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(
        AedbConfig {
            idempotency_window_commits: 100,
            idempotency_window_seconds: 1,
            ..AedbConfig::default()
        },
        dir.path(),
    )
    .expect("open");
    db.create_project("p").await.expect("project");

    let first = db
        .commit_envelope(TransactionEnvelope {
            caller: None,
            idempotency_key: Some(IdempotencyKey([9u8; 16])),
            write_class: WriteClass::Standard,
            assertions: Vec::new(),
            read_set: crate::commit::tx::ReadSet::default(),
            write_intent: WriteIntent {
                mutations: vec![Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"time".to_vec(),
                    value: b"v1".to_vec(),
                }],
            },
            base_seq: 0,
        })
        .await
        .expect("first commit");

    tokio::time::sleep(Duration::from_millis(1_100)).await;

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"tick".to_vec(),
        value: b"v2".to_vec(),
    })
    .await
    .expect("advance and prune");

    let retried = db
        .commit_envelope(TransactionEnvelope {
            caller: None,
            idempotency_key: Some(IdempotencyKey([9u8; 16])),
            write_class: WriteClass::Standard,
            assertions: Vec::new(),
            read_set: crate::commit::tx::ReadSet::default(),
            write_intent: WriteIntent {
                mutations: vec![Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"time".to_vec(),
                    value: b"v3".to_vec(),
                }],
            },
            base_seq: 0,
        })
        .await
        .expect("retried commit");

    assert!(
        retried.commit_seq > first.commit_seq,
        "idempotency record should expire by time window"
    );
}

#[test]
fn open_rejects_invalid_config() {
    let dir = tempdir().expect("temp");
    let bad = AedbConfig {
        durability_mode: DurabilityMode::Batch,
        batch_interval_ms: 0,
        ..AedbConfig::default()
    };
    let err = AedbInstance::open(bad, dir.path())
        .err()
        .expect("invalid config");
    assert!(matches!(err, crate::error::AedbError::InvalidConfig { .. }));

    let too_large_txn = AedbConfig {
        max_transaction_bytes: crate::wal::frame::MAX_FRAME_BODY_BYTES + 1,
        ..AedbConfig::default()
    };
    let err = AedbInstance::open(too_large_txn, dir.path())
        .err()
        .expect("oversized transaction bound");
    assert!(matches!(err, crate::error::AedbError::InvalidConfig { .. }));

    let deadlock_unsafe = AedbConfig {
        recovery_mode: RecoveryMode::Strict,
        coordinator_locking_enabled: false,
        ..AedbConfig::default()
    };
    let err = AedbInstance::open(deadlock_unsafe, dir.path())
        .err()
        .expect("strict mode must require coordinator locking");
    assert!(matches!(err, crate::error::AedbError::InvalidConfig { .. }));
}

#[tokio::test]
async fn snapshot_limit_enforced_on_read_path() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(
        AedbConfig {
            max_concurrent_snapshots: 1,
            ..AedbConfig::default()
        },
        dir.path(),
    )
    .expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: None,
        },
    }))
    .await
    .expect("grant");
    let caller = CallerContext::new("alice");

    let handle = {
        let mut mgr = db.snapshot_manager.lock();
        mgr.acquire_bounded(
            crate::snapshot::reader::SnapshotReadView {
                keyspace: Arc::new(crate::storage::keyspace::Keyspace::default().snapshot()),
                catalog: Arc::new(crate::catalog::Catalog::default()),
                seq: 0,
            },
            1,
        )
        .expect("occupy")
    };

    let err = db
        .kv_get("p", "app", b"k", ConsistencyMode::AtLatest, &caller)
        .await
        .expect_err("snapshot cap");
    assert!(matches!(
        err,
        crate::query::error::QueryError::SnapshotLimitReached
    ));

    let mut mgr = db.snapshot_manager.lock();
    mgr.release(handle);
    let _ = mgr.gc();
}

#[tokio::test]
async fn metrics_surface_reflects_commits() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    let m = db.metrics();
    assert!(m.commits_total >= 1);
    let op = db.operational_metrics().await;
    assert!(op.commits_total >= 1);
    assert!(op.read_set_conflicts <= op.conflict_rejections);
    assert!(op.queue_depth >= op.inflight_commits);
    assert!(op.snapshot_age_micros <= u64::MAX / 2);
}

#[tokio::test]
async fn checkpoint_now_enables_clean_restart() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("table");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users".into(),
        primary_key: vec![Value::Integer(5)],
        row: Row {
            values: vec![Value::Integer(5), Value::Text("bob".into())],
        },
    })
    .await
    .expect("insert");

    let seq = db.checkpoint_now().await.expect("checkpoint");
    assert!(seq >= 3);

    let reopened = AedbInstance::open(AedbConfig::default(), dir.path()).expect("reopen");
    let rows = reopened
        .query(
            "p",
            "app",
            Query::select(&["*"])
                .from("users")
                .where_(Expr::Eq("id".into(), Value::Integer(5))),
        )
        .await
        .expect("query");
    assert_eq!(rows.rows.len(), 1);
}

#[tokio::test]
async fn checkpoint_now_in_batch_mode_flushes_wal_and_recovers_tail() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        durability_mode: DurabilityMode::Batch,
        batch_interval_ms: 60_000,
        batch_max_bytes: usize::MAX,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config.clone(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"tail".to_vec(),
        value: b"v1".to_vec(),
    })
    .await
    .expect("tail write");

    let before = db.head_state().await;
    assert!(before.visible_head_seq > before.durable_head_seq);

    let cp_seq = db.checkpoint_now().await.expect("checkpoint");
    let after = db.head_state().await;
    assert_eq!(after.visible_head_seq, after.durable_head_seq);
    assert!(after.durable_head_seq >= cp_seq);
    drop(db);

    let recovered = crate::recovery::recover_with_config(dir.path(), &config).expect("recover");
    let tail = recovered
        .keyspace
        .kv_get("p", "app", b"tail")
        .expect("tail recovered");
    assert_eq!(tail.value, b"v1".to_vec());
}

#[tokio::test]
async fn commit_with_visible_finality_can_return_before_durable_head_in_batch_mode() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        durability_mode: DurabilityMode::Batch,
        batch_interval_ms: 60_000,
        batch_max_bytes: usize::MAX,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let result = db
        .commit_with_finality(
            Mutation::KvSet {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: b"fast-visible".to_vec(),
                value: b"v".to_vec(),
            },
            CommitFinality::Visible,
        )
        .await
        .expect("commit");

    assert!(
        result.durable_head_seq < result.commit_seq,
        "visible finality should not require durable head in batch mode"
    );
}

#[tokio::test]
async fn commit_with_durable_finality_waits_until_durable_head_catches_up() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        durability_mode: DurabilityMode::Batch,
        batch_interval_ms: 60_000,
        batch_max_bytes: usize::MAX,
        ..AedbConfig::default()
    };
    let db = Arc::new(AedbInstance::open(config, dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    let fsync_db = Arc::clone(&db);
    let fsync_task = tokio::spawn(async move {
        tokio::time::sleep(Duration::from_millis(20)).await;
        fsync_db.force_fsync().await.expect("force fsync");
    });

    let started = Instant::now();
    let result = db
        .commit_with_finality(
            Mutation::KvSet {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: b"fast-durable".to_vec(),
                value: b"v".to_vec(),
            },
            CommitFinality::Durable,
        )
        .await
        .expect("commit");
    fsync_task.await.expect("join fsync");

    assert!(
        started.elapsed() >= Duration::from_millis(15),
        "durable finality should wait for WAL durability in batch mode"
    );
    assert!(
        result.durable_head_seq >= result.commit_seq,
        "durable finality must report durable head at or beyond commit sequence"
    );
}

#[tokio::test]
async fn order_book_new_with_durable_finality_waits_until_durable_head_catches_up() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        durability_mode: DurabilityMode::Batch,
        batch_interval_ms: 60_000,
        batch_max_bytes: usize::MAX,
        ..AedbConfig::default()
    };
    let db = Arc::new(AedbInstance::open(config, dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    let fsync_db = Arc::clone(&db);
    let fsync_task = tokio::spawn(async move {
        tokio::time::sleep(Duration::from_millis(20)).await;
        fsync_db.force_fsync().await.expect("force fsync");
    });

    let started = Instant::now();
    let result = db
        .order_book_new_with_finality(
            "p",
            "app",
            crate::order_book::OrderRequest {
                instrument: "BTC-USD".into(),
                client_order_id: "oid-1".into(),
                side: crate::order_book::OrderSide::Bid,
                order_type: crate::order_book::OrderType::Limit,
                time_in_force: crate::order_book::TimeInForce::Gtc,
                exec_instructions: crate::order_book::ExecInstruction(0),
                self_trade_prevention: crate::order_book::SelfTradePrevention::None,
                price_ticks: 100,
                qty_be: {
                    let mut out = [0u8; 32];
                    out[31] = 1;
                    out
                },
                owner: "alice".into(),
                account: None,
                nonce: 1,
                price_limit_ticks: None,
            },
            CommitFinality::Durable,
        )
        .await
        .expect("order");
    fsync_task.await.expect("join fsync");

    assert!(
        started.elapsed() >= Duration::from_millis(15),
        "durable finality should wait for WAL durability in batch mode"
    );
    assert!(
        result.durable_head_seq >= result.commit_seq,
        "durable finality must report durable head at or beyond commit sequence"
    );
}

#[tokio::test]
async fn order_book_new_fok_reject_is_dropped_before_wal_append() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    let before = db.operational_metrics().await;

    let err = db
        .order_book_new(
            "p",
            "app",
            crate::order_book::OrderRequest {
                instrument: "BTC-USD".into(),
                client_order_id: "fok-no-liq-1".into(),
                side: crate::order_book::OrderSide::Bid,
                order_type: crate::order_book::OrderType::Limit,
                time_in_force: crate::order_book::TimeInForce::Fok,
                exec_instructions: crate::order_book::ExecInstruction(0),
                self_trade_prevention: crate::order_book::SelfTradePrevention::None,
                price_ticks: 100,
                qty_be: {
                    let mut out = [0u8; 32];
                    out[31] = 1;
                    out
                },
                owner: "alice".into(),
                account: None,
                nonce: 1,
                price_limit_ticks: None,
            },
        )
        .await
        .expect_err("unfillable FOK should reject upstream");
    let after = db.operational_metrics().await;

    match err {
        AedbError::Validation(msg) => assert!(
            msg.contains("fok cannot fill"),
            "unexpected validation message: {msg}"
        ),
        other => panic!("unexpected error variant: {other:?}"),
    }
    assert_eq!(
        after.wal_append_ops, before.wal_append_ops,
        "upstream dropped rejects should not append WAL frames"
    );
    assert_eq!(
        after.wal_append_bytes, before.wal_append_bytes,
        "upstream dropped rejects should not increase WAL append bytes"
    );
}

#[tokio::test]
#[ignore = "long-running finality latency profile"]
async fn finality_profile_visible_vs_durable_low_latency_mode() {
    async fn run_profile(
        config: AedbConfig,
        finality: CommitFinality,
        ops: usize,
    ) -> (u64, u64, u64, crate::OperationalMetrics) {
        let dir = tempdir().expect("temp");
        let db = AedbInstance::open(config, dir.path()).expect("open");
        db.create_project("p").await.expect("project");
        let started = Instant::now();
        let mut lat_sum = 0u128;
        let mut lat_max = 0u64;
        for i in 0..ops {
            let op_started = Instant::now();
            db.commit_with_finality(
                Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: format!("finality:{finality:?}:{i}").into_bytes(),
                    value: i.to_be_bytes().to_vec(),
                },
                finality,
            )
            .await
            .expect("commit with finality");
            let us = op_started.elapsed().as_micros() as u64;
            lat_sum = lat_sum.saturating_add(us as u128);
            lat_max = lat_max.max(us);
        }
        db.force_fsync().await.expect("flush");
        let elapsed = started.elapsed().as_secs_f64().max(0.001);
        let tps = (ops as f64 / elapsed) as u64;
        let avg_us = (lat_sum / ops.max(1) as u128) as u64;
        let op = db.operational_metrics().await;
        (tps, avg_us, lat_max, op)
    }

    let ops = std::env::var("AEDB_FINALITY_PROFILE_OPS")
        .ok()
        .and_then(|v| v.parse::<usize>().ok())
        .unwrap_or(600)
        .max(200);

    let mut low_latency_no_coalesce = AedbConfig::low_latency([1u8; 32]);
    low_latency_no_coalesce.durable_ack_coalescing_enabled = false;
    low_latency_no_coalesce.durable_ack_coalesce_window_us = 0;
    let low_latency_coalesce = AedbConfig::low_latency([1u8; 32]);

    let (visible_tps, visible_avg_us, visible_max_us, visible_op) = run_profile(
        low_latency_no_coalesce.clone(),
        CommitFinality::Visible,
        ops,
    )
    .await;
    let (durable_base_tps, durable_base_avg_us, durable_base_max_us, durable_base_op) =
        run_profile(low_latency_no_coalesce, CommitFinality::Durable, ops).await;
    let (durable_tps, durable_avg_us, durable_max_us, durable_op) =
        run_profile(low_latency_coalesce, CommitFinality::Durable, ops).await;

    eprintln!(
        "finality_profile: ops={} visible_tps={} durable_base_tps={} durable_coalesced_tps={} visible_avg_us={} durable_base_avg_us={} durable_coalesced_avg_us={} visible_max_us={} durable_base_max_us={} durable_coalesced_max_us={} visible_durable_wait_ops={} durable_base_wait_ops={} durable_coalesced_wait_ops={} visible_avg_durable_wait_us={} durable_base_avg_durable_wait_us={} durable_coalesced_avg_durable_wait_us={} visible_wal_sync_ops={} durable_base_wal_sync_ops={} durable_coalesced_wal_sync_ops={} visible_avg_wal_sync_us={} durable_base_avg_wal_sync_us={} durable_coalesced_avg_wal_sync_us={} visible_avg_wal_append_us={} durable_base_avg_wal_append_us={} durable_coalesced_avg_wal_append_us={}",
        ops,
        visible_tps,
        durable_base_tps,
        durable_tps,
        visible_avg_us,
        durable_base_avg_us,
        durable_avg_us,
        visible_max_us,
        durable_base_max_us,
        durable_max_us,
        visible_op.durable_wait_ops,
        durable_base_op.durable_wait_ops,
        durable_op.durable_wait_ops,
        visible_op.avg_durable_wait_micros,
        durable_base_op.avg_durable_wait_micros,
        durable_op.avg_durable_wait_micros,
        visible_op.wal_sync_ops,
        durable_base_op.wal_sync_ops,
        durable_op.wal_sync_ops,
        visible_op.avg_wal_sync_micros,
        durable_base_op.avg_wal_sync_micros,
        durable_op.avg_wal_sync_micros,
        visible_op.avg_wal_append_micros,
        durable_base_op.avg_wal_append_micros,
        durable_op.avg_wal_append_micros
    );

    assert_eq!(
        visible_op.queue_full_rejections, 0,
        "visible finality profile should not saturate queue"
    );
    assert_eq!(
        durable_base_op.queue_full_rejections, 0,
        "durable baseline profile should not saturate queue"
    );
    assert_eq!(
        durable_op.queue_full_rejections, 0,
        "durable coalesced profile should not saturate queue"
    );
    assert_eq!(
        visible_op.timeout_rejections, 0,
        "visible finality profile should not timeout"
    );
    assert_eq!(
        durable_base_op.timeout_rejections, 0,
        "durable baseline profile should not timeout"
    );
    assert_eq!(
        durable_op.timeout_rejections, 0,
        "durable coalesced profile should not timeout"
    );
    assert_eq!(
        visible_op.durable_wait_ops, 0,
        "visible finality profile should not accumulate durable wait operations"
    );
    assert!(
        durable_op.durable_wait_ops > 0,
        "durable finality profile should accumulate durable wait operations"
    );
    assert!(
        durable_tps >= durable_base_tps.saturating_div(2),
        "coalesced durable finality regressed severely: base={durable_base_tps} coalesced={durable_tps}"
    );
    assert!(
        durable_tps <= visible_tps.saturating_mul(2),
        "durable finality profile produced implausible TPS vs visible: visible={visible_tps} durable={durable_tps}"
    );
}

#[tokio::test]
async fn order_book_write_requires_authenticated_caller_in_secure_mode() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open_secure(AedbConfig::production([9u8; 32]), dir.path())
        .expect("open secure");
    let err = db
        .order_book_new(
            "p",
            "app",
            crate::order_book::OrderRequest {
                instrument: "BTC-USD".into(),
                client_order_id: "oid-secure".into(),
                side: crate::order_book::OrderSide::Bid,
                order_type: crate::order_book::OrderType::Limit,
                time_in_force: crate::order_book::TimeInForce::Gtc,
                exec_instructions: crate::order_book::ExecInstruction(0),
                self_trade_prevention: crate::order_book::SelfTradePrevention::None,
                price_ticks: 100,
                qty_be: {
                    let mut out = [0u8; 32];
                    out[31] = 1;
                    out
                },
                owner: "alice".into(),
                account: None,
                nonce: 1,
                price_limit_ticks: None,
            },
        )
        .await
        .expect_err("secure mode should require authenticated caller");
    assert!(matches!(err, AedbError::PermissionDenied(_)));
}

#[tokio::test]
async fn secure_mode_supports_order_book_writes_via_authenticated_as_apis() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open_secure(AedbConfig::production([5u8; 32]), dir.path())
        .expect("open secure");
    db.commit_as(
        CallerContext::system_internal(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "p".into(),
        }),
    )
    .await
    .expect("create project");
    db.commit_as(
        CallerContext::system_internal(),
        Mutation::Ddl(DdlOperation::GrantPermission {
            caller_id: "alice".into(),
            permission: Permission::ProjectAdmin {
                project_id: "p".into(),
            },
            actor_id: Some("system".into()),
            delegable: false,
        }),
    )
    .await
    .expect("grant project admin");

    let alice = CallerContext::new("alice");
    db.order_book_new_as(
        alice.clone(),
        "p",
        "app",
        crate::order_book::OrderRequest {
            instrument: "BTC-USD".into(),
            client_order_id: "oid-secure-as".into(),
            side: crate::order_book::OrderSide::Bid,
            order_type: crate::order_book::OrderType::Limit,
            time_in_force: crate::order_book::TimeInForce::Gtc,
            exec_instructions: crate::order_book::ExecInstruction(0),
            self_trade_prevention: crate::order_book::SelfTradePrevention::None,
            price_ticks: 100,
            qty_be: {
                let mut out = [0u8; 32];
                out[31] = 2;
                out
            },
            owner: "alice".into(),
            account: None,
            nonce: 1,
            price_limit_ticks: None,
        },
    )
    .await
    .expect("place order");
    db.order_book_cancel_as(alice.clone(), "p", "app", "BTC-USD", 1, "alice")
        .await
        .expect("cancel order");
    let status = db
        .order_status("p", "app", "BTC-USD", 1, ConsistencyMode::AtLatest, &alice)
        .await
        .expect("status query")
        .expect("order exists");
    assert_eq!(status.status, crate::order_book::OrderStatus::Cancelled);
}

#[tokio::test]
async fn open_orders_requires_kv_read_permission() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.order_book_new(
        "p",
        "app",
        crate::order_book::OrderRequest {
            instrument: "BTC-USD".into(),
            client_order_id: "cid-open-orders-1".into(),
            side: crate::order_book::OrderSide::Bid,
            order_type: crate::order_book::OrderType::Limit,
            time_in_force: crate::order_book::TimeInForce::Gtc,
            exec_instructions: crate::order_book::ExecInstruction(0),
            self_trade_prevention: crate::order_book::SelfTradePrevention::None,
            price_ticks: 100,
            qty_be: {
                let mut out = [0u8; 32];
                out[31] = 1;
                out
            },
            owner: "alice".into(),
            account: None,
            nonce: 1,
            price_limit_ticks: None,
        },
    )
    .await
    .expect("place order");

    let alice = CallerContext::new("alice");
    let denied = db
        .open_orders(
            "p",
            "app",
            "BTC-USD",
            "alice",
            ConsistencyMode::AtLatest,
            &alice,
        )
        .await
        .expect_err("missing KvRead should be denied");
    assert!(matches!(denied, QueryError::PermissionDenied { .. }));

    db.commit_ddl(DdlOperation::GrantPermission {
        caller_id: "alice".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: Some(b"ob:BTC-USD:".to_vec()),
        },
        actor_id: None,
        delegable: false,
    })
    .await
    .expect("grant kv read");

    let open = db
        .open_orders(
            "p",
            "app",
            "BTC-USD",
            "alice",
            ConsistencyMode::AtLatest,
            &alice,
        )
        .await
        .expect("open orders");
    assert_eq!(open.len(), 1);
    assert_eq!(open[0].owner, "alice");
}

#[tokio::test]
async fn secure_multi_agent_user_perspective_invariants_hold() {
    fn u256_be(v: u64) -> [u8; 32] {
        let mut out = [0u8; 32];
        out[24..].copy_from_slice(&v.to_be_bytes());
        out
    }

    #[allow(clippy::too_many_arguments)]
    fn req(
        instrument: &str,
        owner: &str,
        cid: String,
        side: crate::order_book::OrderSide,
        tif: crate::order_book::TimeInForce,
        price: i64,
        qty: u64,
        nonce: u64,
    ) -> crate::order_book::OrderRequest {
        crate::order_book::OrderRequest {
            instrument: instrument.to_string(),
            client_order_id: cid,
            side,
            order_type: crate::order_book::OrderType::Limit,
            time_in_force: tif,
            exec_instructions: crate::order_book::ExecInstruction(0),
            self_trade_prevention: crate::order_book::SelfTradePrevention::None,
            price_ticks: price,
            qty_be: u256_be(qty),
            owner: owner.to_string(),
            account: None,
            nonce,
            price_limit_ticks: None,
        }
    }

    #[derive(Debug, Default)]
    struct AgentMetrics {
        primary_attempted: usize,
        primary_accepted: usize,
        primary_rejected: usize,
        lifecycle_attempted: usize,
        lifecycle_accepted: usize,
        lifecycle_rejected: usize,
        own_read_checks: usize,
    }

    let dir = tempdir().expect("temp");
    let db = Arc::new(
        AedbInstance::open_secure(AedbConfig::production([6u8; 32]), dir.path())
            .expect("open secure"),
    );
    let system = CallerContext::system_internal();
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "p".into(),
        }),
    )
    .await
    .expect("create project");

    let agents: Vec<String> = (0..8).map(|i| format!("agent_{i}")).collect();
    for a in &agents {
        db.commit_as(
            system.clone(),
            Mutation::Ddl(DdlOperation::GrantPermission {
                caller_id: a.clone(),
                permission: Permission::ProjectAdmin {
                    project_id: "p".into(),
                },
                actor_id: Some("system".into()),
                delegable: false,
            }),
        )
        .await
        .expect("grant project admin");
    }

    db.order_book_set_instrument_config_as(
        system.clone(),
        "p",
        "app",
        "BTC-USD",
        crate::order_book::InstrumentConfig {
            instrument: "BTC-USD".into(),
            tick_size: 1,
            lot_size_be: u256_be(1),
            min_price_ticks: 1,
            max_price_ticks: 1_000_000,
            market_order_price_band: Some(50),
            halted: false,
            balance_config: None,
        },
    )
    .await
    .expect("instrument config");

    for i in 0..16u64 {
        db.order_book_new_as(
            system.clone(),
            "p",
            "app",
            req(
                "BTC-USD",
                &format!("seed_ask_{i}"),
                format!("seed-ask-{i}"),
                crate::order_book::OrderSide::Ask,
                crate::order_book::TimeInForce::Gtc,
                1_000 + i as i64,
                10,
                1,
            ),
        )
        .await
        .expect("seed ask");
        db.order_book_new_as(
            system.clone(),
            "p",
            "app",
            req(
                "BTC-USD",
                &format!("seed_bid_{i}"),
                format!("seed-bid-{i}"),
                crate::order_book::OrderSide::Bid,
                crate::order_book::TimeInForce::Gtc,
                999 - i as i64,
                10,
                1,
            ),
        )
        .await
        .expect("seed bid");
    }

    let mut anchors: Vec<(String, u64)> = Vec::with_capacity(agents.len());
    for (idx, agent_id) in agents.iter().enumerate() {
        let caller = CallerContext::new(agent_id.clone());
        let anchor_cid = format!("anchor-{agent_id}");
        db.order_book_new_as(
            caller.clone(),
            "p",
            "app",
            req(
                "BTC-USD",
                agent_id,
                anchor_cid.clone(),
                if idx % 2 == 0 {
                    crate::order_book::OrderSide::Bid
                } else {
                    crate::order_book::OrderSide::Ask
                },
                crate::order_book::TimeInForce::Gtc,
                if idx % 2 == 0 { 980 } else { 1_020 },
                2,
                1,
            ),
        )
        .await
        .expect("anchor order");
        let own_open = db
            .open_orders(
                "p",
                "app",
                "BTC-USD",
                agent_id,
                ConsistencyMode::AtLatest,
                &caller,
            )
            .await
            .expect("own open orders");
        let anchor = own_open
            .into_iter()
            .find(|o| o.client_order_id == anchor_cid)
            .expect("anchor order discoverable");
        anchors.push((agent_id.clone(), anchor.order_id));
    }

    let mut tasks = Vec::with_capacity(agents.len());
    for (idx, agent_id) in agents.iter().enumerate() {
        let db_clone = Arc::clone(&db);
        let caller = CallerContext::new(agent_id.clone());
        let owner = agent_id.clone();
        tasks.push(tokio::spawn(async move {
            let mut m = AgentMetrics::default();
            let mut nonce = 10u64;
            for op in 0..180usize {
                let side = if (op + idx) % 2 == 0 {
                    crate::order_book::OrderSide::Bid
                } else {
                    crate::order_book::OrderSide::Ask
                };
                let price = if matches!(side, crate::order_book::OrderSide::Bid) {
                    1_001
                } else {
                    998
                };
                m.primary_attempted += 1;
                let res = db_clone
                    .order_book_new_as(
                        caller.clone(),
                        "p",
                        "app",
                        req(
                            "BTC-USD",
                            &owner,
                            format!("{owner}-p-{op}"),
                            side,
                            crate::order_book::TimeInForce::Ioc,
                            price,
                            1 + (op % 4) as u64,
                            nonce,
                        ),
                    )
                    .await;
                nonce += 1;
                match res {
                    Ok(_) => m.primary_accepted += 1,
                    Err(AedbError::Validation(_)) | Err(AedbError::Conflict(_)) => {
                        m.primary_rejected += 1
                    }
                    Err(other) => return Err(other),
                }

                if op % 30 == 0 {
                    m.lifecycle_attempted += 1;
                    let cid = format!("{owner}-l-{op}");
                    let opened = db_clone
                        .order_book_new_as(
                            caller.clone(),
                            "p",
                            "app",
                            req(
                                "BTC-USD",
                                &owner,
                                cid.clone(),
                                crate::order_book::OrderSide::Bid,
                                crate::order_book::TimeInForce::Gtc,
                                970,
                                1,
                                nonce,
                            ),
                        )
                        .await;
                    nonce += 1;
                    match opened {
                        Ok(_) => m.lifecycle_accepted += 1,
                        Err(AedbError::Validation(_)) | Err(AedbError::Conflict(_)) => {
                            m.lifecycle_rejected += 1
                        }
                        Err(other) => return Err(other),
                    }

                    m.lifecycle_attempted += 1;
                    match db_clone
                        .order_book_cancel_by_client_id_as(
                            caller.clone(),
                            "p",
                            "app",
                            "BTC-USD",
                            &cid,
                            &owner,
                        )
                        .await
                    {
                        Ok(_) => m.lifecycle_accepted += 1,
                        Err(AedbError::Validation(_)) | Err(AedbError::Conflict(_)) => {
                            m.lifecycle_rejected += 1
                        }
                        Err(other) => return Err(other),
                    }
                }

                if op % 40 == 0 {
                    let own = db_clone
                        .open_orders(
                            "p",
                            "app",
                            "BTC-USD",
                            &owner,
                            ConsistencyMode::AtLatest,
                            &caller,
                        )
                        .await
                        .map_err(|e| {
                            AedbError::Validation(format!("own open_orders failed: {e}"))
                        })?;
                    assert!(
                        own.iter().all(|o| o.owner == owner),
                        "open_orders must only return owner rows"
                    );
                    m.own_read_checks += 1;
                }
            }
            Ok::<_, AedbError>(m)
        }));
    }

    let mut metrics = Vec::with_capacity(agents.len());
    for task in tasks {
        metrics.push(task.await.expect("join agent task").expect("agent run"));
    }

    for (idx, agent_id) in agents.iter().enumerate() {
        let caller = CallerContext::new(agent_id.clone());
        let (target_owner, target_order_id) = &anchors[(idx + 1) % anchors.len()];

        let err = db
            .order_status(
                "p",
                "app",
                "BTC-USD",
                *target_order_id,
                ConsistencyMode::AtLatest,
                &caller,
            )
            .await
            .expect_err("cross-owner order_status must be denied");
        assert!(
            matches!(err, QueryError::PermissionDenied { .. }),
            "expected permission denied, got {err:?}"
        );

        let err = db
            .open_orders(
                "p",
                "app",
                "BTC-USD",
                target_owner,
                ConsistencyMode::AtLatest,
                &caller,
            )
            .await
            .expect_err("cross-owner open_orders must be denied");
        assert!(
            matches!(err, QueryError::PermissionDenied { .. }),
            "expected permission denied, got {err:?}"
        );
    }

    for m in &metrics {
        assert_eq!(
            m.primary_accepted + m.primary_rejected,
            m.primary_attempted,
            "primary accounting mismatch"
        );
        assert_eq!(
            m.lifecycle_accepted + m.lifecycle_rejected,
            m.lifecycle_attempted,
            "lifecycle accounting mismatch"
        );
        assert!(
            m.own_read_checks > 0,
            "agent should perform own-read checks"
        );
    }
}

#[tokio::test]
async fn commit_success_is_observable_at_its_commit_seq() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let result = db
        .commit(Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: b"inclusion-proof".to_vec(),
            value: b"ok".to_vec(),
        })
        .await
        .expect("commit");

    let at_seq = db
        .kv_get_no_auth(
            "p",
            "app",
            b"inclusion-proof",
            ConsistencyMode::AtSeq(result.commit_seq),
        )
        .await
        .expect("kv_get at seq")
        .expect("value present at commit seq");
    assert_eq!(at_seq.value, b"ok".to_vec());
}

#[tokio::test]
async fn failed_multi_mutation_envelope_is_atomic_and_has_no_partial_effects() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let before = db.head_state().await.visible_head_seq;
    let err = db
        .commit_envelope(TransactionEnvelope {
            caller: None,
            idempotency_key: None,
            write_class: WriteClass::Standard,
            assertions: Vec::new(),
            read_set: Default::default(),
            write_intent: WriteIntent {
                mutations: vec![
                    Mutation::KvSet {
                        project_id: "p".into(),
                        scope_id: "app".into(),
                        key: b"atomic-a".to_vec(),
                        value: b"1".to_vec(),
                    },
                    Mutation::KvDecU256 {
                        project_id: "p".into(),
                        scope_id: "app".into(),
                        key: b"missing-counter".to_vec(),
                        amount_be: {
                            let mut out = [0u8; 32];
                            out[31] = 1;
                            out
                        },
                    },
                ],
            },
            base_seq: before,
        })
        .await
        .expect_err("envelope should fail");
    assert!(
        matches!(
            err,
            AedbError::Underflow | AedbError::Validation(_) | AedbError::Conflict(_)
        ),
        "expected semantic failure, got: {err:?}"
    );

    let after = db.head_state().await.visible_head_seq;
    assert_eq!(after, before, "failed envelope must not advance head");
    let leaked = db
        .kv_get_no_auth("p", "app", b"atomic-a", ConsistencyMode::AtLatest)
        .await
        .expect("kv_get")
        .is_some();
    assert!(
        !leaked,
        "failed envelope must not partially apply mutations"
    );
}

fn u256_be_test(v: u64) -> [u8; 32] {
    let mut out = [0u8; 32];
    out[24..].copy_from_slice(&v.to_be_bytes());
    out
}

fn u64_be_test(v: u64) -> [u8; 8] {
    v.to_be_bytes()
}

#[tokio::test]
async fn action_envelope_applied_once() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let result = db
        .commit_action_envelope(ActionEnvelopeRequest {
            caller: None,
            idempotency_key: IdempotencyKey([1u8; 16]),
            write_class: WriteClass::Standard,
            base_seq: 0,
            assertions: Vec::new(),
            mutations: vec![
                Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"action:flag".to_vec(),
                    value: b"ok".to_vec(),
                },
                Mutation::KvAddU256Ex {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"action:counter".to_vec(),
                    amount_be: u256_be_test(3),
                    on_missing: KvU256MissingPolicy::TreatAsZero,
                    on_overflow: crate::commit::validation::KvU256OverflowPolicy::Reject,
                },
            ],
        })
        .await
        .expect("action commit");

    assert_eq!(result.outcome, ActionCommitOutcome::Applied);
    let flag = db
        .kv_get_no_auth("p", "app", b"action:flag", ConsistencyMode::AtLatest)
        .await
        .expect("flag read")
        .expect("flag exists");
    assert_eq!(flag.value, b"ok".to_vec());
}

#[tokio::test]
async fn action_envelope_duplicate_returns_duplicate_outcome() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let req = ActionEnvelopeRequest {
        caller: None,
        idempotency_key: IdempotencyKey([2u8; 16]),
        write_class: WriteClass::Standard,
        base_seq: 0,
        assertions: Vec::new(),
        mutations: vec![Mutation::KvAddU256Ex {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: b"action:dup-counter".to_vec(),
            amount_be: u256_be_test(1),
            on_missing: KvU256MissingPolicy::TreatAsZero,
            on_overflow: crate::commit::validation::KvU256OverflowPolicy::Reject,
        }],
    };

    let first = db
        .commit_action_envelope(req.clone())
        .await
        .expect("first action");
    let second = db
        .commit_action_envelope(req)
        .await
        .expect("duplicate action");

    assert_eq!(first.outcome, ActionCommitOutcome::Applied);
    assert_eq!(second.outcome, ActionCommitOutcome::Duplicate);
    assert_eq!(second.commit_seq, first.commit_seq);

    let counter = db
        .kv_get_no_auth("p", "app", b"action:dup-counter", ConsistencyMode::AtLatest)
        .await
        .expect("counter read")
        .expect("counter exists");
    assert_eq!(
        primitive_types::U256::from_big_endian(&counter.value),
        primitive_types::U256::one(),
    );
}

#[tokio::test]
async fn kv_sub_u256_soft_noop() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.commit(Mutation::KvSubU256Ex {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"soft-noop".to_vec(),
        amount_be: u256_be_test(5),
        on_missing: KvU256MissingPolicy::TreatAsZero,
        on_underflow: KvU256UnderflowPolicy::NoOp,
    })
    .await
    .expect("soft noop");

    let entry = db
        .kv_get_no_auth("p", "app", b"soft-noop", ConsistencyMode::AtLatest)
        .await
        .expect("read");
    assert!(
        entry.is_none(),
        "no-op decrement must not create/update key"
    );
}

#[tokio::test]
async fn kv_sub_u256_strict_reject() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let before = db.head_state().await.visible_head_seq;
    let err = db
        .commit(Mutation::KvSubU256Ex {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: b"strict-reject".to_vec(),
            amount_be: u256_be_test(1),
            on_missing: KvU256MissingPolicy::TreatAsZero,
            on_underflow: KvU256UnderflowPolicy::Reject,
        })
        .await
        .expect_err("strict underflow must reject");
    assert!(
        matches!(
            err,
            AedbError::Underflow | AedbError::Validation(_) | AedbError::Conflict(_)
        ),
        "expected strict reject failure, got {err:?}"
    );
    assert_eq!(db.head_state().await.visible_head_seq, before);
}

#[tokio::test]
async fn kv_max_min_u256() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"maxmin".to_vec(),
        value: u256_be_test(10).to_vec(),
    })
    .await
    .expect("seed");

    db.commit(Mutation::KvMaxU256 {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"maxmin".to_vec(),
        candidate_be: u256_be_test(12),
        on_missing: KvU256MissingPolicy::TreatAsZero,
    })
    .await
    .expect("max");

    db.commit(Mutation::KvMinU256 {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"maxmin".to_vec(),
        candidate_be: u256_be_test(3),
        on_missing: KvU256MissingPolicy::TreatAsZero,
    })
    .await
    .expect("min");

    let entry = db
        .kv_get_no_auth("p", "app", b"maxmin", ConsistencyMode::AtLatest)
        .await
        .expect("read")
        .expect("present");
    assert_eq!(
        primitive_types::U256::from_big_endian(&entry.value),
        primitive_types::U256::from(3u64)
    );
}

#[tokio::test]
async fn kv_sub_u64_soft_noop() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.commit(Mutation::KvSubU64Ex {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"soft-noop-u64".to_vec(),
        amount_be: u64_be_test(5),
        on_missing: KvU64MissingPolicy::TreatAsZero,
        on_underflow: KvU64UnderflowPolicy::NoOp,
    })
    .await
    .expect("soft noop");

    let entry = db
        .kv_get_no_auth("p", "app", b"soft-noop-u64", ConsistencyMode::AtLatest)
        .await
        .expect("read");
    assert!(
        entry.is_none(),
        "no-op decrement must not create/update key"
    );
}

#[tokio::test]
async fn kv_sub_u64_strict_reject() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let before = db.head_state().await.visible_head_seq;
    let err = db
        .commit(Mutation::KvSubU64Ex {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: b"strict-reject-u64".to_vec(),
            amount_be: u64_be_test(1),
            on_missing: KvU64MissingPolicy::TreatAsZero,
            on_underflow: KvU64UnderflowPolicy::Reject,
        })
        .await
        .expect_err("strict underflow must reject");
    assert!(
        matches!(
            err,
            AedbError::Underflow | AedbError::Validation(_) | AedbError::Conflict(_)
        ),
        "expected strict reject failure, got {err:?}"
    );
    assert_eq!(db.head_state().await.visible_head_seq, before);
}

#[tokio::test]
async fn kv_sub_int_ex_supports_u64() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"generic-u64".to_vec(),
        value: u64_be_test(10).to_vec(),
    })
    .await
    .expect("seed");

    db.commit(Mutation::KvSubIntEx {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"generic-u64".to_vec(),
        amount: KvIntegerAmount::U64(u64_be_test(4)),
        on_missing: KvIntegerMissingPolicy::Reject,
        on_underflow: KvIntegerUnderflowPolicy::Reject,
    })
    .await
    .expect("sub");

    let entry = db
        .kv_get_no_auth("p", "app", b"generic-u64", ConsistencyMode::AtLatest)
        .await
        .expect("read")
        .expect("present");
    let final_value = u64::from_be_bytes(entry.value.try_into().expect("u64 bytes"));
    assert_eq!(final_value, 6);
}

#[tokio::test]
async fn kv_sub_int_ex_supports_u256_noop_on_underflow() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.commit(Mutation::KvSubIntEx {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"generic-u256".to_vec(),
        amount: KvIntegerAmount::U256(u256_be_test(3)),
        on_missing: KvIntegerMissingPolicy::TreatAsZero,
        on_underflow: KvIntegerUnderflowPolicy::NoOp,
    })
    .await
    .expect("noop");

    let entry = db
        .kv_get_no_auth("p", "app", b"generic-u256", ConsistencyMode::AtLatest)
        .await
        .expect("read");
    assert!(entry.is_none(), "u256 no-op decrement must not create key");
}

#[tokio::test]
async fn kv_max_min_u64() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"maxmin-u64".to_vec(),
        value: u64_be_test(10).to_vec(),
    })
    .await
    .expect("seed");

    db.commit(Mutation::KvMaxU64 {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"maxmin-u64".to_vec(),
        candidate_be: u64_be_test(12),
        on_missing: KvU64MissingPolicy::TreatAsZero,
    })
    .await
    .expect("max");

    db.commit(Mutation::KvMinU64 {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"maxmin-u64".to_vec(),
        candidate_be: u64_be_test(3),
        on_missing: KvU64MissingPolicy::TreatAsZero,
    })
    .await
    .expect("min");

    let entry = db
        .kv_get_no_auth("p", "app", b"maxmin-u64", ConsistencyMode::AtLatest)
        .await
        .expect("read")
        .expect("present");
    let final_value = u64::from_be_bytes(entry.value.try_into().expect("u64 bytes"));
    assert_eq!(final_value, 3u64);
}

#[tokio::test]
async fn counter_add_and_read_sharded_respects_snapshot_consistency() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    for shard_hint in 0..32u32 {
        db.counter_add_sharded(
            "p",
            "app",
            b"ctr:orders".to_vec(),
            u64_be_test(1),
            8,
            shard_hint,
        )
        .await
        .expect("counter add");
    }
    let mid_seq = db.head_state().await.visible_head_seq;

    for shard_hint in 32..64u32 {
        db.counter_add_sharded(
            "p",
            "app",
            b"ctr:orders".to_vec(),
            u64_be_test(1),
            8,
            shard_hint,
        )
        .await
        .expect("counter add");
    }

    let at_mid = db
        .counter_read_sharded(
            "p",
            "app",
            b"ctr:orders",
            8,
            ConsistencyMode::AtSeq(mid_seq),
        )
        .await
        .expect("counter read at seq");
    assert_eq!(at_mid, 32);

    let at_latest = db
        .counter_read_sharded("p", "app", b"ctr:orders", 8, ConsistencyMode::AtLatest)
        .await
        .expect("counter read latest");
    assert_eq!(at_latest, 64);
}

#[tokio::test]
async fn counter_shard_count_validation_is_enforced() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let err_zero = db
        .counter_add_sharded("p", "app", b"ctr:invalid".to_vec(), u64_be_test(1), 0, 1)
        .await
        .expect_err("zero shard_count must reject");
    assert!(matches!(err_zero, AedbError::Validation(_)));

    let err_too_many = db
        .counter_add_sharded(
            "p",
            "app",
            b"ctr:invalid".to_vec(),
            u64_be_test(1),
            MAX_COUNTER_SHARDS + 1,
            1,
        )
        .await
        .expect_err("oversized shard_count must reject");
    assert!(matches!(err_too_many, AedbError::Validation(_)));

    let read_err = db
        .counter_read_sharded("p", "app", b"ctr:invalid", 0, ConsistencyMode::AtLatest)
        .await
        .expect_err("zero shard_count read must reject");
    assert!(matches!(read_err, AedbError::Validation(_)));
}

#[tokio::test]
async fn single_envelope_atomicity() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"guard".to_vec(),
        value: b"1".to_vec(),
    })
    .await
    .expect("guard seed");

    let before = db.head_state().await.visible_head_seq;
    let err = db
        .commit_action_envelope(ActionEnvelopeRequest {
            caller: None,
            idempotency_key: IdempotencyKey([3u8; 16]),
            write_class: WriteClass::Standard,
            base_seq: before,
            assertions: vec![ReadAssertion::KeyExists {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: b"guard".to_vec(),
                expected: false,
            }],
            mutations: vec![
                Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"atomic:x".to_vec(),
                    value: b"x".to_vec(),
                },
                Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"atomic:y".to_vec(),
                    value: b"y".to_vec(),
                },
            ],
        })
        .await
        .expect_err("assertion failure");
    assert!(matches!(err, AedbError::AssertionFailed { .. }));
    assert!(
        db.head_state().await.visible_head_seq >= before,
        "assertion failure may emit an audit commit, but data mutations must not apply"
    );
    assert!(
        db.kv_get_no_auth("p", "app", b"atomic:x", ConsistencyMode::AtLatest)
            .await
            .expect("read x")
            .is_none()
    );
    assert!(
        db.kv_get_no_auth("p", "app", b"atomic:y", ConsistencyMode::AtLatest)
            .await
            .expect("read y")
            .is_none()
    );
}

#[tokio::test]
async fn idempotent_retry_does_not_double_apply_non_idempotent_mutation() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    let key = IdempotencyKey([42u8; 16]);
    let mut tasks = Vec::new();
    for _ in 0..8 {
        let db = Arc::clone(&db);
        let key = key.clone();
        tasks.push(tokio::spawn(async move {
            db.commit_envelope(TransactionEnvelope {
                caller: None,
                idempotency_key: Some(key),
                write_class: WriteClass::Standard,
                assertions: Vec::new(),
                read_set: Default::default(),
                write_intent: WriteIntent {
                    mutations: vec![Mutation::KvIncU256 {
                        project_id: "p".into(),
                        scope_id: "app".into(),
                        key: b"idem-counter".to_vec(),
                        amount_be: {
                            let mut out = [0u8; 32];
                            out[31] = 1;
                            out
                        },
                    }],
                },
                base_seq: 0,
            })
            .await
            .expect("idempotent commit")
        }));
    }

    let mut seqs = std::collections::BTreeSet::new();
    let mut outcomes = Vec::new();
    for t in tasks {
        let res = t.await.expect("join");
        seqs.insert(res.commit_seq);
        outcomes.push(res.idempotency);
    }
    assert_eq!(seqs.len(), 1, "all retries must resolve to one commit_seq");
    assert!(
        outcomes
            .iter()
            .any(|o| matches!(o, crate::commit::executor::IdempotencyOutcome::Duplicate)),
        "at least one retry should report duplicate outcome"
    );

    let entry = db
        .kv_get_no_auth("p", "app", b"idem-counter", ConsistencyMode::AtLatest)
        .await
        .expect("kv_get")
        .expect("counter exists");
    assert_eq!(
        primitive_types::U256::from_big_endian(&entry.value),
        primitive_types::U256::one(),
        "idempotent retries must apply mutation exactly once"
    );
}

#[tokio::test]
async fn retry_idempotency_is_exactly_once_under_commit_pressure() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        commit_timeout_ms: 1,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let key = IdempotencyKey([91u8; 16]);
    let envelope = TransactionEnvelope {
        caller: None,
        idempotency_key: Some(key),
        write_class: WriteClass::Standard,
        assertions: Vec::new(),
        read_set: Default::default(),
        write_intent: WriteIntent {
            mutations: vec![
                Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"__slow_parallel_worker__".to_vec(),
                    value: b"slow".to_vec(),
                },
                Mutation::KvIncU256 {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"timeout-idem-counter".to_vec(),
                    amount_be: {
                        let mut out = [0u8; 32];
                        out[31] = 1;
                        out
                    },
                },
            ],
        },
        base_seq: 0,
    };

    let noisy_db = Arc::new(db);
    let mut noise_tasks = Vec::new();
    for worker in 0..8 {
        let db_clone = Arc::clone(&noisy_db);
        noise_tasks.push(tokio::spawn(async move {
            for i in 0..200usize {
                let _ = db_clone
                    .commit(Mutation::KvSet {
                        project_id: "p".into(),
                        scope_id: "app".into(),
                        key: format!("noise:{worker}:{i}").into_bytes(),
                        value: b"n".to_vec(),
                    })
                    .await;
            }
        }));
    }

    let mut saw_timeout = false;
    let second = loop {
        match noisy_db.commit_envelope(envelope.clone()).await {
            Ok(result) => break result,
            Err(AedbError::Timeout) => {
                saw_timeout = true;
                tokio::time::sleep(Duration::from_millis(5)).await;
            }
            Err(other) => panic!("unexpected error during retry loop: {other:?}"),
        }
    };
    for t in noise_tasks {
        t.await.expect("join noise worker");
    }
    noisy_db
        .wait_for_durable(second.commit_seq)
        .await
        .expect("durable ack");

    let third = noisy_db
        .commit_envelope(envelope)
        .await
        .expect("repeat idempotent retry");
    assert_eq!(
        third.commit_seq, second.commit_seq,
        "all retries must resolve to one commit sequence"
    );

    let counter = noisy_db
        .kv_get_no_auth(
            "p",
            "app",
            b"timeout-idem-counter",
            ConsistencyMode::AtLatest,
        )
        .await
        .expect("kv counter")
        .expect("counter exists");
    assert_eq!(
        primitive_types::U256::from_big_endian(&counter.value),
        primitive_types::U256::one(),
        "counter must be applied once"
    );

    let op = noisy_db.operational_metrics().await;
    if saw_timeout {
        assert!(
            op.timeout_rejections >= 1,
            "timeout path should be observable in operational metrics"
        );
    }
}

#[tokio::test]
async fn strict_cancel_rejects_missing_order() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let err = db
        .order_book_cancel_strict(
            "p",
            "app",
            "BTC-USD",
            999_999,
            "alice",
            CommitFinality::Visible,
        )
        .await
        .expect_err("strict cancel should fail when target is missing");
    assert!(matches!(err, AedbError::Validation(_)));
}

#[tokio::test]
async fn strict_cancel_rejects_already_final_order() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.order_book_new(
        "p",
        "app",
        crate::order_book::OrderRequest {
            instrument: "BTC-USD".into(),
            client_order_id: "strict-final".into(),
            side: crate::order_book::OrderSide::Bid,
            order_type: crate::order_book::OrderType::Limit,
            time_in_force: crate::order_book::TimeInForce::Gtc,
            exec_instructions: crate::order_book::ExecInstruction(0),
            self_trade_prevention: crate::order_book::SelfTradePrevention::None,
            price_ticks: 100,
            qty_be: {
                let mut out = [0u8; 32];
                out[31] = 1;
                out
            },
            owner: "alice".into(),
            account: None,
            nonce: 1,
            price_limit_ticks: None,
        },
    )
    .await
    .expect("place order");

    db.order_book_cancel_strict("p", "app", "BTC-USD", 1, "alice", CommitFinality::Visible)
        .await
        .expect("first strict cancel");

    let err = db
        .order_book_cancel_strict("p", "app", "BTC-USD", 1, "alice", CommitFinality::Visible)
        .await
        .expect_err("second strict cancel should fail on already-cancelled order");
    assert!(matches!(err, AedbError::Validation(_)));
}

#[tokio::test]
async fn strict_cancel_by_client_id_rejects_missing_mapping() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let err = db
        .order_book_cancel_by_client_id_strict(
            "p",
            "app",
            "BTC-USD",
            "missing-client-order-id",
            "alice",
            CommitFinality::Visible,
        )
        .await
        .expect_err("strict cancel by client id should fail when mapping is missing");
    assert!(matches!(err, AedbError::Validation(_)));
}

#[tokio::test]
async fn strict_cancel_by_client_id_rejects_already_final_order() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.order_book_new(
        "p",
        "app",
        crate::order_book::OrderRequest {
            instrument: "BTC-USD".into(),
            client_order_id: "strict-client-final".into(),
            side: crate::order_book::OrderSide::Bid,
            order_type: crate::order_book::OrderType::Limit,
            time_in_force: crate::order_book::TimeInForce::Gtc,
            exec_instructions: crate::order_book::ExecInstruction(0),
            self_trade_prevention: crate::order_book::SelfTradePrevention::None,
            price_ticks: 100,
            qty_be: {
                let mut out = [0u8; 32];
                out[31] = 1;
                out
            },
            owner: "alice".into(),
            account: None,
            nonce: 1,
            price_limit_ticks: None,
        },
    )
    .await
    .expect("place order");

    db.order_book_cancel_by_client_id_strict(
        "p",
        "app",
        "BTC-USD",
        "strict-client-final",
        "alice",
        CommitFinality::Visible,
    )
    .await
    .expect("first strict cancel by client id");

    let err = db
        .order_book_cancel_by_client_id_strict(
            "p",
            "app",
            "BTC-USD",
            "strict-client-final",
            "alice",
            CommitFinality::Visible,
        )
        .await
        .expect_err("second strict cancel by client id should fail on finalized order");
    assert!(matches!(err, AedbError::Validation(_)));
}

#[tokio::test]
async fn strict_cancel_by_client_id_rejects_invalid_mapping_encoding() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: crate::order_book::key_client_id("BTC-USD", "alice", "cid-corrupt"),
        value: vec![1, 2, 3, 4],
    })
    .await
    .expect("inject corrupted mapping");

    let err = db
        .order_book_cancel_by_client_id_strict(
            "p",
            "app",
            "BTC-USD",
            "cid-corrupt",
            "alice",
            CommitFinality::Visible,
        )
        .await
        .expect_err("strict cancel by client id should reject malformed mapping");
    assert!(matches!(err, AedbError::Validation(_)));
}

#[tokio::test]
async fn strict_cancel_by_client_id_detects_owner_mismatch_under_tampered_mapping() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.order_book_new(
        "p",
        "app",
        crate::order_book::OrderRequest {
            instrument: "BTC-USD".into(),
            client_order_id: "cid-owner-a".into(),
            side: crate::order_book::OrderSide::Bid,
            order_type: crate::order_book::OrderType::Limit,
            time_in_force: crate::order_book::TimeInForce::Gtc,
            exec_instructions: crate::order_book::ExecInstruction(0),
            self_trade_prevention: crate::order_book::SelfTradePrevention::None,
            price_ticks: 100,
            qty_be: {
                let mut out = [0u8; 32];
                out[31] = 1;
                out
            },
            owner: "alice".into(),
            account: None,
            nonce: 1,
            price_limit_ticks: None,
        },
    )
    .await
    .expect("place order");

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: crate::order_book::key_client_id("BTC-USD", "bob", "cid-owner-b"),
        value: 1u64.to_be_bytes().to_vec(),
    })
    .await
    .expect("inject tampered mapping");

    let err = db
        .order_book_cancel_by_client_id_strict(
            "p",
            "app",
            "BTC-USD",
            "cid-owner-b",
            "bob",
            CommitFinality::Visible,
        )
        .await
        .expect_err("strict cancel by client id should reject owner mismatch");
    assert!(matches!(err, AedbError::PermissionDenied(_)));
}

#[tokio::test]
async fn strict_reduce_rejects_missing_order() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    let mut one = [0u8; 32];
    one[31] = 1;
    let err = db
        .order_book_reduce_strict(
            "p",
            "app",
            "BTC-USD",
            777,
            "alice",
            one,
            CommitFinality::Visible,
        )
        .await
        .expect_err("strict reduce should fail on missing order");
    assert!(matches!(err, AedbError::Validation(_)));
}

#[tokio::test]
async fn strict_cancel_replace_rejects_already_final_order() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    db.order_book_new(
        "p",
        "app",
        crate::order_book::OrderRequest {
            instrument: "BTC-USD".into(),
            client_order_id: "strict-cr-final".into(),
            side: crate::order_book::OrderSide::Bid,
            order_type: crate::order_book::OrderType::Limit,
            time_in_force: crate::order_book::TimeInForce::Gtc,
            exec_instructions: crate::order_book::ExecInstruction(0),
            self_trade_prevention: crate::order_book::SelfTradePrevention::None,
            price_ticks: 100,
            qty_be: {
                let mut out = [0u8; 32];
                out[31] = 1;
                out
            },
            owner: "alice".into(),
            account: None,
            nonce: 1,
            price_limit_ticks: None,
        },
    )
    .await
    .expect("place order");
    db.order_book_cancel_strict("p", "app", "BTC-USD", 1, "alice", CommitFinality::Visible)
        .await
        .expect("cancel");

    let err = db
        .order_book_cancel_replace_strict(
            "p",
            "app",
            "BTC-USD",
            1,
            "alice",
            Some(101),
            None,
            None,
            None,
            CommitFinality::Visible,
        )
        .await
        .expect_err("strict cancel-replace should fail on finalized order");
    assert!(matches!(err, AedbError::Validation(_)));
}

#[tokio::test]
async fn multi_update_transaction_envelope_updates_table_and_kv() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        durability_mode: DurabilityMode::Batch,
        batch_interval_ms: 60_000,
        batch_max_bytes: usize::MAX,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "accounts".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "balance".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("accounts table");

    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "accounts".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row {
            values: vec![Value::Integer(1), Value::Integer(100)],
        },
    })
    .await
    .expect("seed account 1");
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "accounts".into(),
        primary_key: vec![Value::Integer(2)],
        row: Row {
            values: vec![Value::Integer(2), Value::Integer(80)],
        },
    })
    .await
    .expect("seed account 2");

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        actor_id: None,
        delegable: false,
        caller_id: "reader".into(),
        permission: Permission::KvRead {
            project_id: "p".into(),
            scope_id: Some("app".into()),
            prefix: None,
        },
    }))
    .await
    .expect("grant kv read");
    let caller = CallerContext::new("reader");

    let result = db
        .commit_envelope_with_finality(
            TransactionEnvelope {
                caller: None,
                idempotency_key: None,
                write_class: WriteClass::Standard,
                assertions: Vec::new(),
                read_set: Default::default(),
                write_intent: WriteIntent {
                    mutations: vec![
                        Mutation::Upsert {
                            project_id: "p".into(),
                            scope_id: "app".into(),
                            table_name: "accounts".into(),
                            primary_key: vec![Value::Integer(1)],
                            row: Row {
                                values: vec![Value::Integer(1), Value::Integer(90)],
                            },
                        },
                        Mutation::Upsert {
                            project_id: "p".into(),
                            scope_id: "app".into(),
                            table_name: "accounts".into(),
                            primary_key: vec![Value::Integer(2)],
                            row: Row {
                                values: vec![Value::Integer(2), Value::Integer(90)],
                            },
                        },
                        Mutation::KvSet {
                            project_id: "p".into(),
                            scope_id: "app".into(),
                            key: b"tx:last".to_vec(),
                            value: b"t1".to_vec(),
                        },
                        Mutation::KvSet {
                            project_id: "p".into(),
                            scope_id: "app".into(),
                            key: b"ledger:1->2".to_vec(),
                            value: b"10".to_vec(),
                        },
                    ],
                },
                base_seq: db.head_state().await.visible_head_seq,
            },
            CommitFinality::Visible,
        )
        .await
        .expect("multi-update tx");

    assert!(
        result.durable_head_seq < result.commit_seq,
        "visible finality should return before durable head in batch mode"
    );

    let acct1 = db
        .query(
            "p",
            "app",
            Query::select(&["balance"])
                .from("accounts")
                .where_(Expr::Eq("id".into(), Value::Integer(1)))
                .limit(1),
        )
        .await
        .expect("query account1");
    let acct2 = db
        .query(
            "p",
            "app",
            Query::select(&["balance"])
                .from("accounts")
                .where_(Expr::Eq("id".into(), Value::Integer(2)))
                .limit(1),
        )
        .await
        .expect("query account2");
    assert_eq!(acct1.rows.len(), 1);
    assert_eq!(acct2.rows.len(), 1);
    assert_eq!(acct1.rows[0].values[0], Value::Integer(90));
    assert_eq!(acct2.rows[0].values[0], Value::Integer(90));

    let tx_last = db
        .kv_get("p", "app", b"tx:last", ConsistencyMode::AtLatest, &caller)
        .await
        .expect("tx:last");
    let ledger = db
        .kv_get(
            "p",
            "app",
            b"ledger:1->2",
            ConsistencyMode::AtLatest,
            &caller,
        )
        .await
        .expect("ledger entry");
    assert_eq!(
        tx_last.as_ref().map(|v| v.value.clone()),
        Some(b"t1".to_vec())
    );
    assert_eq!(
        ledger.as_ref().map(|v| v.value.clone()),
        Some(b"10".to_vec())
    );
}

#[tokio::test]
async fn transaction_envelope_can_commit_table_kv_and_integer_atomically() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::Ddl(DdlOperation::CreateTable {
        owner_id: None,
        if_not_exists: false,
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "ledger".into(),
        columns: vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "amount".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "status".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        primary_key: vec!["id".into()],
    }))
    .await
    .expect("ledger table");
    let envelope = TransactionEnvelope {
        caller: None,
        idempotency_key: Some(IdempotencyKey([11u8; 16])),
        write_class: WriteClass::Standard,
        assertions: Vec::new(),
        read_set: Default::default(),
        write_intent: WriteIntent {
            mutations: vec![
                Mutation::Upsert {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    table_name: "ledger".into(),
                    primary_key: vec![Value::Integer(1)],
                    row: Row::from_values(vec![
                        Value::Integer(1),
                        Value::Integer(75),
                        Value::Text("posted".into()),
                    ]),
                },
                Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"ledger:last".to_vec(),
                    value: b"1".to_vec(),
                },
                Mutation::KvAddU64Ex {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"house_balance".to_vec(),
                    amount_be: 75u64.to_be_bytes(),
                    on_missing: KvU64MissingPolicy::TreatAsZero,
                    on_overflow: KvU64OverflowPolicy::Reject,
                },
            ],
        },
        base_seq: db.head_state().await.visible_head_seq,
    };

    let result = db
        .commit_envelope(envelope.clone())
        .await
        .expect("mixed transaction envelope");
    assert!(
        result.commit_seq > 0,
        "transaction must advance the commit sequence"
    );

    let row = db
        .query(
            "p",
            "app",
            Query::select(&["amount", "status"])
                .from("ledger")
                .where_(Expr::Eq("id".into(), Value::Integer(1)))
                .limit(1),
        )
        .await
        .expect("query ledger row");
    assert_eq!(row.rows.len(), 1);
    assert_eq!(row.rows[0].values[0], Value::Integer(75));
    assert_eq!(row.rows[0].values[1], Value::Text("posted".into()));

    let marker = db
        .kv_get_no_auth("p", "app", b"ledger:last", ConsistencyMode::AtLatest)
        .await
        .expect("read kv marker");
    assert_eq!(
        marker.as_ref().map(|entry| entry.value.as_slice()),
        Some(b"1".as_slice())
    );

    let balance = db
        .kv_get_no_auth("p", "app", b"house_balance", ConsistencyMode::AtLatest)
        .await
        .expect("read integer balance")
        .expect("integer balance exists");
    let balance_bytes: [u8; 8] = balance
        .value
        .as_slice()
        .try_into()
        .expect("u64 balance bytes");
    assert_eq!(u64::from_be_bytes(balance_bytes), 75);

    let replay = db
        .commit_envelope(envelope)
        .await
        .expect("idempotent replay");
    assert_eq!(
        replay.idempotency,
        crate::commit::executor::IdempotencyOutcome::Duplicate,
        "second commit must be treated as replay"
    );

    let stable_row = db
        .query(
            "p",
            "app",
            Query::select(&["amount", "status"])
                .from("ledger")
                .where_(Expr::Eq("id".into(), Value::Integer(1)))
                .limit(1),
        )
        .await
        .expect("query stable row");
    assert_eq!(stable_row.rows[0].values[0], Value::Integer(75));
    assert_eq!(stable_row.rows[0].values[1], Value::Text("posted".into()));
    let stable_marker = db
        .kv_get_no_auth("p", "app", b"ledger:last", ConsistencyMode::AtLatest)
        .await
        .expect("read stable marker");
    assert_eq!(
        stable_marker.as_ref().map(|entry| entry.value.as_slice()),
        Some(b"1".as_slice())
    );
    let stable_balance = db
        .kv_get_no_auth("p", "app", b"house_balance", ConsistencyMode::AtLatest)
        .await
        .expect("read stable integer balance")
        .expect("stable integer balance exists");
    let stable_balance_bytes: [u8; 8] = stable_balance
        .value
        .as_slice()
        .try_into()
        .expect("stable u64 balance bytes");
    assert_eq!(u64::from_be_bytes(stable_balance_bytes), 75);
}

#[tokio::test]
async fn checkpoint_now_allows_commits_while_running() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    // Build enough state to make checkpoint work measurable.
    for i in 0..2_000u32 {
        db.commit(Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: format!("seed:{i:06}").into_bytes(),
            value: vec![b'x'; 1024],
        })
        .await
        .expect("seed write");
    }

    let checkpoint_db = Arc::clone(&db);
    let checkpoint_task = tokio::spawn(async move { checkpoint_db.checkpoint_now().await });

    // Wait briefly for checkpoint to begin.
    for _ in 0..10 {
        if !checkpoint_task.is_finished() {
            break;
        }
        tokio::time::sleep(std::time::Duration::from_millis(1)).await;
    }
    assert!(
        !checkpoint_task.is_finished(),
        "checkpoint should still be running"
    );

    let commit = db
        .commit(Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: b"concurrent:write".to_vec(),
            value: b"ok".to_vec(),
        })
        .await
        .expect("commit should proceed during checkpoint");
    let checkpoint_seq = checkpoint_task
        .await
        .expect("checkpoint join")
        .expect("checkpoint");
    assert!(commit.commit_seq >= checkpoint_seq);
}

#[tokio::test]
async fn checkpoint_now_serializes_checkpoint_writers() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"k".to_vec(),
        value: b"v".to_vec(),
    })
    .await
    .expect("seed");

    let db1 = Arc::clone(&db);
    let db2 = Arc::clone(&db);
    let t1 = tokio::spawn(async move { db1.checkpoint_now().await });
    let t2 = tokio::spawn(async move { db2.checkpoint_now().await });

    let s1 = t1.await.expect("checkpoint task 1").expect("checkpoint 1");
    let s2 = t2.await.expect("checkpoint task 2").expect("checkpoint 2");
    assert_eq!(s1, s2);
}

#[tokio::test]
async fn checkpoint_captures_transaction_all_or_none() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig::default();
    let db = AedbInstance::open(config.clone(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let base_seq = db
        .snapshot_probe(ConsistencyMode::AtLatest)
        .await
        .expect("base");
    let commit = db
        .commit_envelope(TransactionEnvelope {
            caller: None,
            idempotency_key: None,
            write_class: WriteClass::Standard,
            assertions: Vec::new(),
            read_set: Default::default(),
            write_intent: WriteIntent {
                mutations: vec![
                    Mutation::KvSet {
                        project_id: "p".into(),
                        scope_id: "app".into(),
                        key: b"tx:part:a".to_vec(),
                        value: b"1".to_vec(),
                    },
                    Mutation::KvSet {
                        project_id: "p".into(),
                        scope_id: "app".into(),
                        key: b"tx:part:b".to_vec(),
                        value: b"2".to_vec(),
                    },
                ],
            },
            base_seq,
        })
        .await
        .expect("atomic multi-mutation commit");
    let checkpoint_seq = db.checkpoint_now().await.expect("checkpoint");
    assert!(checkpoint_seq >= commit.commit_seq);

    let recovered_at_cp =
        crate::recovery::recover_at_seq_with_config(dir.path(), checkpoint_seq, &config)
            .expect("recover at checkpoint");
    let snapshot_at_cp = recovered_at_cp.keyspace.snapshot();
    assert_eq!(
        snapshot_at_cp
            .kv_get("p", "app", b"tx:part:a")
            .map(|entry| entry.value.clone()),
        Some(b"1".to_vec())
    );
    assert_eq!(
        snapshot_at_cp
            .kv_get("p", "app", b"tx:part:b")
            .map(|entry| entry.value.clone()),
        Some(b"2".to_vec())
    );

    if commit.commit_seq > 0 {
        let recovered_before =
            crate::recovery::recover_at_seq_with_config(dir.path(), commit.commit_seq - 1, &config)
                .expect("recover before commit seq");
        let before_snapshot = recovered_before.keyspace.snapshot();
        assert!(before_snapshot.kv_get("p", "app", b"tx:part:a").is_none());
        assert!(before_snapshot.kv_get("p", "app", b"tx:part:b").is_none());
    }
}

#[tokio::test]
async fn checkpoint_manifest_trims_fully_covered_segments() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_segment_bytes: 4096,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config.clone(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    for i in 0..400u32 {
        db.commit(Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: format!("seg:{i:04}").into_bytes(),
            value: vec![b'x'; 256],
        })
        .await
        .expect("seed");
    }

    let all_segments = crate::lib_helpers::read_segments(dir.path()).expect("all segments");
    assert!(
        all_segments.len() > 1,
        "test must create multiple wal segments"
    );

    let checkpoint_seq = db.checkpoint_now().await.expect("checkpoint");
    let manifest = crate::manifest::atomic::load_manifest_signed(dir.path(), config.hmac_key())
        .expect("manifest");

    assert!(
        manifest.segments.len() < all_segments.len(),
        "checkpoint manifest should drop fully covered historical segments"
    );
    for segment in &manifest.segments {
        let path = dir.path().join(&segment.filename);
        let range = crate::lib_helpers::scan_segment_seq_range(&path).expect("scan segment");
        if let Some((_, max_seq)) = range {
            assert!(
                max_seq > checkpoint_seq || segment.segment_seq == manifest.active_segment_seq,
                "manifest retained a segment fully covered by checkpoint"
            );
        }
    }
}

#[tokio::test]
#[ignore = "manual perf probe: commit latency with and without concurrent checkpoint"]
async fn benchmark_commit_latency_during_checkpoint() {
    fn percentile(sorted: &[u128], p: f64) -> u128 {
        if sorted.is_empty() {
            return 0;
        }
        let percentile_index = ((sorted.len() as f64 - 1.0) * p).round() as usize;
        sorted[percentile_index.min(sorted.len() - 1)]
    }

    async fn run_phase(
        db: &Arc<AedbInstance>,
        start: usize,
        count: usize,
        with_checkpoint: bool,
    ) -> (f64, u128, u128) {
        for i in 0..10_000usize {
            db.commit(Mutation::KvSet {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: format!("warmup:{i:05}").into_bytes(),
                value: vec![b'w'; 256],
            })
            .await
            .expect("warmup seed");
        }

        let checkpoint_task = if with_checkpoint {
            Some(tokio::spawn({
                let db = Arc::clone(db);
                async move { db.checkpoint_now().await }
            }))
        } else {
            None
        };

        let phase_started = Instant::now();
        let mut latencies_us = Vec::with_capacity(count);
        for i in 0..count {
            let started = Instant::now();
            db.commit(Mutation::KvSet {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: format!(
                    "bench:{}:{:06}",
                    if with_checkpoint { "cp" } else { "base" },
                    start + i
                )
                .into_bytes(),
                value: vec![b'x'; 512],
            })
            .await
            .expect("bench commit");
            latencies_us.push(started.elapsed().as_micros());
        }

        if let Some(task) = checkpoint_task {
            let _ = task
                .await
                .expect("checkpoint task join")
                .expect("checkpoint");
        }

        latencies_us.sort_unstable();
        let elapsed_secs = phase_started.elapsed().as_secs_f64().max(0.000_001);
        let tps = count as f64 / elapsed_secs;
        let p50_us = percentile(&latencies_us, 0.50);
        let p99_us = percentile(&latencies_us, 0.99);
        (tps, p50_us, p99_us)
    }

    let dir = tempdir().expect("temp");
    let config = AedbConfig::default();
    let db = Arc::new(AedbInstance::open(config, dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    let (base_tps, base_p50, base_p99) = run_phase(&db, 0, 800, false).await;
    let (cp_tps, cp_p50, cp_p99) = run_phase(&db, 1_000_000, 800, true).await;

    eprintln!(
        "checkpoint_perf: base_tps={:.2} base_p50_us={} base_p99_us={} | cp_tps={:.2} cp_p50_us={} cp_p99_us={} | tps_ratio={:.3} p50_ratio={:.3} p99_ratio={:.3}",
        base_tps,
        base_p50,
        base_p99,
        cp_tps,
        cp_p50,
        cp_p99,
        cp_tps / base_tps.max(0.000_001),
        (cp_p50 as f64) / (base_p50.max(1) as f64),
        (cp_p99 as f64) / (base_p99.max(1) as f64),
    );
}

#[tokio::test]
#[ignore = "manual perf probe: parallel commit throughput with and without concurrent checkpoint"]
async fn benchmark_parallel_commit_throughput_during_checkpoint() {
    fn percentile(sorted: &[u128], p: f64) -> u128 {
        if sorted.is_empty() {
            return 0;
        }
        let percentile_index = ((sorted.len() as f64 - 1.0) * p).round() as usize;
        sorted[percentile_index.min(sorted.len() - 1)]
    }

    async fn run_parallel_phase(
        db: &Arc<AedbInstance>,
        workers: usize,
        commits_per_worker: usize,
        with_checkpoint: bool,
        offset: usize,
    ) -> (f64, u128, u128) {
        let checkpoint_task = if with_checkpoint {
            Some(tokio::spawn({
                let db = Arc::clone(db);
                async move { db.checkpoint_now().await }
            }))
        } else {
            None
        };

        let phase_started = Instant::now();
        let mut tasks = Vec::with_capacity(workers);
        for worker in 0..workers {
            let db = Arc::clone(db);
            tasks.push(tokio::spawn(async move {
                let mut latencies = Vec::with_capacity(commits_per_worker);
                for i in 0..commits_per_worker {
                    let started = Instant::now();
                    db.commit(Mutation::KvSet {
                        project_id: "p".into(),
                        scope_id: "app".into(),
                        key: format!(
                            "par:{}:{:02}:{:06}",
                            if with_checkpoint { "cp" } else { "base" },
                            worker,
                            offset + i
                        )
                        .into_bytes(),
                        value: vec![b'p'; 256],
                    })
                    .await
                    .expect("parallel bench commit");
                    latencies.push(started.elapsed().as_micros());
                }
                latencies
            }));
        }

        let mut all_latencies = Vec::with_capacity(workers * commits_per_worker);
        for task in tasks {
            let mut worker_latencies = task.await.expect("worker join");
            all_latencies.append(&mut worker_latencies);
        }

        if let Some(task) = checkpoint_task {
            let _ = task
                .await
                .expect("checkpoint task join")
                .expect("checkpoint");
        }

        all_latencies.sort_unstable();
        let elapsed_secs = phase_started.elapsed().as_secs_f64().max(0.000_001);
        let total = workers * commits_per_worker;
        let tps = total as f64 / elapsed_secs;
        let p50_us = percentile(&all_latencies, 0.50);
        let p99_us = percentile(&all_latencies, 0.99);
        (tps, p50_us, p99_us)
    }

    let dir = tempdir().expect("temp");
    let mut config = AedbConfig {
        durability_mode: DurabilityMode::Batch,
        batch_interval_ms: 10,
        batch_max_bytes: usize::MAX,
        recovery_mode: RecoveryMode::Permissive,
        hash_chain_required: false,
        ..AedbConfig::default()
    };
    config.manifest_hmac_key = None;
    let db = Arc::new(AedbInstance::open(config, dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    // Seed state so the checkpoint has meaningful work.
    for i in 0..12_000usize {
        db.commit(Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: format!("parallel-seed:{i:05}").into_bytes(),
            value: vec![b's'; 256],
        })
        .await
        .expect("seed");
    }

    let workers = 8usize;
    let commits_per_worker = 300usize;
    let (base_tps, base_p50, base_p99) =
        run_parallel_phase(&db, workers, commits_per_worker, false, 0).await;
    let (cp_tps, cp_p50, cp_p99) =
        run_parallel_phase(&db, workers, commits_per_worker, true, 1_000_000).await;

    eprintln!(
        "checkpoint_parallel_perf: workers={} commits_per_worker={} | base_tps={:.2} base_p50_us={} base_p99_us={} | cp_tps={:.2} cp_p50_us={} cp_p99_us={} | tps_ratio={:.3} p50_ratio={:.3} p99_ratio={:.3}",
        workers,
        commits_per_worker,
        base_tps,
        base_p50,
        base_p99,
        cp_tps,
        cp_p50,
        cp_p99,
        cp_tps / base_tps.max(0.000_001),
        (cp_p50 as f64) / (base_p50.max(1) as f64),
        (cp_p99 as f64) / (base_p99.max(1) as f64),
    );
}

#[tokio::test]
#[ignore = "manual perf probe: compare checkpoint compression levels under parallel load"]
async fn benchmark_parallel_checkpoint_compression_levels() {
    async fn seed(db: &Arc<AedbInstance>) {
        for i in 0..12_000usize {
            db.commit(Mutation::KvSet {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: format!("parallel-seed:{i:05}").into_bytes(),
                value: vec![b's'; 256],
            })
            .await
            .expect("seed");
        }
    }

    async fn run_parallel_phase(
        db: &Arc<AedbInstance>,
        workers: usize,
        commits_per_worker: usize,
        with_checkpoint: bool,
        offset: usize,
    ) -> f64 {
        let checkpoint_task = if with_checkpoint {
            Some(tokio::spawn({
                let db = Arc::clone(db);
                async move { db.checkpoint_now().await }
            }))
        } else {
            None
        };

        let phase_started = Instant::now();
        let mut tasks = Vec::with_capacity(workers);
        for worker in 0..workers {
            let db = Arc::clone(db);
            tasks.push(tokio::spawn(async move {
                for i in 0..commits_per_worker {
                    db.commit(Mutation::KvSet {
                        project_id: "p".into(),
                        scope_id: "app".into(),
                        key: format!(
                            "cmp:{}:{:02}:{:06}",
                            if with_checkpoint { "cp" } else { "base" },
                            worker,
                            offset + i
                        )
                        .into_bytes(),
                        value: vec![b'c'; 256],
                    })
                    .await
                    .expect("parallel bench commit");
                }
            }));
        }
        for task in tasks {
            task.await.expect("worker join");
        }
        if let Some(task) = checkpoint_task {
            let _ = task
                .await
                .expect("checkpoint task join")
                .expect("checkpoint");
        }

        let elapsed_secs = phase_started.elapsed().as_secs_f64().max(0.000_001);
        let total = workers * commits_per_worker;
        total as f64 / elapsed_secs
    }

    let workers = 8usize;
    let commits_per_worker = 300usize;
    let levels = [3, 1, 0];

    let mut baseline_tps = 0.0f64;
    for (idx, level) in levels.iter().copied().enumerate() {
        let dir = tempdir().expect("temp");
        let mut config = AedbConfig {
            durability_mode: DurabilityMode::Batch,
            batch_interval_ms: 10,
            batch_max_bytes: usize::MAX,
            recovery_mode: RecoveryMode::Permissive,
            hash_chain_required: false,
            ..AedbConfig::default()
        };
        config.manifest_hmac_key = None;
        config.checkpoint_compression_level = level;
        let db = Arc::new(AedbInstance::open(config, dir.path()).expect("open"));
        db.create_project("p").await.expect("project");
        seed(&db).await;

        let base =
            run_parallel_phase(&db, workers, commits_per_worker, false, idx * 1_000_000).await;
        let cp = run_parallel_phase(
            &db,
            workers,
            commits_per_worker,
            true,
            idx * 1_000_000 + 500_000,
        )
        .await;
        if idx == 0 {
            baseline_tps = base;
        }

        eprintln!(
            "checkpoint_compression_perf: level={} workers={} commits_per_worker={} base_tps={:.2} cp_tps={:.2} cp_to_base={:.3} cp_to_level3_base={:.3}",
            level,
            workers,
            commits_per_worker,
            base,
            cp,
            cp / base.max(0.000_001),
            cp / baseline_tps.max(0.000_001),
        );
    }
}

#[tokio::test]
#[ignore = "manual perf probe: durability knob sweep (batch/coalescing)"]
async fn benchmark_durability_knob_sweep() {
    fn percentile(sorted: &[u128], p: f64) -> u128 {
        if sorted.is_empty() {
            return 0;
        }
        let percentile_index = ((sorted.len() as f64 - 1.0) * p).round() as usize;
        sorted[percentile_index.min(sorted.len() - 1)]
    }

    #[derive(Clone)]
    struct Profile {
        name: &'static str,
        batch_interval_ms: u64,
        batch_max_bytes: usize,
        coalesce_enabled: bool,
        coalesce_window_us: u64,
    }

    async fn run_profile(profile: &Profile) -> (f64, u128, u128, u64, u64) {
        let dir = tempdir().expect("temp");
        let mut config = AedbConfig {
            durability_mode: DurabilityMode::Batch,
            batch_interval_ms: profile.batch_interval_ms,
            batch_max_bytes: profile.batch_max_bytes,
            recovery_mode: RecoveryMode::Permissive,
            hash_chain_required: false,
            durable_ack_coalescing_enabled: profile.coalesce_enabled,
            durable_ack_coalesce_window_us: profile.coalesce_window_us,
            ..AedbConfig::default()
        };
        config.manifest_hmac_key = None;
        let db = Arc::new(AedbInstance::open(config, dir.path()).expect("open"));
        db.create_project("p").await.expect("project");

        for i in 0..8_000usize {
            db.commit(Mutation::KvSet {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: format!("sweep-seed:{i:05}").into_bytes(),
                value: vec![b's'; 128],
            })
            .await
            .expect("seed");
        }

        let workers = 8usize;
        let commits_per_worker = 500usize;
        let started = Instant::now();
        let mut tasks = Vec::with_capacity(workers);
        for worker in 0..workers {
            let db = Arc::clone(&db);
            tasks.push(tokio::spawn(async move {
                let mut lats = Vec::with_capacity(commits_per_worker);
                for i in 0..commits_per_worker {
                    let t0 = Instant::now();
                    db.commit(Mutation::KvSet {
                        project_id: "p".into(),
                        scope_id: "app".into(),
                        key: format!("sweep:{worker:02}:{i:06}").into_bytes(),
                        value: vec![b'x'; 256],
                    })
                    .await
                    .expect("commit");
                    lats.push(t0.elapsed().as_micros());
                }
                lats
            }));
        }

        let mut all_lat = Vec::with_capacity(workers * commits_per_worker);
        for task in tasks {
            let mut lats = task.await.expect("worker join");
            all_lat.append(&mut lats);
        }
        all_lat.sort_unstable();

        let elapsed = started.elapsed().as_secs_f64().max(0.000_001);
        let tps = (workers * commits_per_worker) as f64 / elapsed;
        let p50 = percentile(&all_lat, 0.50);
        let p99 = percentile(&all_lat, 0.99);
        let op = db.operational_metrics().await;
        (tps, p50, p99, op.wal_sync_ops, op.avg_wal_sync_micros)
    }

    let profiles = vec![
        Profile {
            name: "baseline_10ms_1mb_no_coalesce",
            batch_interval_ms: 10,
            batch_max_bytes: 1024 * 1024,
            coalesce_enabled: false,
            coalesce_window_us: 0,
        },
        Profile {
            name: "trial_20ms_4mb_coalesce_1000us",
            batch_interval_ms: 20,
            batch_max_bytes: 4 * 1024 * 1024,
            coalesce_enabled: true,
            coalesce_window_us: 1000,
        },
        Profile {
            name: "trial_20ms_8mb_coalesce_1500us",
            batch_interval_ms: 20,
            batch_max_bytes: 8 * 1024 * 1024,
            coalesce_enabled: true,
            coalesce_window_us: 1500,
        },
        Profile {
            name: "trial_40ms_8mb_coalesce_1500us",
            batch_interval_ms: 40,
            batch_max_bytes: 8 * 1024 * 1024,
            coalesce_enabled: true,
            coalesce_window_us: 1500,
        },
    ];

    for profile in &profiles {
        let (tps, p50, p99, wal_sync_ops, avg_wal_sync_us) = run_profile(profile).await;
        eprintln!(
            "durability_sweep: profile={} tps={:.2} p50_us={} p99_us={} wal_sync_ops={} avg_wal_sync_us={}",
            profile.name, tps, p50, p99, wal_sync_ops, avg_wal_sync_us
        );
    }
}

#[tokio::test]
#[ignore = "manual profiling: end-to-end pipeline breakdown (commit/checkpoint/recovery)"]
async fn profile_end_to_end_pipeline_breakdown() {
    fn percentile(sorted: &[u128], p: f64) -> u128 {
        if sorted.is_empty() {
            return 0;
        }
        let percentile_index = ((sorted.len() as f64 - 1.0) * p).round() as usize;
        sorted[percentile_index.min(sorted.len() - 1)]
    }

    let dir = tempdir().expect("temp");
    let mut config = AedbConfig {
        durability_mode: DurabilityMode::Batch,
        batch_interval_ms: 10,
        batch_max_bytes: usize::MAX,
        recovery_mode: RecoveryMode::Permissive,
        hash_chain_required: false,
        ..AedbConfig::default()
    };
    config.manifest_hmac_key = None;
    let db = Arc::new(AedbInstance::open(config.clone(), dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    for i in 0..20_000usize {
        db.commit(Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: format!("seed:{i:06}").into_bytes(),
            value: vec![b's'; 256],
        })
        .await
        .expect("seed");
    }

    let workers = 8usize;
    let commits_per_worker = 500usize;
    let commit_started = Instant::now();
    let mut tasks = Vec::with_capacity(workers);
    for worker in 0..workers {
        let db = Arc::clone(&db);
        tasks.push(tokio::spawn(async move {
            let mut latencies_us = Vec::with_capacity(commits_per_worker);
            for i in 0..commits_per_worker {
                let started = Instant::now();
                db.commit(Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: format!("profile:commit:{worker:02}:{i:06}").into_bytes(),
                    value: vec![b'c'; 256],
                })
                .await
                .expect("commit");
                latencies_us.push(started.elapsed().as_micros());
            }
            latencies_us
        }));
    }
    let mut all_commit_latencies = Vec::with_capacity(workers * commits_per_worker);
    for task in tasks {
        let mut worker_latencies = task.await.expect("worker join");
        all_commit_latencies.append(&mut worker_latencies);
    }
    let commit_elapsed = commit_started.elapsed();
    all_commit_latencies.sort_unstable();
    let commit_tps = (workers * commits_per_worker) as f64 / commit_elapsed.as_secs_f64();
    let commit_p50 = percentile(&all_commit_latencies, 0.50);
    let commit_p99 = percentile(&all_commit_latencies, 0.99);
    let op_after_commits = db.operational_metrics().await;

    let checkpoint_total_started = Instant::now();
    let checkpoint_lock_started = Instant::now();
    let _checkpoint_guard = db.checkpoint_lock.lock().await;
    let checkpoint_lock_wait = checkpoint_lock_started.elapsed();

    let snapshot_started = Instant::now();
    let checkpoint_seq = db.executor.durable_head_seq_now();
    let lease = db
        .acquire_snapshot(ConsistencyMode::AtSeq(checkpoint_seq))
        .await
        .expect("checkpoint snapshot");
    let snapshot_elapsed = snapshot_started.elapsed();

    let idempotency_started = Instant::now();
    let mut idempotency = db.executor.idempotency_snapshot().await;
    idempotency.retain(|_, record| record.commit_seq <= checkpoint_seq);
    let idempotency_elapsed = idempotency_started.elapsed();

    let write_checkpoint_started = Instant::now();
    let checkpoint = crate::checkpoint::writer::write_checkpoint_with_key(
        lease.view.keyspace.as_ref(),
        lease.view.catalog.as_ref(),
        checkpoint_seq,
        &db.dir,
        db._config.checkpoint_key(),
        db._config.checkpoint_key_id.clone(),
        idempotency,
        db._config.checkpoint_compression_level,
    )
    .expect("write checkpoint");
    let write_checkpoint_elapsed = write_checkpoint_started.elapsed();

    let segments_started = Instant::now();
    let segments = crate::lib_helpers::read_segments_for_checkpoint(&db.dir, checkpoint_seq)
        .expect("read segments for checkpoint");
    let active_segment_seq = segments
        .last()
        .map(|segment| segment.segment_seq)
        .unwrap_or(checkpoint_seq.saturating_add(1));
    let segments_elapsed = segments_started.elapsed();

    let manifest_started = Instant::now();
    let manifest = crate::manifest::schema::Manifest {
        durable_seq: checkpoint_seq,
        visible_seq: checkpoint_seq,
        active_segment_seq,
        checkpoints: vec![checkpoint.clone()],
        segments: segments.clone(),
    };
    crate::manifest::atomic::write_manifest_atomic_signed(
        &manifest,
        &db.dir,
        db._config.hmac_key(),
    )
    .expect("write manifest");
    let manifest_elapsed = manifest_started.elapsed();
    let checkpoint_total_elapsed = checkpoint_total_started.elapsed();
    drop(_checkpoint_guard);

    let checkpoint_bytes = std::fs::metadata(db.dir.join(&checkpoint.filename))
        .expect("checkpoint stat")
        .len();

    drop(db);
    let reopen_started = Instant::now();
    let reopened = AedbInstance::open(config, dir.path()).expect("reopen");
    let reopen_elapsed = reopen_started.elapsed();
    let reopen_metrics = reopened.operational_metrics().await;

    eprintln!(
        "pipeline_profile commit_phase: workers={} commits_per_worker={} commits={} elapsed_ms={} tps={:.2} p50_us={} p99_us={} | prestage_validate_ops={} avg_prestage_validate_us={} epoch_process_ops={} avg_epoch_process_us={} avg_wal_append_us={} avg_wal_sync_us={} avg_coordinator_apply_us={}",
        workers,
        commits_per_worker,
        workers * commits_per_worker,
        commit_elapsed.as_millis(),
        commit_tps,
        commit_p50,
        commit_p99,
        op_after_commits.prestage_validate_ops,
        op_after_commits.avg_prestage_validate_micros,
        op_after_commits.epoch_process_ops,
        op_after_commits.avg_epoch_process_micros,
        op_after_commits.avg_wal_append_micros,
        op_after_commits.avg_wal_sync_micros,
        op_after_commits.avg_coordinator_apply_micros
    );
    eprintln!(
        "pipeline_profile checkpoint_phase: seq={} total_ms={} lock_wait_ms={} snapshot_ms={} idempotency_ms={} write_checkpoint_ms={} segment_scan_ms={} manifest_ms={} checkpoint_bytes={} retained_segments={}",
        checkpoint_seq,
        checkpoint_total_elapsed.as_millis(),
        checkpoint_lock_wait.as_millis(),
        snapshot_elapsed.as_millis(),
        idempotency_elapsed.as_millis(),
        write_checkpoint_elapsed.as_millis(),
        segments_elapsed.as_millis(),
        manifest_elapsed.as_millis(),
        checkpoint_bytes,
        segments.len()
    );
    eprintln!(
        "pipeline_profile recovery_phase: reopen_ms={} startup_recovery_micros={} startup_recovered_seq={} durable_head_seq={} visible_head_seq={}",
        reopen_elapsed.as_millis(),
        reopen_metrics.startup_recovery_micros,
        reopen_metrics.startup_recovered_seq,
        reopen_metrics.durable_head_seq,
        reopen_metrics.visible_head_seq
    );
}

#[tokio::test]
async fn snapshot_probe_returns_snapshot_seq() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    let seq = db
        .snapshot_probe(ConsistencyMode::AtLatest)
        .await
        .expect("probe");
    assert!(seq >= 1);
}

#[tokio::test]
async fn snapshot_probe_does_not_consume_snapshot_slot() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_concurrent_snapshots: 1,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let _lease = db
        .acquire_snapshot(ConsistencyMode::AtLatest)
        .await
        .expect("lease");

    let seq = db
        .snapshot_probe(ConsistencyMode::AtLatest)
        .await
        .expect("probe");
    assert!(seq >= 1);
}

#[tokio::test]
async fn snapshot_probe_remains_live_under_snapshot_capacity_pressure() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_concurrent_snapshots: 1,
        ..AedbConfig::default()
    };
    let db = Arc::new(AedbInstance::open(config, dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    let _lease = db
        .acquire_snapshot(ConsistencyMode::AtLatest)
        .await
        .expect("lease");

    let mut tasks = Vec::new();
    for _ in 0..4 {
        let db = Arc::clone(&db);
        tasks.push(tokio::spawn(async move {
            for _ in 0..128 {
                let seq = db
                    .snapshot_probe(ConsistencyMode::AtLatest)
                    .await
                    .expect("probe");
                assert!(seq >= 1);
                tokio::task::yield_now().await;
            }
        }));
    }

    tokio::time::timeout(Duration::from_secs(2), async {
        for task in tasks {
            task.await.expect("probe task");
        }
    })
    .await
    .expect("snapshot probes timed out under snapshot pressure");
}

#[tokio::test]
async fn checkpoint_now_completes_under_hot_load() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        durability_mode: DurabilityMode::Batch,
        batch_interval_ms: 60_000,
        batch_max_bytes: usize::MAX,
        ..AedbConfig::default()
    };
    let db = Arc::new(AedbInstance::open(config, dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
    let writes = Arc::new(std::sync::atomic::AtomicU64::new(0));
    let reads = Arc::new(std::sync::atomic::AtomicU64::new(0));

    let writer_db = Arc::clone(&db);
    let writer_stop = Arc::clone(&stop);
    let writes_ref = Arc::clone(&writes);
    let writer = tokio::spawn(async move {
        let mut i = 0u64;
        while !writer_stop.load(Ordering::Relaxed) {
            let _ = writer_db
                .commit(Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: format!("checkpoint-hot:{i}").into_bytes(),
                    value: i.to_string().into_bytes(),
                })
                .await;
            writes_ref.fetch_add(1, Ordering::Relaxed);
            i = i.saturating_add(1);
            tokio::task::yield_now().await;
        }
    });

    let reader_db = Arc::clone(&db);
    let reader_stop = Arc::clone(&stop);
    let reads_ref = Arc::clone(&reads);
    let reader = tokio::spawn(async move {
        while !reader_stop.load(Ordering::Relaxed) {
            let _ = reader_db.snapshot_probe(ConsistencyMode::AtLatest).await;
            reads_ref.fetch_add(1, Ordering::Relaxed);
            tokio::task::yield_now().await;
        }
    });

    tokio::time::sleep(Duration::from_millis(50)).await;
    let writes_before = writes.load(Ordering::Relaxed);
    let reads_before = reads.load(Ordering::Relaxed);

    let checkpoint_seq = tokio::time::timeout(Duration::from_secs(10), db.checkpoint_now())
        .await
        .expect("checkpoint_now timed out")
        .expect("checkpoint_now");
    assert!(checkpoint_seq >= 1);

    let writes_after = writes.load(Ordering::Relaxed);
    let reads_after = reads.load(Ordering::Relaxed);
    assert!(
        writes_after > writes_before,
        "writes stalled during checkpoint"
    );
    assert!(
        reads_after > reads_before,
        "reads stalled during checkpoint"
    );

    stop.store(true, Ordering::Relaxed);
    tokio::time::timeout(Duration::from_secs(5), writer)
        .await
        .expect("writer join timed out")
        .expect("writer join");
    tokio::time::timeout(Duration::from_secs(5), reader)
        .await
        .expect("reader join timed out")
        .expect("reader join");
}

#[tokio::test]
async fn at_checkpoint_falls_back_when_version_evicted() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_versions: 2,
        min_version_age_ms: 0,
        version_gc_interval_ms: 1,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"checkpoint:key".to_vec(),
        value: b"v1".to_vec(),
    })
    .await
    .expect("seed checkpoint value");
    let checkpoint_seq = db.checkpoint_now().await.expect("checkpoint");
    for i in 0..8 {
        db.commit(Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: format!("tail:{i}").into_bytes(),
            value: vec![i as u8],
        })
        .await
        .expect("tail write");
    }
    tokio::time::sleep(std::time::Duration::from_millis(20)).await;
    let cp_view_seq = db
        .snapshot_probe(ConsistencyMode::AtCheckpoint)
        .await
        .expect("checkpoint snapshot");
    assert_eq!(cp_view_seq, checkpoint_seq);
}

#[tokio::test]
async fn migrations_are_idempotent_and_checksum_guarded() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    let migration = crate::migration::Migration {
        version: 1,
        name: "create-users".into(),
        project_id: "p".into(),
        scope_id: "app".into(),
        mutations: vec![Mutation::Ddl(DdlOperation::CreateTable {
            owner_id: None,
            if_not_exists: false,
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            columns: vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "name".into(),
                    col_type: ColumnType::Text,
                    nullable: false,
                },
            ],
            primary_key: vec!["id".into()],
        })],
        down_mutations: Some(vec![Mutation::Ddl(DdlOperation::DropTable {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            if_exists: true,
        })]),
    };
    db.apply_migration(migration.clone())
        .await
        .expect("apply 1");
    db.apply_migration(migration).await.expect("apply 2");

    let changed = crate::migration::Migration {
        version: 1,
        name: "create-users-v2".into(),
        project_id: "p".into(),
        scope_id: "app".into(),
        mutations: vec![],
        down_mutations: None,
    };
    let err = db
        .apply_migration(changed)
        .await
        .expect_err("checksum guard");
    assert!(matches!(err, AedbError::IntegrityError { .. }));
}

#[tokio::test]
async fn run_migrations_reports_applied_and_skipped_versions() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let migration = crate::migration::Migration {
        version: 1,
        name: "create-users".into(),
        project_id: "p".into(),
        scope_id: "app".into(),
        mutations: vec![Mutation::Ddl(DdlOperation::CreateTable {
            owner_id: None,
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            if_not_exists: false,
            columns: vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "name".into(),
                    col_type: ColumnType::Text,
                    nullable: false,
                },
            ],
            primary_key: vec!["id".into()],
        })],
        down_mutations: None,
    };

    let first = db
        .run_migrations(vec![migration.clone()])
        .await
        .expect("run first");
    assert_eq!(first.applied.len(), 1);
    assert!(first.skipped.is_empty());
    assert_eq!(first.current_version, 1);

    let second = db
        .run_migrations(vec![migration])
        .await
        .expect("run second");
    assert!(second.applied.is_empty());
    assert_eq!(second.skipped, vec![1]);
    assert_eq!(second.current_version, 1);
    assert_eq!(db.current_version("p", "app").await.expect("current"), 1);
}

#[tokio::test]
async fn concurrent_apply_migration_converges_idempotently() {
    let dir = tempdir().expect("temp");
    let db = Arc::new(AedbInstance::open(AedbConfig::default(), dir.path()).expect("open"));
    db.create_project("p").await.expect("project");

    let migration = crate::migration::Migration {
        version: 1,
        name: "create-users".into(),
        project_id: "p".into(),
        scope_id: "app".into(),
        mutations: vec![Mutation::Ddl(DdlOperation::CreateTable {
            owner_id: None,
            if_not_exists: false,
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            columns: vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "name".into(),
                    col_type: ColumnType::Text,
                    nullable: false,
                },
            ],
            primary_key: vec!["id".into()],
        })],
        down_mutations: None,
    };

    let barrier = Arc::new(tokio::sync::Barrier::new(2));
    let db_a = Arc::clone(&db);
    let db_b = Arc::clone(&db);
    let migration_a = migration.clone();
    let migration_b = migration.clone();
    let barrier_a = Arc::clone(&barrier);
    let barrier_b = Arc::clone(&barrier);

    let task_a = tokio::spawn(async move {
        barrier_a.wait().await;
        db_a.apply_migration(migration_a).await
    });
    let task_b = tokio::spawn(async move {
        barrier_b.wait().await;
        db_b.apply_migration(migration_b).await
    });

    task_a
        .await
        .expect("task a join")
        .expect("task a apply migration");
    task_b
        .await
        .expect("task b join")
        .expect("task b apply migration");

    let applied = db
        .list_applied_migrations("p", "app")
        .await
        .expect("list applied");
    assert_eq!(applied.len(), 1);
    assert_eq!(applied[0].version, 1);
    assert!(
        db.table_exists("p", "app", "users")
            .await
            .expect("users table exists")
    );
}

struct RecordingTelemetryHook {
    queries: Arc<std::sync::Mutex<Vec<QueryTelemetryEvent>>>,
    commits: Arc<std::sync::Mutex<Vec<CommitTelemetryEvent>>>,
}

impl QueryCommitTelemetryHook for RecordingTelemetryHook {
    fn on_query(&self, event: &QueryTelemetryEvent) {
        self.queries
            .lock()
            .expect("query telemetry mutex poisoned")
            .push(event.clone());
    }

    fn on_commit(&self, event: &CommitTelemetryEvent) {
        self.commits
            .lock()
            .expect("commit telemetry mutex poisoned")
            .push(event.clone());
    }
}

struct EchoSqlAdapter;

impl ReadOnlySqlAdapter for EchoSqlAdapter {
    fn execute_read_only(
        &self,
        _project_id: &str,
        _scope_id: &str,
        sql: &str,
    ) -> Result<(Query, QueryOptions), QueryError> {
        if sql.trim() == "select id from items" {
            Ok((
                Query::select(&["id"]).from("items"),
                QueryOptions {
                    allow_full_scan: true,
                    ..QueryOptions::default()
                },
            ))
        } else {
            Err(QueryError::InvalidQuery {
                reason: "unsupported sql".into(),
            })
        }
    }
}

struct RestrictiveSqlAdapter;

impl ReadOnlySqlAdapter for RestrictiveSqlAdapter {
    fn execute_read_only(
        &self,
        _project_id: &str,
        _scope_id: &str,
        sql: &str,
    ) -> Result<(Query, QueryOptions), QueryError> {
        if sql.trim() == "select * from items" {
            Ok((Query::select(&["*"]).from("items"), QueryOptions::default()))
        } else {
            Err(QueryError::InvalidQuery {
                reason: "unsupported sql".into(),
            })
        }
    }
}

struct LocalRemoteAdapter;

impl RemoteBackupAdapter for LocalRemoteAdapter {
    fn store_backup_dir(&self, uri: &str, backup_dir: &Path) -> Result<(), AedbError> {
        let target = PathBuf::from(uri);
        if target.exists() {
            fs::remove_dir_all(&target)?;
        }
        copy_dir_recursive(backup_dir, &target)
    }

    fn materialize_backup_chain(
        &self,
        uri: &str,
        scratch_dir: &Path,
    ) -> Result<Vec<PathBuf>, AedbError> {
        let source = PathBuf::from(uri);
        let target = scratch_dir.join("remote_chain_0");
        copy_dir_recursive(&source, &target)?;
        Ok(vec![target])
    }
}

fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), AedbError> {
    fs::create_dir_all(dst)?;
    for entry in fs::read_dir(src)? {
        let entry = entry?;
        let entry_path = entry.path();
        let out_path = dst.join(entry.file_name());
        let ty = entry.file_type()?;
        if ty.is_dir() {
            copy_dir_recursive(&entry_path, &out_path)?;
        } else if ty.is_file() {
            fs::copy(&entry_path, &out_path)?;
        }
    }
    Ok(())
}

async fn create_table(
    db: &AedbInstance,
    project_id: &str,
    scope_id: &str,
    table_name: &str,
    columns: Vec<ColumnDef>,
    primary_key: Vec<&str>,
) {
    db.commit_ddl(DdlOperation::CreateTable {
        owner_id: None,
        project_id: project_id.to_string(),
        scope_id: scope_id.to_string(),
        table_name: table_name.to_string(),
        if_not_exists: false,
        columns,
        primary_key: primary_key.into_iter().map(|v| v.to_string()).collect(),
    })
    .await
    .expect("create table");
}

#[tokio::test]
async fn read_tx_keeps_snapshot_consistency_across_queries() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "accounts",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "balance".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "accounts".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row::from_values(vec![Value::Integer(1), Value::Integer(10)]),
    })
    .await
    .expect("seed");

    let tx = db
        .begin_read_tx(ConsistencyMode::AtLatest)
        .await
        .expect("read tx");
    let before = tx
        .query(
            "p",
            "app",
            Query::select(&["balance"])
                .from("accounts")
                .where_(Expr::Eq("id".into(), Value::Integer(1))),
        )
        .await
        .expect("before");
    assert_eq!(before.rows[0].values[0], Value::Integer(10));

    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "accounts".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row::from_values(vec![Value::Integer(1), Value::Integer(99)]),
    })
    .await
    .expect("update");

    let still_before = tx
        .query(
            "p",
            "app",
            Query::select(&["balance"])
                .from("accounts")
                .where_(Expr::Eq("id".into(), Value::Integer(1))),
        )
        .await
        .expect("stable read");
    assert_eq!(still_before.rows[0].values[0], Value::Integer(10));
}

#[tokio::test]
async fn list_batch_and_lookup_helpers_work() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "user_id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;
    create_table(
        &db,
        "p",
        "app",
        "users",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;
    for i in 1..=5 {
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            primary_key: vec![Value::Integer(i)],
            row: Row::from_values(vec![Value::Integer(i), Value::Integer(i)]),
        })
        .await
        .expect("seed item");
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            primary_key: vec![Value::Integer(i)],
            row: Row::from_values(vec![Value::Integer(i), Value::Text(format!("u{i}").into())]),
        })
        .await
        .expect("seed user");
    }

    let batched = db
        .query_batch(
            "p",
            "app",
            vec![
                QueryBatchItem {
                    query: Query::select(&["id"]).from("items").limit(2),
                    options: QueryOptions::default(),
                },
                QueryBatchItem {
                    query: Query::select(&["id"]).from("items").limit(3),
                    options: QueryOptions::default(),
                },
            ],
            ConsistencyMode::AtLatest,
        )
        .await
        .expect("query batch");
    assert_eq!(batched.len(), 2);
    assert_eq!(batched[0].snapshot_seq, batched[1].snapshot_seq);

    let first = db
        .list_with_total(
            "p",
            "app",
            Query::select(&["id"]).from("items"),
            None,
            None,
            2,
            ConsistencyMode::AtLatest,
        )
        .await
        .expect("list");
    assert_eq!(first.total_count, 5);
    assert_eq!(first.rows.len(), 2);
    assert!(first.next_cursor.is_some());

    let via_offset = db
        .list_with_total(
            "p",
            "app",
            Query::select(&["id"]).from("items"),
            None,
            Some(3),
            2,
            ConsistencyMode::AtLatest,
        )
        .await
        .expect("offset");
    assert_eq!(via_offset.total_count, 5);
    assert_eq!(via_offset.rows.len(), 2);

    let (source, hydrated) = db
        .lookup_then_hydrate(
            "p",
            "app",
            Query::select(&["user_id"]).from("items").limit(3),
            0,
            Query::select(&["id", "name"]).from("users"),
            "id",
            ConsistencyMode::AtLatest,
        )
        .await
        .expect("lookup/hydrate");
    assert_eq!(source.rows.len(), 3);
    assert!(!source.truncated);
    assert_eq!(hydrated.rows.len(), 3);
    assert!(!hydrated.truncated);
}

#[tokio::test]
async fn lookup_then_hydrate_fetches_all_pages_for_large_key_sets() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");

    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "user_id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;
    create_table(
        &db,
        "p",
        "app",
        "users",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "username".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;

    for id in 1_i64..=1_000_i64 {
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row::from_values(vec![Value::Integer(id), Value::Integer(id)]),
        })
        .await
        .expect("insert item");
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row::from_values(vec![
                Value::Integer(id),
                Value::Text(format!("u{id}").into()),
            ]),
        })
        .await
        .expect("insert user");
    }

    let (source, hydrated) = db
        .lookup_then_hydrate(
            "p",
            "app",
            Query::select(&["user_id"]).from("items").limit(1_000),
            0,
            Query::select(&["id", "username"]).from("users"),
            "id",
            ConsistencyMode::AtLatest,
        )
        .await
        .expect("lookup/hydrate");
    assert_eq!(source.rows.len(), 1_000);
    assert!(!source.truncated);
    assert_eq!(hydrated.rows.len(), 1_000);
    assert!(!hydrated.truncated);
}

#[tokio::test]
async fn telemetry_sql_and_remote_adapter_paths_work() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "items".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row::from_values(vec![Value::Integer(1)]),
    })
    .await
    .expect("seed");

    let hook = Arc::new(RecordingTelemetryHook {
        queries: Arc::new(std::sync::Mutex::new(Vec::new())),
        commits: Arc::new(std::sync::Mutex::new(Vec::new())),
    });
    db.add_telemetry_hook(hook.clone());

    let sql_result = db
        .query_sql_read_only(
            &EchoSqlAdapter,
            "p",
            "app",
            "select id from items",
            ConsistencyMode::AtLatest,
        )
        .await
        .expect("sql adapter");
    assert_eq!(sql_result.rows.len(), 1);

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"remote:test".to_vec(),
        value: b"ok".to_vec(),
    })
    .await
    .expect("kv set");

    let remote_dir = tempdir().expect("remote");
    let uri = remote_dir.path().join("snapshot");
    let adapter = LocalRemoteAdapter;
    let uri_str = uri.to_string_lossy().to_string();
    db.backup_full_to_remote(&adapter, &uri_str)
        .await
        .expect("backup remote");

    let restored_dir = tempdir().expect("restored");
    AedbInstance::restore_from_remote(
        &adapter,
        &uri_str,
        restored_dir.path(),
        &AedbConfig::default(),
    )
    .expect("restore remote");
    let restored =
        AedbInstance::open(AedbConfig::default(), restored_dir.path()).expect("open restored");
    let restored_val = restored
        .kv_get_no_auth("p", "app", b"remote:test", ConsistencyMode::AtLatest)
        .await
        .expect("kv get");
    assert_eq!(restored_val.expect("entry").value, b"ok".to_vec());

    let query_count = hook.queries.lock().expect("query telemetry").len();
    let commit_count = hook.commits.lock().expect("commit telemetry").len();
    assert!(query_count >= 1);
    assert!(commit_count >= 1);
}

#[tokio::test]
async fn strict_restore_rejects_older_backup_version() {
    let dir = tempdir().expect("data dir");
    let backup_dir = tempdir().expect("backup dir");
    let config = AedbConfig::production([7u8; 32]);
    let db = AedbInstance::open_secure(config.clone(), dir.path()).expect("open secure");
    let system = CallerContext::system_internal();
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "arcana".into(),
        }),
    )
    .await
    .expect("create project");
    db.commit_as(
        system,
        Mutation::Ddl(DdlOperation::CreateScope {
            owner_id: None,
            if_not_exists: true,
            project_id: "arcana".into(),
            scope_id: "app".into(),
        }),
    )
    .await
    .expect("create scope");
    db.backup_full(backup_dir.path())
        .await
        .expect("backup full");

    let mut manifest = crate::backup::load_backup_manifest(backup_dir.path(), config.hmac_key())
        .expect("manifest");
    manifest.aedb_version = "0.2.0".into();
    crate::backup::write_backup_manifest(backup_dir.path(), &manifest, config.hmac_key())
        .expect("rewrite manifest");

    let restore_dir = tempdir().expect("restore dir");
    let err = AedbInstance::restore_from_backup_chain(
        &[backup_dir.path().to_path_buf()],
        restore_dir.path(),
        &config,
        None,
    )
    .expect_err("strict restore must reject older backup version");
    assert!(format!("{err}").contains("matching AEDB patch version"));
}

#[tokio::test]
async fn restore_at_time_rejects_backup_wal_with_invalid_hash_chain() {
    let dir = tempdir().expect("data dir");
    let backup_dir = tempdir().expect("backup dir");
    let restore_dir = tempdir().expect("restore dir");
    let config = AedbConfig::production([8u8; 32]);
    let db = AedbInstance::open_secure(config.clone(), dir.path()).expect("open secure");
    let system = CallerContext::system_internal();
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "arcana".into(),
        }),
    )
    .await
    .expect("create project");
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateScope {
            owner_id: None,
            if_not_exists: true,
            project_id: "arcana".into(),
            scope_id: "app".into(),
        }),
    )
    .await
    .expect("create scope");
    db.commit_as(
        system,
        Mutation::KvSet {
            project_id: "arcana".into(),
            scope_id: "app".into(),
            key: b"k".to_vec(),
            value: b"v".to_vec(),
        },
    )
    .await
    .expect("write kv");
    db.backup_full(backup_dir.path())
        .await
        .expect("backup full");

    let manifest = crate::backup::load_backup_manifest(backup_dir.path(), config.hmac_key())
        .expect("manifest");
    let wal_name = manifest.wal_segments.first().expect("wal segment");
    let wal_path = backup_dir.path().join("wal_tail").join(wal_name);
    let mut wal_bytes = fs::read(&wal_path).expect("read wal");
    wal_bytes[0] ^= 0xFF;
    fs::write(&wal_path, wal_bytes).expect("corrupt wal");
    let mut manifest = crate::backup::load_backup_manifest(backup_dir.path(), config.hmac_key())
        .expect("manifest");
    manifest.file_sha256.insert(
        format!("wal_tail/{wal_name}"),
        crate::backup::sha256_file_hex(&wal_path).expect("rehash corrupted wal"),
    );
    crate::backup::write_backup_manifest(backup_dir.path(), &manifest, config.hmac_key())
        .expect("rewrite manifest");

    let err = AedbInstance::restore_from_backup_chain_at_time(
        &[backup_dir.path().to_path_buf()],
        restore_dir.path(),
        &config,
        u64::MAX,
    )
    .expect_err("time restore must reject invalid hash chain");
    assert!(
        format!("{err}").contains("bad segment header")
            || format!("{err}").contains("segment hash chain mismatch")
    );
}

#[tokio::test]
async fn removing_last_telemetry_hook_disables_commit_and_query_callbacks() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;

    let hook = Arc::new(RecordingTelemetryHook {
        queries: Arc::new(std::sync::Mutex::new(Vec::new())),
        commits: Arc::new(std::sync::Mutex::new(Vec::new())),
    });
    let hook_trait: Arc<dyn QueryCommitTelemetryHook> = hook.clone();
    db.add_telemetry_hook(hook_trait.clone());
    db.remove_telemetry_hook(&hook_trait);

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"telemetry:disabled".to_vec(),
        value: b"ok".to_vec(),
    })
    .await
    .expect("commit");
    let _ = db
        .query_no_auth(
            "p",
            "app",
            Query::select(&["id"]).from("items").limit(1),
            QueryOptions::default(),
        )
        .await
        .expect("query");

    assert!(hook.queries.lock().expect("query telemetry").is_empty());
    assert!(hook.commits.lock().expect("commit telemetry").is_empty());
}

#[tokio::test]
async fn query_sql_read_only_respects_adapter_scan_policy() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "items".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row::from_values(vec![Value::Integer(1)]),
    })
    .await
    .expect("seed");

    let err = db
        .query_sql_read_only(
            &RestrictiveSqlAdapter,
            "p",
            "app",
            "select * from items",
            ConsistencyMode::AtLatest,
        )
        .await
        .expect_err("adapter should not be forced into full scan mode");
    assert!(matches!(err, QueryError::InvalidQuery { .. }));
}

#[tokio::test]
async fn exists_and_explain_diagnostics_work() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;
    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "items".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row::from_values(vec![Value::Integer(1), Value::Text("a".into())]),
    })
    .await
    .expect("seed");

    let tx = db
        .begin_read_tx(ConsistencyMode::AtLatest)
        .await
        .expect("read tx");
    let exists = tx
        .exists(
            "p",
            "app",
            Query::select(&["id"])
                .from("items")
                .where_(Expr::Eq("id".into(), Value::Integer(1))),
        )
        .await
        .expect("exists");
    assert!(exists);

    let explain = tx
        .explain(
            "p",
            "app",
            Query::select(&["id"]).from("items"),
            QueryOptions::default(),
        )
        .expect("explain");
    assert!(explain.estimated_scan_rows >= 1);
    assert!(explain.stages.contains(&ExecutionStage::Scan));
    assert!(
        !explain.plan_trace.is_empty(),
        "explain should include access-path trace"
    );

    let with_diag = tx
        .query_with_diagnostics(
            "p",
            "app",
            Query::select(&["id"]).from("items"),
            QueryOptions {
                allow_full_scan: true,
                ..QueryOptions::default()
            },
        )
        .await
        .expect("diag query");
    assert_eq!(with_diag.result.rows.len(), 1);
    assert_eq!(with_diag.diagnostics.snapshot_seq, tx.snapshot_seq());
}

#[tokio::test]
async fn non_pk_text_eq_regression_in_project_scope_indexed_and_non_indexed_paths() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    let project_scope_id = "__project__";
    db.create_project("p").await.expect("project");
    db.create_scope("p", project_scope_id)
        .await
        .expect("project scope");
    create_table(
        &db,
        "p",
        project_scope_id,
        "sessions",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "user_id".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
            ColumnDef {
                name: "status".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;

    for (id, user_id, status) in [
        (1_i64, "8a25f1bc-ea96-48d0-8535-47b784a2df1d", "open"),
        (2, "8a25f1bc-ea96-48d0-8535-47b784a2df1d", "closed"),
        (3, "2cf2434c-ed95-4f35-b786-4853592e6f25", "open"),
    ] {
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: project_scope_id.into(),
            table_name: "sessions".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row::from_values(vec![
                Value::Integer(id),
                Value::Text(user_id.into()),
                Value::Text(status.into()),
            ]),
        })
        .await
        .expect("seed session");
    }

    let query = Query::select(&["id", "user_id"])
        .from("sessions")
        .where_(Expr::Eq(
            "user_id".into(),
            Value::Text("8a25f1bc-ea96-48d0-8535-47b784a2df1d".into()),
        ));
    let pre_index_result = db
        .query("p", project_scope_id, query.clone())
        .await
        .expect("eq query without index");
    assert_eq!(pre_index_result.rows.len(), 2);

    let pre_index_explain = db
        .explain_query(
            "p",
            project_scope_id,
            query.clone(),
            QueryOptions::default(),
        )
        .await
        .expect("explain without index");
    assert_eq!(
        pre_index_explain.predicate_evaluation_path,
        PredicateEvaluationPath::FullScanFilter
    );
    assert!(
        pre_index_explain.selected_indexes.is_empty(),
        "non-index path should not report selected indexes"
    );

    db.commit(Mutation::Ddl(DdlOperation::CreateIndex {
        project_id: "p".into(),
        scope_id: project_scope_id.into(),
        table_name: "sessions".into(),
        index_name: "by_user_id".into(),
        if_not_exists: false,
        columns: vec!["user_id".into()],
        index_type: IndexType::BTree,
        partial_filter: None,
    }))
    .await
    .expect("user_id index");

    let indexed_result = db
        .query("p", project_scope_id, query.clone())
        .await
        .expect("eq query with index");
    assert_eq!(indexed_result.rows.len(), 2);
    assert!(
        indexed_result.rows_examined <= pre_index_result.rows_examined,
        "indexed equality should not examine more rows"
    );

    let indexed_explain = db
        .explain_query("p", project_scope_id, query, QueryOptions::default())
        .await
        .expect("explain with index");
    assert_eq!(
        indexed_explain.predicate_evaluation_path,
        PredicateEvaluationPath::SecondaryIndexLookup
    );
    assert!(
        indexed_explain
            .selected_indexes
            .contains(&"by_user_id".to_string()),
        "explain should report selected secondary index"
    );
    assert!(
        indexed_explain
            .plan_trace
            .iter()
            .any(|line| line.contains("by_user_id")),
        "plan trace should include selected index name"
    );
}

#[tokio::test]
async fn uuid_text_equality_parity_between_primary_key_and_secondary_index_paths() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");

    create_table(
        &db,
        "p",
        "app",
        "users_pk_uuid",
        vec![
            ColumnDef {
                name: "user_uuid".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
            ColumnDef {
                name: "display_name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["user_uuid"],
    )
    .await;
    create_table(
        &db,
        "p",
        "app",
        "users_secondary_uuid",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "user_uuid".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
            ColumnDef {
                name: "display_name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;
    db.commit(Mutation::Ddl(DdlOperation::CreateIndex {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "users_secondary_uuid".into(),
        index_name: "by_user_uuid".into(),
        if_not_exists: false,
        columns: vec!["user_uuid".into()],
        index_type: IndexType::BTree,
        partial_filter: None,
    }))
    .await
    .expect("secondary uuid index");

    let rows = [
        (1_i64, "8e1e917f-f8f8-4f06-bf38-3f2c37dcd857", "alice"),
        (2, "6e0df6fd-5095-4e37-bf9d-1f6b5d6dfcb8", "bob"),
        (3, "1b631635-766d-4207-aa53-f5367b9bf13a", "carol"),
    ];
    for (_id, user_uuid, display_name) in rows {
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users_pk_uuid".into(),
            primary_key: vec![Value::Text(user_uuid.to_string().into())],
            row: Row::from_values(vec![
                Value::Text(user_uuid.to_string().into()),
                Value::Text(display_name.into()),
            ]),
        })
        .await
        .expect("seed pk uuid");
    }

    for (id, user_uuid, display_name) in rows {
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users_secondary_uuid".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row::from_values(vec![
                Value::Integer(id),
                Value::Text(user_uuid.into()),
                Value::Text(display_name.into()),
            ]),
        })
        .await
        .expect("seed secondary uuid");
    }

    for (_id, user_uuid, _display_name) in rows {
        let pk_query = Query::select(&["display_name"])
            .from("users_pk_uuid")
            .where_(Expr::Eq(
                "user_uuid".into(),
                Value::Text(user_uuid.to_string().into()),
            ));
        let secondary_query = Query::select(&["display_name"])
            .from("users_secondary_uuid")
            .where_(Expr::Eq(
                "user_uuid".into(),
                Value::Text(user_uuid.to_string().into()),
            ));

        let pk_result = db
            .query("p", "app", pk_query.clone())
            .await
            .expect("pk query");
        let secondary_result = db
            .query("p", "app", secondary_query.clone())
            .await
            .expect("secondary query");
        assert_eq!(pk_result.rows.len(), 1);
        assert_eq!(secondary_result.rows.len(), 1);
        assert_eq!(pk_result.rows[0].values, secondary_result.rows[0].values);
    }

    let pk_query = Query::select(&["display_name"])
        .from("users_pk_uuid")
        .where_(Expr::Eq(
            "user_uuid".into(),
            Value::Text("8e1e917f-f8f8-4f06-bf38-3f2c37dcd857".into()),
        ));
    let secondary_query = Query::select(&["display_name"])
        .from("users_secondary_uuid")
        .where_(Expr::Eq(
            "user_uuid".into(),
            Value::Text("8e1e917f-f8f8-4f06-bf38-3f2c37dcd857".into()),
        ));

    let pk_explain = db
        .explain_query("p", "app", pk_query, QueryOptions::default())
        .await
        .expect("pk explain");
    assert_eq!(
        pk_explain.predicate_evaluation_path,
        PredicateEvaluationPath::PrimaryKeyEqLookup
    );

    let secondary_explain = db
        .explain_query("p", "app", secondary_query, QueryOptions::default())
        .await
        .expect("secondary explain");
    assert_eq!(
        secondary_explain.predicate_evaluation_path,
        PredicateEvaluationPath::SecondaryIndexLookup
    );
    assert!(
        secondary_explain
            .selected_indexes
            .contains(&"by_user_uuid".to_string())
    );
}

#[tokio::test]
async fn u8_column_type_supports_write_read_and_indexed_equality() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");

    create_table(
        &db,
        "p",
        "app",
        "levels",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "level".into(),
                col_type: ColumnType::U8,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;

    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "levels".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row::from_values(vec![Value::Integer(1), Value::U8(7)]),
    })
    .await
    .expect("seed u8 row");

    let without_index = db
        .query(
            "p",
            "app",
            Query::select(&["id", "level"])
                .from("levels")
                .where_(Expr::Eq("level".into(), Value::Integer(7))),
        )
        .await
        .expect("u8 equality via integer literal");
    assert_eq!(without_index.rows.len(), 1);
    assert_eq!(without_index.rows[0].values[1], Value::U8(7));

    db.commit(Mutation::Ddl(DdlOperation::CreateIndex {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "levels".into(),
        index_name: "by_level".into(),
        if_not_exists: false,
        columns: vec!["level".into()],
        index_type: IndexType::BTree,
        partial_filter: None,
    }))
    .await
    .expect("create u8 index");

    let with_index = db
        .query(
            "p",
            "app",
            Query::select(&["id", "level"])
                .from("levels")
                .where_(Expr::Eq("level".into(), Value::U8(7))),
        )
        .await
        .expect("u8 equality with index");
    assert_eq!(with_index.rows.len(), 1);
    assert!(with_index.rows_examined <= without_index.rows_examined);

    let err = db
        .commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "levels".into(),
            primary_key: vec![Value::Integer(2)],
            row: Row::from_values(vec![Value::Integer(2), Value::Integer(8)]),
        })
        .await
        .expect_err("integer value should not satisfy U8 column type");
    assert!(matches!(err, AedbError::TypeMismatch { .. }));
}

#[tokio::test]
async fn u64_column_type_supports_write_read_and_indexed_equality() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");

    create_table(
        &db,
        "p",
        "app",
        "balances_u64",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "balance".into(),
                col_type: ColumnType::U64,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;

    db.commit(Mutation::Upsert {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "balances_u64".into(),
        primary_key: vec![Value::Integer(1)],
        row: Row::from_values(vec![Value::Integer(1), Value::U64(7)]),
    })
    .await
    .expect("seed u64 row");

    let without_index = db
        .query(
            "p",
            "app",
            Query::select(&["id", "balance"])
                .from("balances_u64")
                .where_(Expr::Eq("balance".into(), Value::Integer(7))),
        )
        .await
        .expect("u64 equality via integer literal");
    assert_eq!(without_index.rows.len(), 1);
    assert_eq!(without_index.rows[0].values[1], Value::U64(7));

    db.commit(Mutation::Ddl(DdlOperation::CreateIndex {
        project_id: "p".into(),
        scope_id: "app".into(),
        table_name: "balances_u64".into(),
        index_name: "by_balance_u64".into(),
        if_not_exists: false,
        columns: vec!["balance".into()],
        index_type: IndexType::BTree,
        partial_filter: None,
    }))
    .await
    .expect("create u64 index");

    let with_index = db
        .query(
            "p",
            "app",
            Query::select(&["id", "balance"])
                .from("balances_u64")
                .where_(Expr::Eq("balance".into(), Value::U64(7))),
        )
        .await
        .expect("u64 equality with index");
    assert_eq!(with_index.rows.len(), 1);
    assert!(with_index.rows_examined <= without_index.rows_examined);

    let err = db
        .commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "balances_u64".into(),
            primary_key: vec![Value::Integer(2)],
            row: Row::from_values(vec![Value::Integer(2), Value::Integer(8)]),
        })
        .await
        .expect_err("integer value should not satisfy U64 column type");
    assert!(matches!(err, AedbError::TypeMismatch { .. }));
}

#[tokio::test]
async fn sql_transaction_plan_helpers_commit() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;

    let plan = db
        .plan_sql_transaction(
            ConsistencyMode::AtLatest,
            vec![Mutation::Upsert {
                project_id: "p".into(),
                scope_id: "app".into(),
                table_name: "items".into(),
                primary_key: vec![Value::Integer(10)],
                row: Row::from_values(vec![Value::Integer(10)]),
            }],
        )
        .await
        .expect("plan");
    db.commit_sql_transaction_plan(plan)
        .await
        .expect("commit plan");

    let exists = db
        .exists(
            "p",
            "app",
            Query::select(&["id"])
                .from("items")
                .where_(Expr::Eq("id".into(), Value::Integer(10))),
            ConsistencyMode::AtLatest,
        )
        .await
        .expect("exists");
    assert!(exists);
}

#[tokio::test]
#[ignore = "long-running multi-agent user-perspective profile"]
async fn secure_multi_agent_profile_identifies_core_shortcomings() {
    fn u256_be(v: u64) -> [u8; 32] {
        let mut out = [0u8; 32];
        out[24..].copy_from_slice(&v.to_be_bytes());
        out
    }
    fn decode_u256_u64(bytes: [u8; 32]) -> u64 {
        let mut out = [0u8; 8];
        out.copy_from_slice(&bytes[24..]);
        u64::from_be_bytes(out)
    }

    #[derive(Debug, Default, Clone, Copy)]
    struct WorkerStats {
        attempted: usize,
        accepted: usize,
        rejected: usize,
        unauthorized_attempted: usize,
        unauthorized_denied: usize,
        latency_sum_us: u128,
        latency_max_us: u64,
    }

    #[derive(Debug, Default, Clone, Copy)]
    struct RuntimePeaks {
        queue_depth: usize,
        inflight: usize,
        durable_lag: u64,
        conflict_rate: f64,
        durable_wait_ops: u64,
        durable_wait_avg_us: u64,
        wal_append_ops: u64,
        wal_append_bytes: u64,
        wal_append_avg_us: u64,
        wal_sync_ops: u64,
        wal_sync_avg_us: u64,
        commit_errors: u64,
        permission_rejections: u64,
        validation_rejections: u64,
        queue_full_rejections: u64,
        timeout_rejections: u64,
        read_set_conflicts: u64,
    }

    let agents = std::env::var("AEDB_MULTI_AGENT_PROFILE_AGENTS")
        .ok()
        .and_then(|v| v.parse::<usize>().ok())
        .unwrap_or(12)
        .max(4);
    let ops_per_agent = std::env::var("AEDB_MULTI_AGENT_PROFILE_OPS")
        .ok()
        .and_then(|v| v.parse::<usize>().ok())
        .unwrap_or(800)
        .max(100);

    let dir = tempdir().expect("temp");
    let db = Arc::new(
        AedbInstance::open_secure(AedbConfig::production([3u8; 32]), dir.path())
            .expect("open secure"),
    );
    let system = CallerContext::system_internal();
    db.commit_as(
        system.clone(),
        Mutation::Ddl(DdlOperation::CreateProject {
            owner_id: None,
            if_not_exists: true,
            project_id: "p".into(),
        }),
    )
    .await
    .expect("create project");

    let agent_ids: Vec<String> = (0..agents).map(|i| format!("prof_agent_{i}")).collect();
    for a in &agent_ids {
        db.commit_as(
            system.clone(),
            Mutation::Ddl(DdlOperation::GrantPermission {
                caller_id: a.clone(),
                permission: Permission::KvRead {
                    project_id: "p".into(),
                    scope_id: Some("app".into()),
                    prefix: Some(format!("agent:{a}:").into_bytes()),
                },
                actor_id: Some("system".into()),
                delegable: false,
            }),
        )
        .await
        .expect("grant kv read");
        db.commit_as(
            system.clone(),
            Mutation::Ddl(DdlOperation::GrantPermission {
                caller_id: a.clone(),
                permission: Permission::KvWrite {
                    project_id: "p".into(),
                    scope_id: Some("app".into()),
                    prefix: Some(format!("agent:{a}:").into_bytes()),
                },
                actor_id: Some("system".into()),
                delegable: false,
            }),
        )
        .await
        .expect("grant kv write");
        db.commit_as(
            system.clone(),
            Mutation::KvSet {
                project_id: "p".into(),
                scope_id: "app".into(),
                key: format!("agent:{a}:balance").into_bytes(),
                value: u256_be(0).to_vec(),
            },
        )
        .await
        .expect("seed balance");
    }

    let done = Arc::new(tokio::sync::Notify::new());
    let peaks = Arc::new(std::sync::Mutex::new(RuntimePeaks::default()));
    let monitor_db = Arc::clone(&db);
    let monitor_done = Arc::clone(&done);
    let monitor_peaks = Arc::clone(&peaks);
    let monitor = tokio::spawn(async move {
        loop {
            tokio::select! {
                _ = monitor_done.notified() => break,
                _ = tokio::time::sleep(Duration::from_millis(10)) => {
                    let op = monitor_db.operational_metrics().await;
                    let mut p = monitor_peaks.lock().expect("peaks lock");
                    p.queue_depth = p.queue_depth.max(op.queue_depth);
                    p.inflight = p.inflight.max(op.inflight_commits);
                    p.durable_lag = p.durable_lag.max(op.durable_head_lag);
                    p.conflict_rate = p.conflict_rate.max(op.conflict_rate);
                    p.durable_wait_ops = p.durable_wait_ops.max(op.durable_wait_ops);
                    p.durable_wait_avg_us = p.durable_wait_avg_us.max(op.avg_durable_wait_micros);
                    p.wal_append_ops = p.wal_append_ops.max(op.wal_append_ops);
                    p.wal_append_bytes = p.wal_append_bytes.max(op.wal_append_bytes);
                    p.wal_append_avg_us = p.wal_append_avg_us.max(op.avg_wal_append_micros);
                    p.wal_sync_ops = p.wal_sync_ops.max(op.wal_sync_ops);
                    p.wal_sync_avg_us = p.wal_sync_avg_us.max(op.avg_wal_sync_micros);
                    p.commit_errors = p.commit_errors.max(op.commit_errors);
                    p.permission_rejections = p.permission_rejections.max(op.permission_rejections);
                    p.validation_rejections = p.validation_rejections.max(op.validation_rejections);
                    p.queue_full_rejections = p.queue_full_rejections.max(op.queue_full_rejections);
                    p.timeout_rejections = p.timeout_rejections.max(op.timeout_rejections);
                    p.read_set_conflicts = p.read_set_conflicts.max(op.read_set_conflicts);
                }
            }
        }
    });

    let started = Instant::now();
    let mut tasks = Vec::with_capacity(agent_ids.len());
    for (idx, agent) in agent_ids.iter().enumerate() {
        let db_clone = Arc::clone(&db);
        let caller = CallerContext::new(agent.clone());
        let own_key = format!("agent:{agent}:balance").into_bytes();
        let neighbor = &agent_ids[(idx + 1) % agent_ids.len()];
        let cross_key = format!("agent:{neighbor}:balance").into_bytes();
        tasks.push(tokio::spawn(async move {
            let mut stats = WorkerStats::default();
            for op in 0..ops_per_agent {
                stats.attempted += 1;
                let now = Instant::now();
                let res = db_clone
                    .commit_as(
                        caller.clone(),
                        Mutation::KvIncU256 {
                            project_id: "p".into(),
                            scope_id: "app".into(),
                            key: own_key.clone(),
                            amount_be: u256_be(1),
                        },
                    )
                    .await;
                let us = now.elapsed().as_micros() as u64;
                stats.latency_sum_us += us as u128;
                stats.latency_max_us = stats.latency_max_us.max(us);
                match res {
                    Ok(_) => stats.accepted += 1,
                    Err(AedbError::Validation(_)) | Err(AedbError::Conflict(_)) => {
                        stats.rejected += 1
                    }
                    Err(other) => return Err(other),
                }

                if op % 25 == 0 {
                    stats.unauthorized_attempted += 1;
                    let denied = db_clone
                        .commit_as(
                            caller.clone(),
                            Mutation::KvIncU256 {
                                project_id: "p".into(),
                                scope_id: "app".into(),
                                key: cross_key.clone(),
                                amount_be: u256_be(1),
                            },
                        )
                        .await;
                    if matches!(denied, Err(AedbError::PermissionDenied(_))) {
                        stats.unauthorized_denied += 1;
                    } else if let Err(other) = denied {
                        return Err(other);
                    }
                }
            }
            Ok::<_, AedbError>(stats)
        }));
    }

    let mut merged = WorkerStats::default();
    for t in tasks {
        let s = t.await.expect("join profile task").expect("profile run");
        merged.attempted += s.attempted;
        merged.accepted += s.accepted;
        merged.rejected += s.rejected;
        merged.unauthorized_attempted += s.unauthorized_attempted;
        merged.unauthorized_denied += s.unauthorized_denied;
        merged.latency_sum_us += s.latency_sum_us;
        merged.latency_max_us = merged.latency_max_us.max(s.latency_max_us);
    }

    done.notify_waiters();
    monitor.await.expect("join monitor");
    db.force_fsync().await.expect("force fsync");

    let elapsed = started.elapsed().as_secs_f64().max(0.001);
    let attempted_tps = (merged.attempted as f64 / elapsed) as u64;
    let accepted_tps = (merged.accepted as f64 / elapsed) as u64;
    let avg_lat_us = (merged.latency_sum_us / merged.attempted.max(1) as u128) as u64;
    let peaks = *peaks.lock().expect("peaks lock");
    let heads = db.head_state().await;

    eprintln!(
        "multi_agent_profile: agents={} ops_per_agent={} attempted={} accepted={} rejected={} unauthorized_denied={} attempted_tps={} accepted_tps={} avg_lat_us={} max_lat_us={} peak_queue_depth={} peak_inflight={} peak_durable_lag={} peak_conflict_rate={:.4} peak_durable_wait_ops={} peak_durable_wait_avg_us={} peak_wal_append_ops={} peak_wal_append_bytes={} peak_wal_append_avg_us={} peak_wal_sync_ops={} peak_wal_sync_avg_us={} peak_commit_errors={} peak_permission_rejections={} peak_validation_rejections={} peak_queue_full_rejections={} peak_timeout_rejections={} peak_read_set_conflicts={} heads(v={},d={})",
        agents,
        ops_per_agent,
        merged.attempted,
        merged.accepted,
        merged.rejected,
        merged.unauthorized_denied,
        attempted_tps,
        accepted_tps,
        avg_lat_us,
        merged.latency_max_us,
        peaks.queue_depth,
        peaks.inflight,
        peaks.durable_lag,
        peaks.conflict_rate,
        peaks.durable_wait_ops,
        peaks.durable_wait_avg_us,
        peaks.wal_append_ops,
        peaks.wal_append_bytes,
        peaks.wal_append_avg_us,
        peaks.wal_sync_ops,
        peaks.wal_sync_avg_us,
        peaks.commit_errors,
        peaks.permission_rejections,
        peaks.validation_rejections,
        peaks.queue_full_rejections,
        peaks.timeout_rejections,
        peaks.read_set_conflicts,
        heads.visible_head_seq,
        heads.durable_head_seq
    );

    assert_eq!(
        merged.accepted + merged.rejected,
        merged.attempted,
        "profile run must not drop operations"
    );
    assert_eq!(
        merged.unauthorized_denied, merged.unauthorized_attempted,
        "cross-agent unauthorized writes must always be denied"
    );
    assert!(
        heads.visible_head_seq >= heads.durable_head_seq,
        "visible head should be >= durable head"
    );
    assert!(
        attempted_tps >= 200,
        "profile indicates severe throughput regression: attempted_tps={attempted_tps}"
    );
    assert!(
        peaks.commit_errors <= (merged.rejected + merged.unauthorized_denied) as u64,
        "unexpected commit errors exceed rejected + unauthorized-denied operations"
    );
    assert_eq!(
        peaks.permission_rejections, merged.unauthorized_denied as u64,
        "permission rejection accounting must match denied unauthorized attempts"
    );
    assert_eq!(
        peaks.validation_rejections, merged.rejected as u64,
        "validation rejection accounting must match application-level rejected operations"
    );
    assert_eq!(
        peaks.queue_full_rejections, 0,
        "unexpected queue-full rejections under baseline profile load"
    );
    assert_eq!(
        peaks.timeout_rejections, 0,
        "unexpected timeout rejections under baseline profile load"
    );
    assert_eq!(
        peaks.durable_wait_ops, 0,
        "baseline non-durable profile should not accumulate durable wait operations"
    );
    assert!(
        peaks.wal_sync_ops > 0,
        "full-durability secure profile should execute WAL sync operations"
    );

    for agent in &agent_ids {
        let caller = CallerContext::new(agent.clone());
        let entry = db
            .kv_get(
                "p",
                "app",
                format!("agent:{agent}:balance").as_bytes(),
                ConsistencyMode::AtLatest,
                &caller,
            )
            .await
            .expect("read own balance")
            .expect("balance exists");
        let mut bal = [0u8; 32];
        bal.copy_from_slice(&entry.value);
        assert!(
            decode_u256_u64(bal) as usize <= ops_per_agent,
            "agent balance must not exceed own attempts"
        );
    }
}

#[tokio::test]
async fn strict_open_rejects_directory_previously_opened_in_non_strict_mode() {
    let dir = tempdir().expect("temp");
    let mut permissive = AedbConfig::production([7u8; 32]);
    permissive.recovery_mode = RecoveryMode::Permissive;
    permissive.hash_chain_required = false;

    let db = AedbInstance::open(permissive, dir.path()).expect("open permissive");
    db.shutdown().await.expect("shutdown permissive");

    let strict = AedbConfig::production([7u8; 32]);
    let err = match AedbInstance::open(strict, dir.path()) {
        Ok(db) => {
            db.shutdown().await.expect("shutdown unexpected strict db");
            panic!("strict open should fail closed");
        }
        Err(err) => err,
    };
    assert!(
        matches!(err, AedbError::Validation(ref msg) if msg.contains("strict open denied")),
        "unexpected error: {err}"
    );
}

#[tokio::test]
async fn strict_open_rejects_tampered_trust_mode_marker() {
    let dir = tempdir().expect("temp");
    let mut permissive = AedbConfig::production([9u8; 32]);
    permissive.recovery_mode = RecoveryMode::Permissive;
    permissive.hash_chain_required = false;

    let db = AedbInstance::open(permissive, dir.path()).expect("open permissive");
    db.shutdown().await.expect("shutdown permissive");

    fs::write(
        dir.path().join("trust_mode.json"),
        r#"{"ever_non_strict_recovery":false,"ever_hash_chain_disabled":false}"#,
    )
    .expect("tamper trust mode marker");

    let err = match AedbInstance::open(AedbConfig::production([9u8; 32]), dir.path()) {
        Ok(db) => {
            db.shutdown().await.expect("shutdown unexpected strict db");
            panic!("tampered trust marker should fail closed");
        }
        Err(err) => err,
    };
    assert!(
        matches!(err, AedbError::IntegrityError { ref message } if message.contains("trust mode marker hmac mismatch")),
        "unexpected error: {err}"
    );
}

#[tokio::test]
async fn list_with_total_respects_scan_bounds_for_count_queries() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_scan_rows: 3,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;

    for id in 1_i64..=5_i64 {
        db.commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row::from_values(vec![
                Value::Integer(id),
                Value::Text(format!("item-{id}").into()),
            ]),
        })
        .await
        .expect("insert item");
    }

    let err = db
        .list_with_total(
            "p",
            "app",
            Query::select(&["id"]).from("items"),
            None,
            None,
            2,
            ConsistencyMode::AtLatest,
        )
        .await
        .expect_err("count query should respect scan bounds");
    assert!(matches!(
        err,
        QueryError::ScanBoundExceeded {
            estimated_rows: 5,
            max_scan_rows: 3
        }
    ));
}

#[tokio::test]
async fn delete_where_rejects_deep_predicate_trees() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;

    let mut predicate = Expr::IsNotNull("id".into());
    for _ in 0..40 {
        predicate = predicate.and(Expr::IsNotNull("id".into()));
    }

    let err = db
        .commit(Mutation::DeleteWhere {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            predicate,
            limit: None,
        })
        .await
        .expect_err("deep predicate should be rejected");
    assert!(matches!(err, AedbError::Validation(ref msg) if msg.contains("expression depth")));
}

#[tokio::test]
async fn insert_batch_rejects_duplicate_primary_keys_within_same_batch() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "users",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;

    let err = db
        .insert_batch(
            "p",
            "app",
            "users",
            vec![
                Row::from_values(vec![Value::Integer(1), Value::Text("alice".into())]),
                Row::from_values(vec![Value::Integer(1), Value::Text("alice-dup".into())]),
            ],
        )
        .await
        .expect_err("duplicate PKs in one batch should fail before apply");
    assert!(matches!(err, AedbError::DuplicatePK { .. }));

    let rows = db
        .query_with_options(
            "p",
            "app",
            Query::select(&["id", "name"]).from("users").limit(10),
            QueryOptions::default(),
        )
        .await
        .expect("query users");
    assert!(
        rows.rows.is_empty(),
        "failed batch must not partially apply"
    );
}

#[tokio::test]
async fn delete_where_respects_scan_budget() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_scan_rows: 3,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;

    for id in 1_i64..=5_i64 {
        db.commit(Mutation::Insert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row::from_values(vec![Value::Integer(id)]),
        })
        .await
        .expect("insert item");
    }

    let err = db
        .commit(Mutation::DeleteWhere {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            predicate: Expr::IsNotNull("id".into()),
            limit: None,
        })
        .await
        .expect_err("delete_where should honor scan budget");
    assert!(
        matches!(err, AedbError::Validation(ref msg) if msg.contains("mutation scan bound exceeded"))
    );

    for id in 1_i64..=5_i64 {
        let row = db
            .query_with_options(
                "p",
                "app",
                Query::select(&["id"])
                    .from("items")
                    .where_(Expr::Eq("id".into(), Value::Integer(id)))
                    .limit(1),
                QueryOptions::default(),
            )
            .await
            .expect("query item by id");
        assert_eq!(row.rows.len(), 1, "row {id} should still exist");
    }
}

#[tokio::test]
async fn count_compare_assertions_respect_scan_budget() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_scan_rows: 3,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;

    for id in 1_i64..=5_i64 {
        db.commit(Mutation::Insert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row::from_values(vec![Value::Integer(id)]),
        })
        .await
        .expect("insert item");
    }

    let err = db
        .commit_envelope(TransactionEnvelope {
            caller: None,
            idempotency_key: None,
            write_class: WriteClass::Standard,
            assertions: vec![ReadAssertion::CountCompare {
                project_id: "p".into(),
                scope_id: "app".into(),
                table_name: "items".into(),
                filter: None,
                op: crate::commit::validation::CompareOp::Eq,
                threshold: 5,
            }],
            read_set: Default::default(),
            write_intent: WriteIntent {
                mutations: vec![Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"guard".to_vec(),
                    value: b"1".to_vec(),
                }],
            },
            base_seq: 0,
        })
        .await
        .expect_err("assertion scan should be bounded");
    assert!(
        matches!(err, AedbError::Validation(ref msg) if msg.contains("assertion scan bound exceeded"))
    );
}

#[tokio::test]
async fn preflight_uses_instance_config_limits() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_kv_value_bytes: 4,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let result = db
        .preflight(Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: b"k".to_vec(),
            value: b"too-large".to_vec(),
        })
        .await;
    assert!(
        matches!(result, crate::preflight::PreflightResult::Err { reason } if reason.contains("value too large"))
    );
}

#[tokio::test]
async fn commit_rejects_oversized_kv_before_enqueue() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_kv_value_bytes: 4,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    let err = db
        .kv_set("p", "app", b"k".to_vec(), b"too-large".to_vec())
        .await
        .expect_err("oversized kv write should fail");
    assert!(matches!(err, AedbError::Validation(ref msg) if msg.contains("kv value size")));
}

#[tokio::test]
async fn queries_reject_oversized_in_lists_and_like_patterns() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "name".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;

    let oversized_in = db
        .query_with_options(
            "p",
            "app",
            Query::select(&["id"]).from("items").where_(Expr::In(
                "id".into(),
                (0..10_001).map(Value::Integer).collect(),
            )),
            QueryOptions::default(),
        )
        .await
        .expect_err("oversized IN list should be rejected");
    assert!(
        matches!(oversized_in, QueryError::InvalidQuery { reason } if reason.contains("IN list"))
    );

    let oversized_like = db
        .query_with_options(
            "p",
            "app",
            Query::select(&["name"])
                .from("items")
                .where_(Expr::Like("name".into(), "a".repeat(257))),
            QueryOptions::default(),
        )
        .await
        .expect_err("oversized LIKE should be rejected");
    assert!(
        matches!(oversized_like, QueryError::InvalidQuery { reason } if reason.contains("LIKE pattern"))
    );
}

#[tokio::test]
async fn project_and_scope_admin_grants_allow_table_writes() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "project-admin".into(),
        permission: Permission::ProjectAdmin {
            project_id: "p".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant project admin");
    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "scope-admin".into(),
        permission: Permission::ScopeAdmin {
            project_id: "p".into(),
            scope_id: "app".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant scope admin");

    db.commit_as(
        CallerContext::new("project-admin"),
        Mutation::Insert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            primary_key: vec![Value::Integer(1)],
            row: Row::from_values(vec![Value::Integer(1)]),
        },
    )
    .await
    .expect("project admin insert");
    db.commit_as(
        CallerContext::new("scope-admin"),
        Mutation::Insert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            primary_key: vec![Value::Integer(2)],
            row: Row::from_values(vec![Value::Integer(2)]),
        },
    )
    .await
    .expect("scope admin insert");
}

#[tokio::test]
async fn delete_where_respects_row_level_read_policy() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "owner".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;

    for (id, owner) in [(1_i64, "alice"), (2_i64, "bob")] {
        db.commit(Mutation::Insert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            primary_key: vec![Value::Integer(id)],
            row: Row::from_values(vec![Value::Integer(id), Value::Text(owner.into())]),
        })
        .await
        .expect("seed item");
    }

    db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
        caller_id: "alice".into(),
        permission: Permission::TableWrite {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
        },
        actor_id: None,
        delegable: false,
    }))
    .await
    .expect("grant table write");
    db.set_read_policy(
        "p",
        "app",
        "items",
        Expr::Eq("owner".into(), Value::Text("$caller_id".into())),
    )
    .await
    .expect("set policy");

    db.commit_as(
        CallerContext::new("alice"),
        Mutation::DeleteWhere {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            predicate: Expr::IsNotNull("id".into()),
            limit: None,
        },
    )
    .await
    .expect("delete visible rows only");

    let remaining = db
        .query_with_options(
            "p",
            "app",
            Query::select(&["id", "owner"]).from("items").limit(10),
            QueryOptions::default(),
        )
        .await
        .expect("query remaining rows");
    assert_eq!(remaining.rows.len(), 1, "one row should remain");
    assert_eq!(remaining.rows[0].values[0], Value::Integer(2));
    assert_eq!(remaining.rows[0].values[1], Value::Text("bob".into()));
}

#[tokio::test]
async fn idempotency_keys_are_scoped_to_caller() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");

    for caller_id in ["alice", "bob"] {
        db.commit(Mutation::Ddl(DdlOperation::GrantPermission {
            caller_id: caller_id.into(),
            permission: Permission::KvWrite {
                project_id: "p".into(),
                scope_id: Some("app".into()),
                prefix: None,
            },
            actor_id: None,
            delegable: false,
        }))
        .await
        .expect("grant kv write");
    }

    let key = IdempotencyKey([7u8; 16]);
    let alice = db
        .commit_envelope(TransactionEnvelope {
            caller: Some(CallerContext::new("alice")),
            idempotency_key: Some(key.clone()),
            write_class: WriteClass::Standard,
            assertions: Vec::new(),
            read_set: Default::default(),
            write_intent: WriteIntent {
                mutations: vec![Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"alice".to_vec(),
                    value: b"1".to_vec(),
                }],
            },
            base_seq: 0,
        })
        .await
        .expect("alice commit");
    let bob = db
        .commit_envelope(TransactionEnvelope {
            caller: Some(CallerContext::new("bob")),
            idempotency_key: Some(key),
            write_class: WriteClass::Standard,
            assertions: Vec::new(),
            read_set: Default::default(),
            write_intent: WriteIntent {
                mutations: vec![Mutation::KvSet {
                    project_id: "p".into(),
                    scope_id: "app".into(),
                    key: b"bob".to_vec(),
                    value: b"1".to_vec(),
                }],
            },
            base_seq: 0,
        })
        .await
        .expect("bob commit");

    assert!(matches!(
        alice.idempotency,
        crate::commit::executor::IdempotencyOutcome::Applied
    ));
    assert!(matches!(
        bob.idempotency,
        crate::commit::executor::IdempotencyOutcome::Applied
    ));
    assert_ne!(alice.commit_seq, bob.commit_seq);
}

#[tokio::test]
async fn batch_mutations_respect_max_batch_rows() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_batch_rows: 3,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;

    let rows = (1_i64..=4_i64)
        .map(|id| Row::from_values(vec![Value::Integer(id)]))
        .collect::<Vec<_>>();
    let insert_err = db
        .commit(Mutation::InsertBatch {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            rows: rows.clone(),
        })
        .await
        .expect_err("insert batch should be bounded");
    assert!(matches!(insert_err, AedbError::Validation(ref msg) if msg.contains("max_batch_rows")));

    let upsert_err = db
        .commit(Mutation::UpsertBatch {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            rows,
        })
        .await
        .expect_err("upsert batch should be bounded");
    assert!(matches!(upsert_err, AedbError::Validation(ref msg) if msg.contains("max_batch_rows")));
}

#[tokio::test]
async fn emit_event_respects_max_event_payload_bytes() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_event_payload_bytes: 16,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");

    let err = db
        .emit_event(
            "p",
            "app",
            "topic",
            "event-1".into(),
            "{\"payload\":\"1234567890\"}".into(),
        )
        .await
        .expect_err("event payload should be bounded");
    assert!(
        matches!(err, AedbError::Validation(ref msg) if msg.contains("max_event_payload_bytes"))
    );
}

#[tokio::test]
async fn float_columns_reject_non_finite_values() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    create_table(
        &db,
        "p",
        "app",
        "metrics",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "value".into(),
                col_type: ColumnType::Float,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;

    let err = db
        .commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "metrics".into(),
            primary_key: vec![Value::Integer(1)],
            row: Row::from_values(vec![Value::Integer(1), Value::Float(f64::NAN)]),
        })
        .await
        .expect_err("nan should be rejected");
    assert!(matches!(err, AedbError::Validation(ref msg) if msg.contains("non-finite float")));
}

#[tokio::test]
async fn table_values_respect_max_table_value_bytes() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_table_value_bytes: 8,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    create_table(
        &db,
        "p",
        "app",
        "docs",
        vec![
            ColumnDef {
                name: "id".into(),
                col_type: ColumnType::Integer,
                nullable: false,
            },
            ColumnDef {
                name: "body".into(),
                col_type: ColumnType::Text,
                nullable: false,
            },
        ],
        vec!["id"],
    )
    .await;

    let err = db
        .commit(Mutation::Upsert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "docs".into(),
            primary_key: vec![Value::Integer(1)],
            row: Row::from_values(vec![
                Value::Integer(1),
                Value::Text("too-large-body".into()),
            ]),
        })
        .await
        .expect_err("oversized cell should be rejected");
    assert!(matches!(err, AedbError::Validation(ref msg) if msg.contains("max_table_value_bytes")));
}

#[tokio::test]
async fn managed_system_tables_reject_direct_user_mutations() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    let err = db
        .commit(Mutation::Upsert {
            project_id: crate::catalog::SYSTEM_PROJECT_ID.into(),
            scope_id: "app".into(),
            table_name: "reactive_processor_checkpoints".into(),
            primary_key: vec![Value::Text("processor".into())],
            row: Row {
                values: vec![
                    Value::Text("processor".into()),
                    Value::Integer(42),
                    Value::Timestamp(1),
                ],
            },
        })
        .await
        .expect_err("managed system tables must reject direct writes");

    assert!(
        matches!(err, AedbError::Validation(message) if message.contains("managed and read-only"))
    );
}

#[tokio::test]
async fn cascade_delete_respects_max_depth() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");

    for depth in 0..=9 {
        let table = format!("t{depth}");
        create_table(
            &db,
            "p",
            "app",
            &table,
            vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "parent_id".into(),
                    col_type: ColumnType::Integer,
                    nullable: true,
                },
            ],
            vec!["id"],
        )
        .await;
        if depth > 0 {
            db.commit_ddl(DdlOperation::AlterTable {
                project_id: "p".into(),
                scope_id: "app".into(),
                table_name: table.clone(),
                alteration: crate::catalog::schema::TableAlteration::AddForeignKey(
                    crate::catalog::schema::ForeignKey {
                        name: format!("fk_t{depth}_to_t{}", depth - 1),
                        columns: vec!["parent_id".into()],
                        references_project_id: "p".into(),
                        references_scope_id: "app".into(),
                        references_table: format!("t{}", depth - 1),
                        references_columns: vec!["id".into()],
                        on_delete: crate::catalog::schema::ForeignKeyAction::Cascade,
                        on_update: crate::catalog::schema::ForeignKeyAction::Cascade,
                    },
                ),
            })
            .await
            .expect("add fk");
        }
        db.commit(Mutation::Insert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: table,
            primary_key: vec![Value::Integer(depth as i64)],
            row: Row::from_values(vec![
                Value::Integer(depth as i64),
                if depth == 0 {
                    Value::Null
                } else {
                    Value::Integer((depth - 1) as i64)
                },
            ]),
        })
        .await
        .expect("insert row");
    }

    let err = db
        .commit(Mutation::Delete {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "t0".into(),
            primary_key: vec![Value::Integer(0)],
        })
        .await
        .expect_err("cascade depth should be bounded");
    assert!(
        matches!(err, AedbError::Validation(ref msg) if msg.contains("cascade delete depth exceeded"))
    );
}

#[tokio::test]
async fn memory_limit_is_enforced_before_wal_commit() {
    let dir = tempdir().expect("temp");
    let config = AedbConfig {
        max_memory_estimate_bytes: 500,
        ..AedbConfig::default()
    };
    let db = AedbInstance::open(config, dir.path()).expect("open");
    db.create_project("p").await.expect("project");
    db.create_scope("p", "app").await.expect("scope");
    create_table(
        &db,
        "p",
        "app",
        "items",
        vec![ColumnDef {
            name: "id".into(),
            col_type: ColumnType::Integer,
            nullable: false,
        }],
        vec!["id"],
    )
    .await;

    let err = db
        .commit(Mutation::Insert {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "items".into(),
            primary_key: vec![Value::Integer(1)],
            row: Row::from_values(vec![Value::Integer(1)]),
        })
        .await
        .expect_err("memory limit should reject commit");
    assert!(
        matches!(err, AedbError::Validation(ref msg) if msg.contains("memory estimate exceeded before WAL commit"))
    );

    let rows = db
        .query_with_options(
            "p",
            "app",
            Query::select(&["id"]).from("items").limit(10),
            QueryOptions::default(),
        )
        .await
        .expect("query rows");
    assert!(
        rows.rows.is_empty(),
        "rejected commit must leave no row behind"
    );
}

#[tokio::test]
async fn subscribe_commits_delivers_delta_after_commit() {
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(AedbConfig::default(), dir.path()).expect("open");

    db.create_project("p").await.expect("project");
    // Subscribe AFTER setup so we observe only the deltas under test.
    let mut rx = db.subscribe_commits();

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: b"hello".to_vec(),
        value: b"world".to_vec(),
    })
    .await
    .expect("commit");

    // Drain until we find our KvSet — internal subsystems may also produce
    // bookkeeping deltas, but ours must arrive within the timeout window.
    let mut saw_kv_set = false;
    let mut last_seq = 0u64;
    let deadline = std::time::Instant::now() + Duration::from_secs(2);
    while std::time::Instant::now() < deadline {
        match tokio::time::timeout(Duration::from_millis(200), rx.recv()).await {
            Ok(Ok(delta)) => {
                last_seq = last_seq.max(delta.seq);
                if delta
                    .mutations
                    .iter()
                    .any(|m| matches!(m, Mutation::KvSet { key, .. } if key == b"hello"))
                {
                    saw_kv_set = true;
                    break;
                }
            }
            Ok(Err(_)) => break,
            Err(_) => break,
        }
    }
    assert!(last_seq > 0, "broadcast delivered at least one delta");
    assert!(
        saw_kv_set,
        "broadcast delta must include the committed KvSet mutation"
    );

    db.shutdown().await.expect("shutdown");
}

#[tokio::test]
async fn subscribe_commits_lagged_subscriber_can_resume() {
    let mut config = AedbConfig::default();
    config.commit_broadcast_capacity = 2;
    let dir = tempdir().expect("temp");
    let db = AedbInstance::open(config, dir.path()).expect("open");

    let mut rx = db.subscribe_commits();
    db.create_project("p").await.expect("project");

    for i in 0..8u8 {
        db.commit(Mutation::KvSet {
            project_id: "p".into(),
            scope_id: "app".into(),
            key: vec![i],
            value: vec![i],
        })
        .await
        .expect("commit");
    }

    let mut saw_lagged = false;
    let mut delivered = 0usize;
    let deadline = std::time::Instant::now() + Duration::from_secs(2);
    while std::time::Instant::now() < deadline {
        match tokio::time::timeout(Duration::from_millis(200), rx.recv()).await {
            Ok(Ok(_delta)) => {
                delivered += 1;
                if saw_lagged && delivered >= 2 {
                    break;
                }
            }
            Ok(Err(tokio::sync::broadcast::error::RecvError::Lagged(_))) => {
                saw_lagged = true;
            }
            Ok(Err(tokio::sync::broadcast::error::RecvError::Closed)) => break,
            Err(_) => break,
        }
    }
    assert!(
        saw_lagged,
        "slow subscriber must observe RecvError::Lagged with capacity=2 + burst"
    );

    db.commit(Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "app".into(),
        key: vec![99],
        value: vec![99],
    })
    .await
    .expect("post-burst commit");

    let post = tokio::time::timeout(Duration::from_secs(1), rx.recv())
        .await
        .expect("post-burst delivery within timeout")
        .expect("post-burst delta");
    let resumed = post.mutations.iter().any(|m| {
        matches!(
            m,
            Mutation::KvSet { key, .. } if key == &[99u8]
        )
    });
    assert!(resumed, "subscriber must resume after Lagged error");

    db.shutdown().await.expect("shutdown");
}

#[test]
fn mutation_write_keys_table_row_variants() {
    use crate::commit::tx::WriteKey;

    let row = Row {
        values: vec![Value::Integer(7), Value::Text("a".into())],
    };
    let pk = vec![Value::Integer(7)];

    let insert = Mutation::Insert {
        project_id: "p".into(),
        scope_id: "s".into(),
        table_name: "t".into(),
        primary_key: pk.clone(),
        row: row.clone(),
    };
    let keys = insert.write_keys();
    assert_eq!(keys.len(), 1);
    assert!(matches!(
        &keys[0],
        WriteKey::TableRow { project_id, scope_id, table_name, primary_key }
            if project_id == "p" && scope_id == "s" && table_name == "t" && primary_key == &pk
    ));

    let delete = Mutation::Delete {
        project_id: "p".into(),
        scope_id: "s".into(),
        table_name: "t".into(),
        primary_key: pk.clone(),
    };
    let keys = delete.write_keys();
    assert!(matches!(&keys[0], WriteKey::TableRow { primary_key, .. } if primary_key == &pk));

    let batch = Mutation::InsertBatch {
        project_id: "p".into(),
        scope_id: "s".into(),
        table_name: "t".into(),
        rows: vec![row.clone()],
    };
    let keys = batch.write_keys();
    assert!(matches!(
        &keys[0],
        WriteKey::TableRange { table_name, .. } if table_name == "t"
    ));
}

#[test]
fn mutation_write_keys_kv_and_scope_variants() {
    use crate::commit::tx::WriteKey;

    let kv = Mutation::KvSet {
        project_id: "p".into(),
        scope_id: "s".into(),
        key: b"k".to_vec(),
        value: b"v".to_vec(),
    };
    let keys = kv.write_keys();
    assert_eq!(keys.len(), 1);
    assert!(matches!(
        &keys[0],
        WriteKey::KvKey { project_id, scope_id, key }
            if project_id == "p" && scope_id == "s" && key == b"k"
    ));

    let emit = Mutation::EmitEvent {
        project_id: "p".into(),
        scope_id: "s".into(),
        topic: "topic".into(),
        event_key: "ek".into(),
        payload_json: "{}".into(),
    };
    let keys = emit.write_keys();
    assert!(matches!(
        &keys[0],
        WriteKey::ScopeAll { project_id, scope_id }
            if project_id == "p" && scope_id == "s"
    ));
}

#[test]
fn read_set_intersects_positive_and_negative_cases() {
    use crate::commit::tx::{
        ReadBound, ReadKey, ReadRange, ReadRangeEntry, ReadSet, ReadSetEntry, WriteKey,
    };

    let mut rs = ReadSet::default();
    rs.points.push(ReadSetEntry {
        key: ReadKey::TableRow {
            project_id: "p".into(),
            scope_id: "s".into(),
            table_name: "t".into(),
            primary_key: vec![Value::Integer(1)],
        },
        version_at_read: 0,
    });
    rs.ranges.push(ReadRangeEntry {
        range: ReadRange::KvRange {
            project_id: "p".into(),
            scope_id: "s".into(),
            start: ReadBound::Included(b"a".to_vec()),
            end: ReadBound::Excluded(b"c".to_vec()),
        },
        max_version_at_read: 0,
        structural_version_at_read: 0,
    });

    // positive: same TableRow
    assert!(rs.intersects(&[WriteKey::TableRow {
        project_id: "p".into(),
        scope_id: "s".into(),
        table_name: "t".into(),
        primary_key: vec![Value::Integer(1)],
    }]));
    // positive: KvRange covers KvKey "b"
    assert!(rs.intersects(&[WriteKey::KvKey {
        project_id: "p".into(),
        scope_id: "s".into(),
        key: b"b".to_vec(),
    }]));
    // positive: ScopeAll matches any read in the scope
    assert!(rs.intersects(&[WriteKey::ScopeAll {
        project_id: "p".into(),
        scope_id: "s".into(),
    }]));
    // negative: disjoint scope
    assert!(!rs.intersects(&[WriteKey::TableRow {
        project_id: "p".into(),
        scope_id: "other".into(),
        table_name: "t".into(),
        primary_key: vec![Value::Integer(1)],
    }]));
    // negative: disjoint table
    assert!(!rs.intersects(&[WriteKey::TableRow {
        project_id: "p".into(),
        scope_id: "s".into(),
        table_name: "other".into(),
        primary_key: vec![Value::Integer(1)],
    }]));
    // negative: KvKey outside range ("c" is excluded end)
    assert!(!rs.intersects(&[WriteKey::KvKey {
        project_id: "p".into(),
        scope_id: "s".into(),
        key: b"c".to_vec(),
    }]));
    // negative: empty write set
    assert!(!rs.intersects(&[]));
    // negative: mismatching kinds (TableRow read vs KvKey write at same proj/scope)
    let rs_table_only = ReadSet {
        points: vec![ReadSetEntry {
            key: ReadKey::TableRow {
                project_id: "p".into(),
                scope_id: "s".into(),
                table_name: "t".into(),
                primary_key: vec![Value::Integer(1)],
            },
            version_at_read: 0,
        }],
        ranges: Vec::new(),
    };
    assert!(!rs_table_only.intersects(&[WriteKey::KvKey {
        project_id: "p".into(),
        scope_id: "s".into(),
        key: b"k".to_vec(),
    }]));
}
