ln-642-layer-boundary-auditor

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Layer Boundary Auditor

架构层边界审计器

L3 Worker that audits architectural layer boundaries and detects violations.
用于审计架构层边界并检测违规问题的L3 Worker。

Purpose & Scope

目标与范围

  • Read architecture.md to discover project's layer structure
  • Detect layer violations (I/O code outside infrastructure layer)
  • Detect cross-layer consistency issues:
    • Transaction boundaries (commit/rollback ownership)
    • Session ownership (DI vs local)
    • Async consistency (sync I/O in async)
    • Fire-and-forget tasks (unhandled exceptions)
  • Check pattern coverage (all HTTP calls use client abstraction)
  • Detect error handling duplication
  • Return violations list to coordinator
  • 读取architecture.md文件以了解项目的分层结构
  • 检测分层违规问题(如基础设施层外出现I/O代码)
  • 检测跨层一致性问题:
    • 事务边界(提交/回滚归属)
    • 会话归属(DI vs 本地创建)
    • 异步一致性(异步函数中的同步I/O)
    • 即发即弃任务(未处理异常)
  • 检查模式覆盖率(所有HTTP调用是否使用客户端抽象)
  • 检测错误处理逻辑重复问题
  • 向协调器返回违规问题列表

Input (from ln-640)

输入参数(来自ln-640)

- architecture_path: string    # Path to docs/architecture.md
- codebase_root: string        # Root directory to scan
- skip_violations: string[]    # Files to skip (legacy)
- architecture_path: string    # 指向docs/architecture.md的路径
- codebase_root: string        # 代码库根目录
- skip_violations: string[]    # 需跳过检测的文件(遗留代码)

Workflow

工作流程

Phase 1: Discover Architecture

阶段1:识别架构

Read docs/architecture.md

Extract from Section 4.2 (Top-Level Decomposition):
  - architecture_type: "Layered" | "Hexagonal" | "Clean" | "MVC" | etc.
  - layers: [{name, directories[], purpose}]

Extract from Section 5.3 (Infrastructure Layer Components):
  - infrastructure_components: [{name, responsibility}]

IF architecture.md not found:
  Use fallback presets from common_patterns.md

Build ruleset:
  FOR EACH layer:
    allowed_deps = layers that can be imported
    forbidden_deps = layers that cannot be imported
读取docs/architecture.md文件

从4.2节(顶层分解)提取信息:
  - architecture_type: "Layered" | "Hexagonal" | "Clean" | "MVC" | etc.
  - layers: [{name, directories[], purpose}]

从5.3节(基础设施层组件)提取信息:
  - infrastructure_components: [{name, responsibility}]

如果未找到architecture.md文件:
  使用common_patterns.md中的预设规则作为备选

构建规则集:
  遍历每个分层:
    allowed_deps = 允许依赖的分层
    forbidden_deps = 禁止依赖的分层

Phase 2: Detect Layer Violations

阶段2:检测分层违规问题

FOR EACH violation_type IN common_patterns.md I/O Pattern Boundary Rules:
  grep_pattern = violation_type.detection_grep
  forbidden_dirs = violation_type.forbidden_in

  matches = Grep(grep_pattern, codebase_root, include="*.py,*.ts,*.js")

  FOR EACH match IN matches:
    IF match.path NOT IN skip_violations:
      IF any(forbidden IN match.path FOR forbidden IN forbidden_dirs):
        violations.append({
          type: "layer_violation",
          severity: "HIGH",
          pattern: violation_type.name,
          file: match.path,
          line: match.line,
          code: match.context,
          allowed_in: violation_type.allowed_in,
          suggestion: f"Move to {violation_type.allowed_in}"
        })
遍历common_patterns.md中I/O模式边界规则的每种违规类型:
  grep_pattern = 违规类型的检测正则
  forbidden_dirs = 该违规类型禁止出现的目录

  matches = 对代码库根目录执行Grep匹配(包含*.py,*.ts,*.js文件)

  遍历每个匹配结果:
    如果匹配文件不在skip_violations列表中:
      如果匹配文件路径包含任何forbidden_dirs中的目录:
        将以下信息添加到违规列表:
          type: "layer_violation",
          severity: "HIGH",
          pattern: 违规类型名称,
          file: 匹配文件路径,
          line: 匹配行号,
          code: 匹配上下文代码,
          allowed_in: 该代码允许出现的目录,
          suggestion: f"建议迁移至{violation_type.allowed_in}"

Phase 2.5: Cross-Layer Consistency Checks

阶段2.5:跨层一致性检查

2.5.1 Transaction Boundary Violations

