ln-654-resource-lifecycle-auditor
Paths: File paths (
shared/,references/,../ln-*) are relative to skills repo root. If not found at CWD, locate this SKILL.md directory and go up one level for repo root.
Resource Lifecycle Auditor (L3 Worker)
Specialized worker auditing resource acquisition/release patterns, scope mismatches, and connection pool hygiene.
Purpose & Scope
- Worker in ln-650 coordinator pipeline - invoked by ln-650-persistence-performance-auditor
- Audit resource lifecycle (Priority: HIGH)
- Check session/connection scope mismatch, streaming endpoint resource holding, cleanup patterns, pool config
- Write structured findings to file with severity, location, effort, recommendations
- Calculate compliance score (X/10) for Resource Lifecycle category
Inputs (from Coordinator)
MANDATORY READ: Load shared/references/audit_worker_core_contract.md.
Receives contextStore with: tech_stack, best_practices, db_config (database type, ORM settings, pool config, session factory), codebase_root, output_dir.
Domain-aware: Supports domain_mode + current_domain.
Workflow
MANDATORY READ: Load shared/references/two_layer_detection.md for detection methodology.
-
Parse context from contextStore
- Extract tech_stack, best_practices, db_config, output_dir
- Determine scan_path
-
Detect DI framework
- FastAPI
Depends(), Django middleware, Spring@Autowired/@PersistenceContext, Express middleware, Go wire/fx
- FastAPI
-
Discover resource infrastructure
- Find session/connection factory patterns (
sessionmaker,create_engine,DataSource, pool creation) - Find DI registration (
Depends(),@Inject, providers, middleware mounting) - Find streaming endpoints (SSE, WebSocket, long-poll, streaming response)
- Map: which endpoints receive which resources via DI
- Find session/connection factory patterns (
-
Scan codebase for violations (6 checks)
- Trace resource injection -> usage -> release across endpoint lifetime
- Analyze streaming endpoints for held resources
- Check error paths for cleanup
-
Collect findings with severity, location, effort, recommendation
-
Calculate score using penalty algorithm
-
Write Report: Build full markdown report in memory per
shared/templates/audit_worker_report_template.md, write to{output_dir}/654-resource-lifecycle.mdin single Write call -
Return Summary: Return minimal summary to coordinator (see Output Format)
Audit Rules (Priority: HIGH)
1. Resource Scope Mismatch
What: Resource injected via DI lives for entire request/connection scope but is used for only a fraction of it.
Detection (Python/FastAPI):
- Step 1 - Find endpoints with DB session dependency:
- Grep:
async def\s+\w+\(.*Depends\(get_db\)|Depends\(get_session\)|db:\s*AsyncSession|session:\s*AsyncSession
- Grep:
- Step 2 - Measure session usage span within endpoint body:
- Count lines between first and last
session\.|db\.|await.*repousage - Count total lines in endpoint function body
- Count lines between first and last
- Step 3 - Flag if
usage_lines / total_lines < 0.2(session used in <20% of function body)- Especially: session used only at function start (auth check, initial load) but function continues with non-DB work
Detection (Node.js/Express):
- Middleware injects
req.dborreq.knexat request start - Grep:
app\.use.*pool|app\.use.*knex|app\.use.*prisma(middleware injection) - Route handler uses
req.dbonly in first 20% of function body
Detection (Java/Spring):
@Transactionalon method with long non-DB processingEntityManagerinjected but used only briefly- Grep:
@Autowired.*EntityManager|@PersistenceContext+ method body analysis
Detection (Go):
sql.DBor*gorm.DBpassed to handler, used once, then long processing- Grep:
func.*Handler.*\*sql\.DB|func.*Handler.*\*gorm\.DB
Severity:
- CRITICAL: Session scope mismatch in streaming endpoint (SSE, WebSocket) - session held for minutes/hours
- HIGH: Session scope mismatch in endpoint with external API calls (session held during network latency)
- MEDIUM: Session scope mismatch in endpoint with >50 lines of non-DB processing
Recommendation: Extract DB operations into scoped function; acquire session only for the duration needed; use async with get_session() as session: block instead of endpoint-level DI injection.
Effort: M (refactor DI to scoped acquisition)
2. Streaming Endpoint Resource Holding
What: SSE, WebSocket, or long-poll endpoint holds DB session/connection for stream duration.
Detection (Python/FastAPI):
- Step 1 - Find streaming endpoints:
- Grep:
StreamingResponse|EventSourceResponse|SSE|async def.*websocket|@app\.websocket - Grep:
yield\s+.*event|yield\s+.*data:|async for.*yield(SSE generator pattern)
- Grep:
- Step 2 - Check if streaming function/generator has DB session in scope:
- Session from
Depends()in endpoint signature -> held for entire stream - Session from context manager inside generator -> scoped (OK)
- Session from
- Step 3 - Analyze session usage inside generator:
- If session used once at start (auth/permission check) then stream loops without DB -> scope mismatch
Detection (Node.js):
- Grep:
res\.write\(|res\.flush\(|Server-Sent Events|new WebSocket|ws\.on\( - Check if connection/pool client acquired before stream loop and not released
Detection (Java/Spring):
- Grep:
SseEmitter|WebSocketHandler|StreamingResponseBody - Check if
@Transactionalwraps streaming method
Detection (Go):
- Grep:
Flusher|http\.Flusher|websocket\.Conn - Check if
*sql.DBor transaction held during flush loop
Severity:
- CRITICAL: DB session/connection held for entire SSE/WebSocket stream duration (pool exhaustion under load)
- HIGH: DB connection held during long-poll (>30s timeout)
Recommendation: Move auth/permission check BEFORE stream: acquire session, check auth, release session, THEN start streaming. Use separate scoped session for any mid-stream DB access.
Effort: M (restructure endpoint to release session before streaming)
3. Missing Resource Cleanup Patterns
What: Resource acquired without guaranteed cleanup (no try/finally, no context manager, no close()).
Detection (Python):
- Grep:
session\s*=\s*Session\(\)|session\s*=\s*sessionmaker|engine\.connect\(\)NOT insidewithorasync with - Grep:
connection\s*=\s*pool\.acquire\(\)|conn\s*=\s*await.*connect\(\)NOT followed bytry:.*finally:.*close\(\) - Pattern: bare
session = get_session()without context manager - Safe patterns to exclude:
async with session_factory() as session:,with engine.connect() as conn:
Detection (Node.js):
- Grep:
pool\.connect\(\)|knex\.client\.acquireConnection|\.getConnection\(\)without corresponding.release()or.end()in same function - Grep:
createConnection\(\)without.destroy()in try/finally
Detection (Java):
- Grep:
getConnection\(\)|dataSource\.getConnection\(\)without try-with-resources - Pattern:
Connection conn = ds.getConnection()withouttry (Connection conn = ...)syntax
Detection (Go):
- Grep:
sql\.Open\(|db\.Begin\(\)withoutdefer.*Close\(\)|defer.*Rollback\(\) - Pattern:
tx, err := db.Begin()withoutdefer tx.Rollback()
Severity:
- HIGH: Session/connection acquired without cleanup guarantee (leak on exception)
- MEDIUM: File handle or cursor without cleanup in non-critical path
Exception: Session acquired and released before streaming/long-poll begins → skip. NullPool / pool_size config documented as serverless design → skip.
Recommendation: Ensure resources are cleaned up on all exit paths (context managers, try-finally, or framework-managed lifecycle).
Effort: S (wrap in context manager or add defer)
4. Connection Pool Configuration Gaps
What: Missing pool health monitoring, no pre-ping, no recycle, no overflow limits.
Detection (Python/SQLAlchemy):
- Grep for
create_engine\(|create_async_engine\(:- Missing
pool_pre_ping=True-> stale connections not detected - Missing
pool_recycle-> connections kept beyond DB server timeout (default: MySQL 8h, PG unlimited) - Missing
pool_size-> uses default 5 (may be too small for production) - Missing
max_overflow-> unbounded overflow under load pool_size=0orNullPoolin web service -> no pooling (anti-pattern)
- Missing
- Grep for pool event listeners:
- Missing
@event.listens_for(engine, "invalidate")-> no visibility into connection invalidation - Missing
@event.listens_for(engine, "checkout")-> no connection checkout monitoring - Missing
@event.listens_for(engine, "checkin")-> no connection return monitoring
- Missing
Detection (Node.js):
- Grep for
createPool\(|new Pool\(:- Missing
min/maxconfiguration - Missing
idleTimeoutMillisorreapIntervalMillis - Missing connection validation (
validateConnection,testOnBorrow)
- Missing
Detection (Java/Spring):
- Grep:
DataSource|HikariConfig|HikariDataSource:- Missing
leakDetectionThreshold - Missing
maximumPoolSize(defaults to 10) - Missing
connectionTestQueryorconnectionInitSql
- Missing
Detection (Go):
- Grep:
sql\.Open\(:- Missing
db.SetMaxOpenConns() - Missing
db.SetMaxIdleConns() - Missing
db.SetConnMaxLifetime()
- Missing
Severity:
- HIGH: No pool_pre_ping AND no pool_recycle (stale connections served silently)
- HIGH: No max_overflow limit in web service (unbounded connection creation under load)
- MEDIUM: Missing pool event listeners (no visibility into pool health)
- MEDIUM: Missing leak detection threshold (Java/HikariCP)
- LOW: Pool size at default value (may be adequate for small services)
Context-dependent exceptions:
- NullPool is valid for serverless/Lambda
- pool_size=5 may be fine for low-traffic services
Recommendation: Configure pool_pre_ping=True, pool_recycle < DB server timeout, appropriate pool_size for expected concurrency, add pool event listeners for monitoring.
Effort: S (add config parameters), M (add event listeners/monitoring)
5. Unclosed Resources in Error Paths
What: Exception/error handling paths that skip resource cleanup.
Detection (Python):
- Find
exceptblocks containingraiseorreturnwithout priorsession.close(),conn.close(), orcursor.close() - Pattern:
except Exception: logger.error(...); raise(re-raise without cleanup) - Find generator functions with DB session where GeneratorExit is not handled:
- Grep:
async def.*yield.*session|def.*yield.*sessionwithouttry:.*finally:.*close\(\)
- Grep:
Detection (Node.js):
- Grep:
catch\s*\(blocks thatthroworreturnwithout releasing connection - Pattern:
pool.connect().then(client => { ... })without.finally(() => client.release()) - Promise chains without
.finally()for cleanup
Detection (Java):
- Grep:
catch\s*\(blocks withoutfinally { conn.close() }when connection opened intry - Not using try-with-resources for AutoCloseable resources
Detection (Go):
- Grep:
if err != nil \{.*returnbeforedeferstatement for resource cleanup - Pattern: error check between
Open()anddefer Close()that returns without closing
Severity:
- CRITICAL: Session/connection leak in high-frequency endpoint error path (pool exhaustion)
- HIGH: Resource leak in error path of API handler
- MEDIUM: Resource leak in error path of background task
Recommendation: Use context managers/try-with-resources/defer BEFORE any code that can fail; for generators, add try/finally around yield.
Effort: S (restructure acquisition to before-error-path)
6. Resource Factory vs Injection Anti-pattern
What: Using framework DI to inject short-lived resources into long-lived contexts instead of using factory pattern.
Detection (Python/FastAPI):
- Step 1 - Find DI-injected sessions in endpoint signatures:
- Grep:
Depends\(get_db\)|Depends\(get_session\)|Depends\(get_async_session\)
- Grep:
- Step 2 - Classify endpoint lifetime:
- Short-lived: regular REST endpoint (request/response) -> DI injection OK
- Long-lived: SSE (
StreamingResponse,EventSourceResponse), WebSocket (@app.websocket), background task (BackgroundTasks.add_task)
- Step 3 - Flag DI injection in long-lived endpoints:
- Long-lived endpoint should use factory pattern:
async with session_factory() as session:at point of need - NOT
session: AsyncSession = Depends(get_session)at endpoint level
- Long-lived endpoint should use factory pattern:
Detection (Node.js/Express):
- Middleware-injected pool connection (
req.db) used in WebSocket handler or SSE route - Should use:
const conn = await pool.connect(); try { ... } finally { conn.release() }at point of need
Detection (Java/Spring):
@Autowired EntityManagerin@Controllerwith SSE endpoint (SseEmitter)- Should use: programmatic EntityManager creation from EntityManagerFactory
Detection (Go):
*sql.DBinjected at handler construction time but*sql.Connshould be acquired per-operation
Severity:
- CRITICAL: DI-injected session in SSE/WebSocket endpoint (session outlives intended scope by orders of magnitude)
- HIGH: DI-injected session passed to background task (task outlives request)
Recommendation: Use factory pattern for long-lived contexts; inject the factory (sessionmaker, pool), not the session/connection itself.
Effort: M (change DI from session to session factory, add scoped acquisition)
Scoring Algorithm
MANDATORY READ: Load shared/references/audit_worker_core_contract.md and shared/references/audit_scoring.md.
Output Format
MANDATORY READ: Load shared/references/audit_worker_core_contract.md and shared/templates/audit_worker_report_template.md.
Write report to {output_dir}/654-resource-lifecycle.md with category: "Resource Lifecycle" and checks: resource_scope_mismatch, streaming_resource_holding, missing_cleanup, pool_configuration, error_path_leak, factory_vs_injection.
Return summary to coordinator:
Report written: docs/project/.audit/ln-650/{YYYY-MM-DD}/654-resource-lifecycle.md
Score: X.X/10 | Issues: N (C:N H:N M:N L:N)
Critical Rules
MANDATORY READ: Load shared/references/audit_worker_core_contract.md.
- Do not auto-fix: Report only
- DI-aware: Understand framework dependency injection lifetime scopes (request, singleton, transient)
- Framework detection first: Identify DI framework before checking injection patterns
- Streaming detection first: Find all streaming/long-lived endpoints before scope analysis
- Exclude tests: Do not flag test fixtures, test session setup, mock sessions
- Exclude CLI/scripts: DI scope mismatch is not relevant for single-run scripts
- Effort realism: S = <1h, M = 1-4h, L = >4h
- Pool config is context-dependent: NullPool is valid for serverless/Lambda; pool_size=5 may be fine for low-traffic services
- Safe pattern awareness: Do not flag resources inside
async with,with, try-with-resources,defer(already managed)
Definition of Done
MANDATORY READ: Load shared/references/audit_worker_core_contract.md.
- contextStore parsed successfully (including output_dir, db_config)
- scan_path determined
- DI framework detected (FastAPI Depends, Django middleware, Spring @Autowired, Express middleware, Go wire)
- Streaming endpoints inventoried
- All 6 checks completed:
- resource scope mismatch, streaming resource holding, missing cleanup, pool configuration, error path leak, factory vs injection
- Findings collected with severity, location, effort, recommendation
- Score calculated using penalty algorithm
- Report written to
{output_dir}/654-resource-lifecycle.md(atomic single Write call) - Summary returned to coordinator
Reference Files
- Audit output schema:
shared/references/audit_output_schema.md
Version: 1.0.0 Last Updated: 2026-03-03