---
phase: 12-asyncapi
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - Cargo.toml
  - src/lib.rs
  - src/async_api/mod.rs
autonomous: true
requirements:
  - ASYNC-01
  - ASYNC-02
  - ASYNC-03
  - ASYNC-04
tags:
  - rust
  - tokio
  - async
  - feature-flag

must_haves:
  truths:
    - "用户在 async fn 中调用 AsyncLogParser::new(path).parse().await 可得到 Vec<Sqllog>，无需手写 spawn_blocking"
    - "用户在 async fn 中调用 AsyncLogParser::new(path).with_filter(filter).parse().await 仅返回符合 filter 的记录"
    - "用户在 async fn 中调用 AsyncLogParser::new(path).encoding_hint(FileEncodingHint::Utf8).parse().await 等价于显式指定编码"
    - "不启用 async feature 时（cargo build），编译产物的依赖树中不出现 tokio crate"
    - "启用 async feature 时（cargo build --features async / cargo test --features async），编译通过且 tokio 在依赖树中"
    - "AsyncError::Parse(ParseError) 在文件不存在等 I/O 错误时被返回，AsyncError::Panic(String) 用于封装 spawn_blocking JoinError"
    - "整体覆盖率 cargo llvm-cov --workspace --all-features --fail-under-lines 90 通过（≥90%）"
  artifacts:
    - path: "Cargo.toml"
      provides: "tokio optional dependency + dev-dependency + [features] async 节"
      contains: "tokio"
    - path: "src/async_api/mod.rs"
      provides: "AsyncLogParser struct + AsyncError enum + #[cfg(test)] 单元测试块"
      exports: ["AsyncLogParser", "AsyncError"]
      min_lines: 80
    - path: "src/lib.rs"
      provides: "#[cfg(feature = \"async\")] 守卫下的 pub mod async_api + pub use 重导出"
      contains: "async_api"
  key_links:
    - from: "src/async_api/mod.rs"
      to: "src/parser/builder.rs (LogParserBuilder)"
      via: "spawn_blocking 闭包内 LogParserBuilder::new(&path).encoding_hint(hint).build()"
      pattern: "LogParserBuilder::new"
    - from: "src/async_api/mod.rs"
      to: "src/parser/iterator.rs (LogIterator::apply_filter)"
      via: "spawn_blocking 闭包内 iter.apply_filter(filter).filter_map(Result::ok).collect()"
      pattern: "apply_filter"
    - from: "src/async_api/mod.rs"
      to: "tokio::task::spawn_blocking"
      via: "tokio::task::spawn_blocking(move || { ... }).await"
      pattern: "spawn_blocking"
    - from: "src/lib.rs"
      to: "src/async_api/mod.rs"
      via: "#[cfg(feature = \"async\")] pub mod async_api; pub use async_api::{AsyncLogParser, AsyncError};"
      pattern: "pub mod async_api"
    - from: "Cargo.toml [features]"
      to: "Cargo.toml [dependencies] tokio (optional=true)"
      via: "async = [\"tokio/rt\"] 激活可选依赖"
      pattern: "async = \\[\"tokio/rt\"\\]"
---

<objective>
为 dm-database-parser-sqllog 添加 tokio async 接口层：实现 `AsyncLogParser` 结构体，通过 `tokio::task::spawn_blocking` 封装现有同步 mmap 解析路径，tokio 通过 `features = ["async"]` 可选引入。

Purpose: 让用户在 tokio async 上下文中无需手写 spawn_blocking 即可解析日志文件；保持非 async 用户零额外依赖。
Output: `src/async_api/mod.rs`（AsyncLogParser + AsyncError + 单元测试）、`Cargo.toml`（tokio 依赖 + feature）、`src/lib.rs`（async_api 重导出）。
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/REQUIREMENTS.md
@.planning/phases/12-asyncapi/12-CONTEXT.md
@.planning/phases/12-asyncapi/12-RESEARCH.md
@.planning/phases/12-asyncapi/12-PATTERNS.md

# 现有代码（read_first 中引用）
@Cargo.toml
@src/lib.rs
@src/async_api/mod.rs
@src/parser/builder.rs
@src/parser/iterator.rs
@src/parser/encoding.rs
@src/filter/builder.rs
@src/error.rs
@src/record.rs