2.5.1 事务边界违规

What: commit()/rollback() called at inconsistent layers (repo + service + API)
Detection:
repo_commits = Grep("\.commit\(\)|\.rollback\(\)", "**/repositories/**/*.py")
service_commits = Grep("\.commit\(\)|\.rollback\(\)", "**/services/**/*.py")
api_commits = Grep("\.commit\(\)|\.rollback\(\)", "**/api/**/*.py")

layers_with_commits = count([repo_commits, service_commits, api_commits].filter(len > 0))
Safe Patterns (ignore):
  • Comment "# best-effort telemetry" in same context
  • File ends with
    _callbacks.py
    (progress notifiers)
  • Explicit
    # UoW boundary
    comment
Violation Rules:
ConditionSeverityIssue
layers_with_commits >= 3CRITICALMixed UoW ownership across all layers
repo + api commitsHIGHTransaction control bypasses service layer
repo + service commitsHIGHAmbiguous UoW owner (repo vs service)
service + api commitsMEDIUMTransaction control spans service + API
Recommendation: Choose single UoW owner (service layer recommended), remove commit() from other layers
Effort: L (requires architectural decision + refactoring)
问题描述: commit()/rollback()在不一致的分层中被调用(仓库层 + 服务层 + API层)
检测逻辑:
repo_commits = 在**/repositories/**/*.py文件中匹配"\.commit\(\)|\.rollback\(\)"
service_commits = 在**/services/**/*.py文件中匹配"\.commit\(\)|\.rollback\(\)"
api_commits = 在**/api/**/*.py文件中匹配"\.commit\(\)|\.rollback\(\)"

layers_with_commits = 统计存在匹配结果的分层数量
安全模式(忽略):
  • 上下文包含注释"# best-effort telemetry"
  • 文件以
    _callbacks.py
    结尾(进度通知器)
  • 存在明确的
    # UoW boundary
    注释
违规判定规则:
条件严重程度问题说明
layers_with_commits >= 3CRITICAL所有分层中存在混合的工作单元(UoW)归属
仓库层和API层均有commit调用HIGH事务控制绕过了服务层
仓库层和服务层均有commit调用HIGH工作单元(UoW)归属不明确(仓库层 vs 服务层)
服务层和API层均有commit调用MEDIUM事务控制跨越了服务层和API层
修复建议: 选择单一的工作单元(UoW)归属层(推荐服务层),移除其他分层中的commit()调用
修复工作量: L(需要架构决策和代码重构)

2.5.2 Session Ownership Violations

2.5.2 会话归属违规

What: Mixed DI-injected and locally-created sessions in same call chain
Detection:
di_session = Grep("Depends\(get_session\)|Depends\(get_db\)", "**/api/**/*.py")
local_session = Grep("AsyncSessionLocal\(\)|async_sessionmaker", "**/services/**/*.py")
local_in_repo = Grep("AsyncSessionLocal\(\)", "**/repositories/**/*.py")
Violation Rules:
ConditionSeverityIssue
di_session AND local_in_repo in same moduleHIGHRepo creates own session while API injects different
local_session in service calling DI-based repoMEDIUMSession mismatch in call chain
Recommendation: Use DI consistently OR use local sessions consistently. Document exceptions (e.g., telemetry)
Effort: M
问题描述: 同一调用链中同时存在依赖注入(DI)注入的会话和本地创建的会话
检测逻辑:
di_session = 在**/api/**/*.py文件中匹配"Depends\(get_session\)|Depends\(get_db\)"
local_session = 在**/services/**/*.py文件中匹配"AsyncSessionLocal\(\)|async_sessionmaker"
local_in_repo = 在**/repositories/**/*.py文件中匹配"AsyncSessionLocal\(\)"
违规判定规则:
条件严重程度问题说明
同一模块中同时存在DI会话和仓库层本地会话HIGH仓库层创建自有会话,而API层注入不同的会话
服务层使用本地会话,同时调用基于DI的仓库层MEDIUM调用链中存在会话不匹配问题
修复建议: 统一使用DI注入会话,或统一使用本地创建的会话。对例外情况进行文档说明(如遥测场景)
修复工作量: M

2.5.3 Async Consistency Violations

2.5.3 异步一致性违规

What: Synchronous blocking I/O inside async functions
Detection:
undefined
问题描述: 异步函数中存在同步阻塞式I/O操作
检测逻辑:
undefined

For each file with "async def":

遍历所有包含"async def"的文件:

