ln-642-layer-boundary-auditor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLayer 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 (progress notifiers)
_callbacks.py - Explicit comment
# UoW boundary
Violation Rules:
| Condition | Severity | Issue |
|---|---|---|
| layers_with_commits >= 3 | CRITICAL | Mixed UoW ownership across all layers |
| repo + api commits | HIGH | Transaction control bypasses service layer |
| repo + service commits | HIGH | Ambiguous UoW owner (repo vs service) |
| service + api commits | MEDIUM | Transaction 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 >= 3 | CRITICAL | 所有分层中存在混合的工作单元(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:
| Condition | Severity | Issue |
|---|---|---|
| di_session AND local_in_repo in same module | HIGH | Repo creates own session while API injects different |
| local_session in service calling DI-based repo | MEDIUM | Session 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操作
检测逻辑:
undefinedFor 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-M2.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:检查模式覆盖率
undefinedundefinedHTTP 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"
})
undefinedhttp_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: "建议将错误处理逻辑集中到基础设施层"
undefinedPhase 3.5: Calculate Score
阶段3.5:计算审计分数
See for unified formula and score interpretation.
shared/references/audit_scoring.md统一公式和分数解释请参考。
shared/references/audit_scoring.mdPhase 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