<interfaces>
<!-- 关键 API 契约，executor 不需要二次探索 -->

From src/parser/builder.rs:
```rust
pub struct LogParserBuilder { /* path, encoding_hint: Option<FileEncodingHint> */ }
impl LogParserBuilder {
    pub fn new<P: AsRef<Path>>(path: P) -> Self;
    pub fn encoding_hint(mut self, hint: FileEncodingHint) -> Self;
    pub fn build(self) -> Result<LogParser, ParseError>;
}
```

From src/parser/encoding.rs:
```rust
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub enum FileEncodingHint {
    #[default] Auto,
    Utf8,
    Gb18030,
}
```

From src/parser/iterator.rs:
```rust
pub struct LogIterator<'a> { /* data: &'a [u8], pos, encoding, line_number */ }
impl<'a> LogIterator<'a> {
    pub fn apply_filter(self, filter: Filter)
        -> impl Iterator<Item = Result<Sqllog, ParseError>> + 'a;
    // 还有 skip_errors / filter_by_exec_time / filter_by_sql_contains / apply_filter_keep_errors
}
```

From src/parser/mod.rs (LogParser):
```rust
impl LogParser {
    pub fn iter(&self) -> LogIterator<'_>;
}
```

From src/filter/builder.rs:
```rust
pub struct Filter { /* predicates: Vec<Box<dyn Fn(&Sqllog) -> bool + Send + Sync>> */ }
pub struct FilterBuilder { /* ... */ }
impl FilterBuilder {
    pub fn new() -> Self;
    pub fn build(self) -> Filter;
    pub fn exec_time_gt(self, value: f32) -> Self; // 以及其他 50+ 谓词方法
}
```

From src/error.rs:
```rust
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ParseError {
    #[error("invalid format at line {line_number} | raw: {raw}")]
    InvalidFormat { raw: String, line_number: u64 },
    #[error("file not found or inaccessible: {path}")]
    FileNotFound { path: String },
    #[error("IO error: {0}")]
    IoError(String),
    // 还有其它变体
}
```