sync_file_io = Grep(".read_bytes()|.read_text()|.write_bytes()|.write_text()", file) sync_open = Grep("(?<!aiofiles.)open(", file) # open() not preceded by aiofiles.
sync_file_io = 在文件中匹配".read_bytes()|.read_text()|.write_bytes()|.write_text()" sync_open = 在文件中匹配"(?<!aiofiles.)open(" # 匹配未使用aiofiles前缀的open()调用

Safe patterns (not violations):

安全模式(不判定为违规):

- "asyncio.to_thread(" wrapping the call

- 调用被"asyncio.to_thread("包裹

- "await aiofiles.open("

- 调用为"await aiofiles.open("

- "run_in_executor(" wrapping the call

- 调用被"run_in_executor("包裹


**Violation Rules:**

| Pattern | Severity | Issue |
|---------|----------|-------|
| Path.read_bytes() in async def | HIGH | Blocking file read in async context |
| open() without aiofiles in async def | HIGH | Blocking file operation |
| time.sleep() in async def | HIGH | Blocking sleep (use asyncio.sleep) |

**Recommendation:** Use `asyncio.to_thread()` or `aiofiles` for file I/O in async functions

**Effort:** S-M

**违规判定规则:**

| 模式 | 严重程度 | 问题说明 |
|---------|----------|-------|
| async def函数中存在Path.read_bytes()调用 | HIGH | 异步上下文存在阻塞式文件读取操作 |
| async def函数中存在未使用aiofiles的open()调用 | HIGH | 存在阻塞式文件操作 |
| async def函数中存在time.sleep()调用 | HIGH | 存在阻塞式sleep(建议使用asyncio.sleep) |

**修复建议:** 在异步函数中使用`asyncio.to_thread()`或`aiofiles`处理文件I/O操作

**修复工作量:** S-M

2.5.4 Fire-and-Forget Violations

2.5.4 即发即弃任务违规

What: asyncio.create_task() without error handling
Detection:
all_tasks = Grep("create_task\(", codebase)
问题描述: asyncio.create_task()调用未添加错误处理
检测逻辑:
all_tasks = 在代码库中匹配"create_task\("

For each match, check context:

遍历每个匹配结果,检查上下文:

- Has .add_done_callback() → OK

- 包含.add_done_callback() → 合规

- Assigned to variable with later await → OK

- 任务被赋值给变量并在后续使用await → 合规

- Has "# fire-and-forget" comment → OK (documented intent)

- 包含"# fire-and-forget"注释 → 合规(已记录意图)

- None of above → VIOLATION

- 以上均不满足 → 违规


**Violation Rules:**

| Pattern | Severity | Issue |
|---------|----------|-------|
| create_task() without handler or comment | MEDIUM | Unhandled task exception possible |
| create_task() in loop without error collection | HIGH | Multiple silent failures possible |

**Recommendation:** Add `task.add_done_callback(handle_exception)` or document intent with comment

**Effort:** S

---

**违规判定规则:**

| 模式 | 严重程度 | 问题说明 |
|---------|----------|-------|
| create_task()调用无错误处理或注释 | MEDIUM | 可能存在未处理的任务异常 |
| 循环中调用create_task()且未收集错误 | HIGH | 可能存在多个静默失败的任务 |

**修复建议:** 添加`task.add_done_callback(handle_exception)`或使用注释记录意图

**修复工作量:** S

---

Phase 3: Check Pattern Coverage

阶段3:检查模式覆盖率

undefined
undefined

HTTP Client Coverage

HTTP客户端抽象覆盖率检查

all_http_calls = Grep("httpx\.|aiohttp\.|requests\.", codebase_root) abstracted_calls = Grep("client\.(get|post|put|delete)", infrastructure_dirs)
IF len(all_http_calls) > 0: coverage = len(abstracted_calls) / len(all_http_calls) * 100 IF coverage < 90%: violations.append({ type: "low_coverage", severity: "MEDIUM", pattern: "HTTP Client Abstraction", coverage: coverage, uncovered_files: files with direct calls outside infrastructure })
all_http_calls = 在代码库根目录中匹配"httpx\.|aiohttp\.|requests\." abstracted_calls = 在基础设施层目录中匹配"client\.(get|post|put|delete)"
如果all_http_calls数量大于0: coverage = len(abstracted_calls) / len(all_http_calls) * 100 如果coverage < 90%: 向违规列表添加以下信息: type: "low_coverage", severity: "MEDIUM", pattern: "HTTP Client Abstraction", coverage: coverage, uncovered_files: 基础设施层外存在直接HTTP调用的文件

Error Handling Duplication

错误处理逻辑重复检查