From src/record.rs:
```rust
pub struct Sqllog {
    pub ts: String,
    pub tag: Option<String>,
    pub ep: u8,
    pub sess_id: String, pub thrd_id: String, pub username: String,
    pub trxid: String, pub statement: String, pub appname: String,
    pub client_ip: String, pub sql: String,
    pub exectime: f32, pub rowcount: u32, pub exec_id: i64,
}
// 全字段为 String/原始数值类型 -> 'static + Send + Sync 自动满足
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: 在 Cargo.toml 中添加 tokio 可选依赖、dev-dependency 与 async feature</name>
  <files>Cargo.toml</files>
  <read_first>
    - Cargo.toml（当前依赖与节结构；务必先读取当前文件以避免破坏现有 [package] / [profile.release] / [[bench]] 节）
    - .planning/phases/12-asyncapi/12-CONTEXT.md（D-07 / D-08 锁定决策）
    - .planning/phases/12-asyncapi/12-RESEARCH.md（Standard Stack 节：tokio 1.52.3，crates.io 已验证）
    - .planning/phases/12-asyncapi/12-PATTERNS.md（Cargo.toml 章节：精确插入位置）
  </read_first>
  <action>
    按 CONTEXT.md D-07 与 D-08 修改 Cargo.toml，使 tokio 仅在 async feature 启用时进入库依赖树，而 dev-dependencies 中无条件可用以支持 `#[tokio::test]`：

    1. 在 `[dependencies]` 节末尾追加一行声明 tokio 为 optional：crate 名 `tokio`，版本约束 `"1"`，features 列表只包含 `"rt"`，`optional = true`。不要启用其它 tokio 子特性（不要 `rt-multi-thread`、`io-util`、`fs`、`net`、`time`、`sync`、`macros` 等）。
    2. 在 `[dev-dependencies]` 节末尾追加一行声明 tokio，版本约束 `"1"`，features 列表包含 `"rt"` 与 `"macros"`，**不**加 `optional = true`。`macros` 用于 `#[tokio::test]` 宏。
    3. 在 `[dev-dependencies]` 节之后、`[[bench]]` 节之前，新增 `[features]` 节，在其中声明 `async = ["tokio/rt"]`（feature 名为 `async`，激活 tokio 的 `rt` 子特性）。
    4. 不修改 `[package]`、`[lib]`、`[profile.release]`、`[[bench]]` 节的任何内容；不修改 atoi/memchr/thiserror/encoding/criterion/tempfile 现有依赖；不改 package 版本（保持 `1.1.0`，版本提升属于后续 publish 阶段）。

    严格按 CONTEXT.md D-07 的版本约束 `"1"`（不要写成 `"1.52.3"` 或 `"^1.0"`），与 RESEARCH.md Cargo.toml 变更（最终态）示例一致。
  </action>
  <acceptance_criteria>
    - `grep -c '^tokio = { version = "1", features = \["rt"\], optional = true }$' Cargo.toml` 输出 `1`
    - `grep -c '^tokio = { version = "1", features = \["rt", "macros"\] }$' Cargo.toml` 输出 `1`
    - `grep -c '^\[features\]$' Cargo.toml` 输出 `1`
    - `grep -c '^async = \["tokio/rt"\]$' Cargo.toml` 输出 `1`
    - `cargo build` 成功（无 async feature 时编译通过，async_api 不参与编译）
    - `cargo build --features async` 成功（async feature 启用时 tokio 进入依赖树，但 async_api/mod.rs 尚未填充实现，仍应编译通过——目前是占位注释文件）
    - `cargo metadata --format-version 1 --no-default-features 2>/dev/null | grep -c '"name":"tokio"'` 输出 `1`（仅来自 dev-dependencies；库不引入 tokio 到调用方）
    - `cargo metadata --format-version 1 --features async 2>/dev/null | grep -c '"name":"tokio"'` 输出 `>=1`
    - `[package]`、`[lib]`、`[profile.release]`、`[[bench]]` 节保持原样（git diff Cargo.toml 仅显示在 `[dependencies]`、`[dev-dependencies]` 末尾追加行 + 新增 `[features]` 节）
  </acceptance_criteria>
  <verify>
    <automated>cargo build &amp;&amp; cargo build --features async &amp;&amp; grep -c '^async = \["tokio/rt"\]$' Cargo.toml | grep -q '^1$'</automated>
  </verify>
  <done>
    Cargo.toml 包含 tokio optional dependency、tokio dev-dependency 与 `[features] async = ["tokio/rt"]`；`cargo build` 与 `cargo build --features async` 均通过；其余依赖与节未受影响。
  </done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: 实现 AsyncLogParser + AsyncError 并接入 lib.rs</name>
  <files>src/async_api/mod.rs, src/lib.rs</files>
  <read_first>
    - src/async_api/mod.rs（当前是 Phase 10 创建的占位注释文件）
    - src/lib.rs（当前 pub(crate) mod / pub use 列表；Phase 12 唯一需要修改的位置在文件末尾）
    - src/parser/builder.rs（LogParserBuilder 完整 API：`new<P: AsRef<Path>>(path)`、`encoding_hint(hint)`、`build() -> Result<LogParser, ParseError>`）
    - src/parser/iterator.rs（LogIterator::apply_filter 公开签名，第 68-73 行；adapter 内 apply_filter 是 pub(crate)，从 async_api 不可访问）
    - src/parser/encoding.rs（FileEncodingHint 三个变体 Auto/Utf8/Gb18030，已 derive Copy+Clone+Default，#[default] 在 Auto）
    - src/filter/builder.rs（Filter struct + FilterBuilder::new()/build()/exec_time_gt 等；Predicate 已是 Send + Sync）
    - src/error.rs（ParseError 完整变体，已 derive thiserror::Error）
    - src/record.rs（Sqllog 字段全为 String/原始数值 -> 自动 'static + Send + Sync）
    - .planning/phases/12-asyncapi/12-CONTEXT.md（D-01..D-06, D-09, D-10, D-11 锁定决策）
    - .planning/phases/12-asyncapi/12-RESEARCH.md（Pattern 1/2/3/4，Pitfall 1/2/3/5，AsyncLogParser 完整实现骨架，测试结构示例）
    - .planning/phases/12-asyncapi/12-PATTERNS.md（src/async_api/mod.rs 章节：imports / Builder pattern / spawn_blocking 核心 pattern / Error type pattern / Test structure pattern / `#[from]` 与手写 impl From 的冲突说明 / `#[cfg(not(miri))]` 测试守卫惯例 / apply_filter 调用路径）
  </read_first>
  <behavior>
    <!-- TDD 行为表，先以测试落地这些断言（详见 acceptance_criteria 中的具体测试列表） -->
    - AsyncLogParser::new(path) 构造默认实例（encoding_hint = Auto，filter = None），不进行 I/O
    - .encoding_hint(FileEncodingHint::Utf8) 链式覆盖编码 hint
    - .with_filter(filter) 链式设置过滤器
    - .parse().await 在合法日志文件上返回 Ok(Vec<Sqllog>)，记录数量与文件中可解析记录数一致
    - .parse().await 在不存在路径上返回 Err(AsyncError::Parse(ParseError::IoError(_))) 或 Err(AsyncError::Parse(ParseError::FileNotFound{..}))
    - .with_filter(FilterBuilder::new().exec_time_gt(100.0).build()).parse().await 仅保留 exectime > 100.0 的记录
    - 调用 .encoding_hint(FileEncodingHint::Utf8) 后解析 UTF-8 文件成功，证明 hint 已传递到 LogParserBuilder
    - AsyncError 实现 std::error::Error；AsyncError::Panic("msg").to_string() 包含 "msg"
    - AsyncError 可由 ParseError 自动转换（通过 #[from]）
  </behavior>
  <action>
    实现 Phase 12 主体代码，按 CONTEXT.md D-01..D-11 与 RESEARCH.md AsyncLogParser 完整实现骨架进行。分两个文件改动：

    **A. 完全重写 `src/async_api/mod.rs`**（当前仅占位注释一行）：

    1. 顶部 imports：`std::path::{Path, PathBuf}`；`crate::error::ParseError`；`crate::filter::builder::Filter`；`crate::parser::builder::LogParserBuilder`；`crate::parser::encoding::FileEncodingHint`；`crate::record::Sqllog`。不要在文件内部加 `#[cfg(feature = "async")]`——整个文件由 lib.rs 的 `#[cfg(feature = "async")]` 守卫控制。

    2. 定义 `pub struct AsyncLogParser`，字段：`path: PathBuf`、`encoding_hint: FileEncodingHint`（**不是** Option，直接持有，默认 `FileEncodingHint::Auto`）、`filter: Option<Filter>`。

    3. impl AsyncLogParser 四个方法：
       - `pub fn new(path: impl AsRef<Path>) -> Self`：path.as_ref().to_path_buf()；encoding_hint = FileEncodingHint::Auto；filter = None
       - `pub fn encoding_hint(mut self, hint: FileEncodingHint) -> Self`：消费 self，赋值字段，返回 self（D-11）
       - `pub fn with_filter(mut self, filter: Filter) -> Self`：消费 self，filter = Some(filter)，返回 self（D-05）
       - `pub async fn parse(self) -> Result<Vec<Sqllog>, AsyncError>`：见 4

    4. `parse(self)` 实现：
       - 先把 self 的 path / encoding_hint / filter 解构进局部变量（FileEncodingHint 是 Copy，可直接 move；PathBuf 与 Option<Filter> 直接 move）
       - 调用 `tokio::task::spawn_blocking(move || -> Result<Vec<Sqllog>, ParseError> { ... })`
       - 闭包内：`let parser = LogParserBuilder::new(&path).encoding_hint(encoding_hint).build()?;`，再 `let iter = parser.iter();`，然后分支收集：`Some(f)` → `iter.apply_filter(f).filter_map(Result::ok).collect()`，`None` → `iter.filter_map(Result::ok).collect()`；最终 `Ok(records)`
       - 闭包返回后 `.await`，链上的错误映射：先用 `.map_err(|join_err| AsyncError::Panic(join_err.to_string()))?` 处理 JoinError，再用 `.map_err(AsyncError::Parse)` 把内层 `ParseError` 转 `AsyncError::Parse`（注意：直接 `?` 因为已有 `#[from]`，但为可读性显式 map）
       - **不要**使用 `join_err.into_panic().downcast_ref::<...>()`（Pitfall 3：直接 to_string() 即可）
       - **不要**重新实现解析逻辑或直接调用 pub(crate) 的 `filter::adapter::apply_filter`，只通过 LogIterator 公开方法

    5. 定义 `pub enum AsyncError`，使用 `#[derive(Debug, thiserror::Error)]`：
       - 变体 `Parse(#[from] ParseError)`，error 消息 `"parse error: {0}"`
       - 变体 `Panic(String)`，error 消息 `"blocking task panicked: {0}"`
       - **不要**手写 `impl From<ParseError> for AsyncError`——`#[from]` 属性已生成此 impl，重复实现会导致 "duplicate impl" 编译错误（Critical Note 1）
       - **不要** derive Clone 或 PartialEq（与 ParseError 不同，跨线程语义不需要）

    6. 在文件末尾追加 `#[cfg(test)] mod tests { ... }` 块，写以下 `#[tokio::test]` 测试（**全部加 `#[cfg(not(miri))]`** 与项目惯例一致——见 PATTERNS.md `#[cfg(not(miri))]` 测试守卫）：
       - 测试 imports：`use super::*;`、`use std::io::Write;`、`use tempfile::NamedTempFile;`、`use crate::filter::builder::FilterBuilder;`、`use crate::parser::encoding::FileEncodingHint;`
       - 辅助函数 `fn write_test_file(content: &str) -> NamedTempFile`：与 PATTERNS.md 中给出的实现一致（NamedTempFile::new().unwrap()、write_all、返回）
       - `test_parse_returns_records`：写入单条标准记录 `"2025-11-17 16:09:41.123 (EP[0] sess:1 thrd:2 user:u trxid:3 stmt:4 appname:a) SELECT 1\n"`，断言 `records.len() == 1`
       - `test_parse_file_not_found_returns_error`：路径 `/nonexistent/path.log`，断言 `matches!(result, Err(AsyncError::Parse(_)))`
       - `test_parse_with_filter`：写入两条记录（exectime 200ms 与 50ms，格式见 RESEARCH.md 测试示例），filter 为 `FilterBuilder::new().exec_time_gt(100.0).build()`，断言 `records.len() == 1` 且 `records[0].exectime > 100.0`
       - `test_encoding_hint_propagated`：写入标准记录，调用 `.encoding_hint(FileEncodingHint::Utf8).parse().await`，断言 `records.len() == 1`（证明 hint 已传递且无 panic）
       - `test_async_error_is_error`：这是普通 `#[test]`（非 async），构造 `AsyncError::Panic("test".to_string())`，断言 `(&err as &dyn std::error::Error).to_string().contains("test")`
       - `test_async_error_from_parse_error`：构造 `let pe = ParseError::IoError("io".to_string()); let ae: AsyncError = pe.into();`，断言 `matches!(ae, AsyncError::Parse(_))`

    **B. 修改 `src/lib.rs`**（仅在文件末尾追加 4 行，不动现有 82-90 行的模块声明 / 重导出）：

    在第 90 行 `pub use record::Sqllog;` 之后追加：
    ```
    #[cfg(feature = "async")]
    pub mod async_api;
    #[cfg(feature = "async")]
    pub use async_api::{AsyncError, AsyncLogParser};
    ```

    注意：`async_api` 必须用 `pub mod`（不是 `pub(crate) mod`），因为下方 `pub use` 要求模块本身可见；`#[cfg(feature = "async")]` 守卫保证非 async 用户编译路径不变。
  </action>
  <acceptance_criteria>
    - `src/async_api/mod.rs` 文件存在，行数 ≥ 80
    - `grep -c '^pub struct AsyncLogParser {' src/async_api/mod.rs` 输出 `1`
    - `grep -c '^pub enum AsyncError {' src/async_api/mod.rs` 输出 `1`
    - `grep -c 'pub fn new<' src/async_api/mod.rs` ≥ `1`
    - `grep -c 'pub fn encoding_hint(mut self' src/async_api/mod.rs` 输出 `1`
    - `grep -c 'pub fn with_filter(mut self' src/async_api/mod.rs` 输出 `1`
    - `grep -c 'pub async fn parse(self) -> Result<Vec<Sqllog>, AsyncError>' src/async_api/mod.rs` 输出 `1`
    - `grep -c 'tokio::task::spawn_blocking' src/async_api/mod.rs` ≥ `1`
    - `grep -c 'LogParserBuilder::new' src/async_api/mod.rs` ≥ `1`
    - `grep -c '\.apply_filter(' src/async_api/mod.rs` ≥ `1`
    - `grep -c '#\[from\]' src/async_api/mod.rs` ≥ `1`（Parse 变体）
    - `grep -c 'impl From<ParseError> for AsyncError' src/async_api/mod.rs` 输出 `0`（不允许手写——会与 #[from] 冲突）
    - `grep -c '\.downcast_ref' src/async_api/mod.rs` 输出 `0`（不允许 Any downcast 模式）
    - `grep -c '#\[tokio::test\]' src/async_api/mod.rs` ≥ `4`
    - `src/lib.rs` 末尾包含 `#[cfg(feature = "async")] pub mod async_api;` 与 `pub use async_api::{AsyncError, AsyncLogParser};`：
      - `grep -c '#\[cfg(feature = "async")\]' src/lib.rs` ≥ `2`
      - `grep -c 'pub mod async_api;' src/lib.rs` 输出 `1`
      - `grep -c 'pub use async_api::' src/lib.rs` 输出 `1`
    - `cargo build` 通过（无 async feature 时 async_api 模块未编译，lib.rs 现有公开 API 不受影响）
    - `cargo build --features async` 通过（async feature 启用时编译完整 async_api 模块）
    - `cargo clippy --features async -- -D warnings` 零警告
    - `cargo clippy -- -D warnings` 零警告
    - `cargo test --features async` 全部测试通过，包括至少：`test_parse_returns_records`、`test_parse_file_not_found_returns_error`、`test_parse_with_filter`、`test_encoding_hint_propagated`、`test_async_error_is_error`、`test_async_error_from_parse_error`
    - `cargo test` 通过（非 async 路径测试仍全部通过）
    - `cargo llvm-cov --workspace --all-features --fail-under-lines 90` 通过（覆盖率 ≥90%）
  </acceptance_criteria>
  <verify>
    <automated>cargo build --features async &amp;&amp; cargo clippy --features async -- -D warnings &amp;&amp; cargo test --features async &amp;&amp; cargo test &amp;&amp; cargo llvm-cov --workspace --all-features --fail-under-lines 90</automated>
  </verify>
  <done>
    `src/async_api/mod.rs` 包含完整 AsyncLogParser/AsyncError 实现与 ≥6 个单元测试；`src/lib.rs` 添加 async_api feature-gated 重导出；`cargo test --features async`、`cargo test`、`cargo clippy --features async -- -D warnings` 全部通过；`cargo llvm-cov --workspace --all-features --fail-under-lines 90` 覆盖率 ≥90%。
  </done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| 用户代码 ↔ AsyncLogParser API | 用户在 async 上下文中传入文件路径与 Filter；untrusted input 仅为文件路径与 filter 谓词，无网络或外部反序列化 |
| async 任务 ↔ spawn_blocking 阻塞线程池 | tokio 内部管理；JoinError 信息（含 panic payload string）传回 async 任务，仅用于错误传播 |
| tokio runtime ↔ 文件系统（mmap/fs::read） | 复用 Phase 10 既有 LogParserBuilder 路径，未引入新文件读取代码 |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-12-01 | Information Disclosure | AsyncError::Panic 消息 | accept | `JoinError::to_string()` 仅包含 tokio 标准 panic 消息（"task X panicked with ..."），通常是开发期断言信息；本库无密钥/PII，文件路径已是调用方提供，泄露风险极低 |
| T-12-02 | Denial of Service | spawn_blocking 线程池耗尽 | accept | tokio 默认 blocking pool 上限 512，远超日志解析单次需求；调用方控制并发；超载属于调用方运行时配置问题 |
| T-12-03 | Tampering | tokio 依赖（crates.io） | mitigate | Package Legitimacy Audit 已 VERIFIED：tokio 1.x，下载量 688M+，github.com/tokio-rs/tokio 官方仓库，无 [ASSUMED]/[SUS]/[SLOP] |
| T-12-04 | Repudiation | AsyncError 错误信息完整性 | mitigate | `#[derive(thiserror::Error)]` 与 `#[from]` 保证 ParseError 完整传播；`Display` impl 输出 "parse error: {0}" / "blocking task panicked: {0}"，便于日志追踪 |
| T-12-05 | Elevation of Privilege | spawn_blocking 闭包 'static 约束绕过 | mitigate | 闭包内 `path: PathBuf` / `filter: Option<Filter>` / `encoding_hint: FileEncodingHint`（Copy）全部 move 进闭包；`AsyncLogParser::parse(self)` 消费 self，无 `&self` 借用逃逸 |
| T-12-06 | Spoofing | feature flag 误用导致 tokio 强制依赖 | mitigate | `tokio = { ..., optional = true }` + `async = ["tokio/rt"]`：CI 验证 `cargo build`（no feature）的 `cargo metadata --no-default-features` 无 tokio 库依赖 |
| T-12-SC | Tampering | cargo crate 安装 | accept | 本 plan 仅修改 Cargo.toml 添加已有 crate（tokio），无 npm/pip/cargo 新生态包；Package Legitimacy Audit 已批准 |
</threat_model>