http_error_handlers = Grep("except\s+(httpx\.|aiohttp\.|requests\.)", codebase_root) unique_files = set(f.path for f in http_error_handlers)
IF len(unique_files) > 2: violations.append({ type: "duplication", severity: "MEDIUM", pattern: "HTTP Error Handling", files: list(unique_files), suggestion: "Centralize in infrastructure layer" })
undefined
http_error_handlers = 在代码库根目录中匹配"except\s+(httpx\.|aiohttp\.|requests\.)" unique_files = 去重后的匹配文件集合
如果len(unique_files) > 2: 向违规列表添加以下信息: type: "duplication", severity: "MEDIUM", pattern: "HTTP Error Handling", files: list(unique_files), suggestion: "建议将错误处理逻辑集中到基础设施层"
undefined

Phase 3.5: Calculate Score

阶段3.5:计算审计分数

See
shared/references/audit_scoring.md
for unified formula and score interpretation.
统一公式和分数解释请参考
shared/references/audit_scoring.md

Phase 4: Return Result

阶段4:返回结果

json
{
  "category": "Layer Boundary",
  "score": 4.5,
  "total_issues": 8,
  "critical": 1,
  "high": 3,
  "medium": 4,
  "low": 0,
  "architecture": {
    "type": "Layered",
    "layers": ["api", "services", "domain", "infrastructure"]
  },
  "checks": [
    {"id": "io_isolation", "name": "I/O Isolation", "status": "failed", "details": "HTTP client found in domain layer"},
    {"id": "http_abstraction", "name": "HTTP Abstraction", "status": "warning", "details": "75% coverage, 3 direct calls outside infrastructure"},
    {"id": "error_centralization", "name": "Error Centralization", "status": "failed", "details": "HTTP error handlers in 4 files, should be centralized"},
    {"id": "transaction_boundary", "name": "Transaction Boundary", "status": "failed", "details": "commit() in repos (3), services (2), api (4) - mixed UoW ownership"},
    {"id": "session_ownership", "name": "Session Ownership", "status": "passed", "details": "DI-based sessions used consistently"},
    {"id": "async_consistency", "name": "Async Consistency", "status": "failed", "details": "Blocking I/O in async functions detected"},
    {"id": "fire_and_forget", "name": "Fire-and-Forget Handling", "status": "warning", "details": "2 tasks without error handlers"}
  ],
  "findings": [
    {
      "severity": "CRITICAL",
      "location": "app/",
      "issue": "Mixed UoW ownership: commit() found in repositories (3), services (2), api (4)",
      "principle": "Layer Boundary / Transaction Control",
      "recommendation": "Choose single UoW owner (service layer recommended), remove commit() from other layers",
      "effort": "L"
    },
    {
      "severity": "HIGH",
      "location": "app/services/job/service.py:45",
      "issue": "Blocking file I/O in async: Path.read_bytes() inside async def process_job()",
      "principle": "Layer Boundary / Async Consistency",
      "recommendation": "Use asyncio.to_thread(path.read_bytes) or aiofiles",
      "effort": "S"
    },
    {
      "severity": "HIGH",
      "location": "app/domain/pdf/parser.py:45",
      "issue": "Layer violation: HTTP client used in domain layer",
      "principle": "Layer Boundary / I/O Isolation",
      "recommendation": "Move httpx.AsyncClient to infrastructure/http/clients/",
      "effort": "M"
    },
    {
      "severity": "MEDIUM",
      "location": "app/api/v1/jobs.py:78",
      "issue": "Fire-and-forget task without error handler: create_task(notify_user())",
      "principle": "Layer Boundary / Task Error Handling",
      "recommendation": "Add task.add_done_callback(handle_exception) or document with # fire-and-forget comment",
      "effort": "S"
    }
  ],
  "coverage": {
    "http_abstraction": 75,
    "error_centralization": false,
    "transaction_boundary_consistent": false,
    "session_ownership_consistent": true,
    "async_io_consistent": false,
    "fire_and_forget_handled": false
  }
}
json
{
  "category": "Layer Boundary",
  "score": 4.5,
  "total_issues": 8,
  "critical": 1,
  "high": 3,
  "medium": 4,
  "low": 0,
  "architecture": {
    "type": "Layered",
    "layers": ["api", "services", "domain", "infrastructure"]
  },
  "checks": [
    {"id": "io_isolation", "name": "I/O Isolation", "status": "failed", "details": "HTTP client found in domain layer"},
    {"id": "http_abstraction", "name": "HTTP Abstraction", "status": "warning", "details": "75% coverage, 3 direct calls outside infrastructure"},
    {"id": "error_centralization", "name": "Error Centralization", "status": "failed", "details": "HTTP error handlers in 4 files, should be centralized"},
    {"id": "transaction_boundary", "name": "Transaction Boundary", "status": "failed", "details": "commit() in repos (3), services (2), api (4) - mixed UoW ownership"},
    {"id": "session_ownership", "name": "Session Ownership", "status": "passed", "details": "DI-based sessions used consistently"},
    {"id": "async_consistency", "name": "Async Consistency", "status": "failed", "details": "Blocking I/O in async functions detected"},
    {"id": "fire_and_forget", "name": "Fire-and-Forget Handling", "status": "warning", "details": "2 tasks without error handlers"}
  ],
  "findings": [
    {
      "severity": "CRITICAL",
      "location": "app/",
      "issue": "Mixed UoW ownership: commit() found in repositories (3), services (2), api (4)",
      "principle": "Layer Boundary / Transaction Control",
      "recommendation": "Choose single UoW owner (service layer recommended), remove commit() from other layers",
      "effort": "L"
    },
    {
      "severity": "HIGH",
      "location": "app/services/job/service.py:45",
      "issue": "Blocking file I/O in async: Path.read_bytes() inside async def process_job()",
      "principle": "Layer Boundary / Async Consistency",
      "recommendation": "Use asyncio.to_thread(path.read_bytes) or aiofiles",
      "effort": "S"
    },
    {
      "severity": "HIGH",
      "location": "app/domain/pdf/parser.py:45",
      "issue": "Layer violation: HTTP client used in domain layer",
      "principle": "Layer Boundary / I/O Isolation",
      "recommendation": "Move httpx.AsyncClient to infrastructure/http/clients/",
      "effort": "M"
    },
    {
      "severity": "MEDIUM",
      "location": "app/api/v1/jobs.py:78",
      "issue": "Fire-and-forget task without error handler: create_task(notify_user())",
      "principle": "Layer Boundary / Task Error Handling",
      "recommendation": "Add task.add_done_callback(handle_exception) or document with # fire-and-forget comment",
      "effort": "S"
    }
  ],
  "coverage": {
    "http_abstraction": 75,
    "error_centralization": false,
    "transaction_boundary_consistent": false,
    "session_ownership_consistent": true,
    "async_io_consistent": false,
    "fire_and_forget_handled": false
  }
}