<verification>
## Phase-level checks

1. **Cargo.toml 形态正确：**
   - `cargo build` 成功（默认无 async feature，库依赖树不含 tokio 作为公共依赖）
   - `cargo build --features async` 成功
   - `cargo metadata --format-version 1 --no-default-features | grep -c '"name":"tokio"'` 仅来自 dev-dependencies（视构建模式而定，库公开依赖中应无 tokio）

2. **lib.rs 公开 API：**
   - 默认构建：`use dm_database_parser_sqllog::AsyncLogParser` 编译失败（async feature 未启用）
   - `--features async`：`use dm_database_parser_sqllog::{AsyncLogParser, AsyncError}` 可见
   - 现有 `use dm_database_parser_sqllog::{LogParser, LogParserBuilder, Sqllog, Filter, FilterBuilder, FileEncodingHint, ParseError}` 在两种构建下均可见

3. **async API 行为（cargo test --features async）：**
   - 基础解析：`AsyncLogParser::new(path).parse().await` 返回 `Ok(Vec<Sqllog>)`
   - filter 集成：`AsyncLogParser::new(path).with_filter(f).parse().await` 在 spawn_blocking 内执行过滤
   - encoding_hint：`.encoding_hint(FileEncodingHint::Utf8)` 路径不 panic
   - 错误路径：不存在文件返回 `Err(AsyncError::Parse(_))`
   - `AsyncError` 实现 `std::error::Error`，可被 `Box<dyn Error>` 容纳

4. **代码质量：**
   - `cargo clippy -- -D warnings` 零警告
   - `cargo clippy --features async -- -D warnings` 零警告
   - `cargo fmt --check` 通过（若 CI 检查格式）

5. **覆盖率：** `cargo llvm-cov --workspace --all-features --fail-under-lines 90` ≥90%
</verification>

<success_criteria>
- ROADMAP.md Phase 12 五条 Success Criteria 全部满足：
  1. ✅ 用户可在 `async fn` 中调用 `AsyncLogParser::new(path).parse().await` 得到 `Vec<Sqllog>`（等价于 ROADMAP 中 `parse_file_async(path).await`，builder 模式实现）
  2. ✅ 内部通过 `tokio::task::spawn_blocking` 封装同步 mmap 解析路径，返回 `Vec<Sqllog>`（owned，'static）
  3. ✅ tokio 仅在 `features = ["async"]` 启用时进入依赖树；默认 `cargo build` 不引入 tokio
  4. ✅ async API 接受 `FilterBuilder` 产物（`Filter`），filter 在 `spawn_blocking` 内部完成
  5. ✅ `cargo test --features async` 全部通过；`cargo llvm-cov --workspace --all-features --fail-under-lines 90` ≥90%
- 四条 ASYNC-XX requirements 全部对应任务实现：ASYNC-01/02/04 → Task 2；ASYNC-03 → Task 1
- 全部 11 条 CONTEXT.md 锁定决策（D-01..D-11）已对应实现
</success_criteria>

<output>
Create `.planning/phases/12-asyncapi/12-01-SUMMARY.md` when done
</output>