Critical Rules

核心规则

  • Read architecture.md first - never assume architecture type
  • Skip violations list - respect legacy files marked for gradual fix
  • File + line + code - always provide exact location with context
  • Actionable suggestions - always tell WHERE to move the code
  • No false positives - verify path contains forbidden dir, not just substring
  • 优先读取architecture.md - 绝不假设架构类型
  • 尊重skip_violations列表 - 跳过标记为逐步修复的遗留文件
  • 提供精确位置 - 始终返回包含上下文代码的准确文件和行号
  • 给出可执行建议 - 明确告知代码应迁移至何处
  • 避免误报 - 验证文件路径是否包含禁止目录,而非仅匹配子串

Definition of Done

完成标准

  • Architecture discovered from docs/architecture.md (or fallback used)
  • All violation types from common_patterns.md checked
  • Cross-layer consistency checked:
    • Transaction boundaries analyzed (commit/rollback distribution)
    • Session ownership analyzed (DI vs local)
    • Async consistency analyzed (sync I/O in async functions)
    • Fire-and-forget tasks analyzed (error handling)
  • Coverage calculated for HTTP abstraction + 4 consistency metrics
  • Violations list with severity, location, suggestion
  • Summary counts returned to coordinator
  • 已从docs/architecture.md识别架构(或使用备选规则)
  • 已检查common_patterns.md中的所有违规类型
  • 已完成跨层一致性检查:
    • 分析了事务边界(提交/回滚的分布情况)
    • 分析了会话归属(DI vs 本地创建)
    • 分析了异步一致性(异步函数中的同步I/O)
    • 分析了即发即弃任务(错误处理情况)
  • 已计算HTTP抽象覆盖率及4项一致性指标
  • 已生成包含严重程度、位置和修复建议的违规问题列表
  • 已向协调器返回问题统计摘要

Reference Files

参考文件

  • Layer rules:
    ../ln-640-pattern-evolution-auditor/references/common_patterns.md
  • Scoring impact:
    ../ln-640-pattern-evolution-auditor/references/scoring_rules.md

Version: 2.0.0 Last Updated: 2026-02-04
  • 分层规则:
    ../ln-640-pattern-evolution-auditor/references/common_patterns.md
  • 分数影响规则:
    ../ln-640-pattern-evolution-auditor/references/scoring_rules.md

版本: 2.0.0 最后更新时间: 2026-02-04