Files
gongxue-base/.agents/skills/systematic-debugging/defense-in-depth.md
wangziqi b6d398b102 feat: 导入导出错误信息 Markdown 渲染升级
Phase 1: 共用类型定义
- apps/server/src/common/import-result.types.ts — ImportRowError + ImportResult<T>
- apps/admin/src/types/import.ts — 前端对应类型 + ImportDisplayConfig

Phase 2: 后端增强
- students.service.ts — batchImport/matchImport 改为 ImportResult 格式
  · 每行跳过时收集 ImportRowError(code + reason)
  · reason 支持 markdown 标记(**字段名** / `值`)
- archive.service.ts — batchImportArchive 统一用 ImportResult 格式
  · 各 Sheet 错误使用标准 error code
- archive.controller.ts — 日志使用新的 success 字段

Phase 3: 前端基础
- 安装 react-markdown
- ImportResultModal 组件(共用导入结果弹窗)
  · Statistic 成功统计 + Table 错误明细
  · 原因列支持 react-markdown 渲染(粗体字段名、等宽代码值)
  · 错误类型用 Tag 颜色区分
- api/index.ts — 403 错误从 console.warn 改为 message.warning 用户可见

Phase 4: 前端集成
- Students/index.tsx — 替换硬编码导入结果弹窗
  · 删除'后端只返回统计汇总'注释和跳过原因 hardcode
  · 新格式自动使用 ImportResultModal,旧格式兼容
- StudentProfileContent/index.tsx — 替换内联导入 Modal 为 ImportResultModal
2026-07-20 15:25:14 +08:00

3.6 KiB

Defense-in-Depth Validation

Overview

When you fix a bug caused by invalid data, adding validation at one place feels sufficient. But that single check can be bypassed by different code paths, refactoring, or mocks.

Core principle: Validate at EVERY layer data passes through. Make the bug structurally impossible.

Why Multiple Layers

Single validation: "We fixed the bug" Multiple layers: "We made the bug impossible"

Different layers catch different cases:

  • Entry validation catches most bugs
  • Business logic catches edge cases
  • Environment guards prevent context-specific dangers
  • Debug logging helps when other layers fail

The Four Layers

Layer 1: Entry Point Validation

Purpose: Reject obviously invalid input at API boundary

function createProject(name: string, workingDirectory: string) {
  if (!workingDirectory || workingDirectory.trim() === '') {
    throw new Error('workingDirectory cannot be empty');
  }
  if (!existsSync(workingDirectory)) {
    throw new Error(`workingDirectory does not exist: ${workingDirectory}`);
  }
  if (!statSync(workingDirectory).isDirectory()) {
    throw new Error(`workingDirectory is not a directory: ${workingDirectory}`);
  }
  // ... proceed
}

Layer 2: Business Logic Validation

Purpose: Ensure data makes sense for this operation

function initializeWorkspace(projectDir: string, sessionId: string) {
  if (!projectDir) {
    throw new Error('projectDir required for workspace initialization');
  }
  // ... proceed
}

Layer 3: Environment Guards

Purpose: Prevent dangerous operations in specific contexts

async function gitInit(directory: string) {
  // In tests, refuse git init outside temp directories
  if (process.env.NODE_ENV === 'test') {
    const normalized = normalize(resolve(directory));
    const tmpDir = normalize(resolve(tmpdir()));

    if (!normalized.startsWith(tmpDir)) {
      throw new Error(
        `Refusing git init outside temp dir during tests: ${directory}`
      );
    }
  }
  // ... proceed
}

Layer 4: Debug Instrumentation

Purpose: Capture context for forensics

async function gitInit(directory: string) {
  const stack = new Error().stack;
  logger.debug('About to git init', {
    directory,
    cwd: process.cwd(),
    stack,
  });
  // ... proceed
}

Applying the Pattern

When you find a bug:

  1. Trace the data flow - Where does bad value originate? Where used?
  2. Map all checkpoints - List every point data passes through
  3. Add validation at each layer - Entry, business, environment, debug
  4. Test each layer - Try to bypass layer 1, verify layer 2 catches it

Example from Session

Bug: Empty projectDir caused git init in source code

Data flow:

  1. Test setup → empty string
  2. Project.create(name, '')
  3. WorkspaceManager.createWorkspace('')
  4. git init runs in process.cwd()

Four layers added:

  • Layer 1: Project.create() validates not empty/exists/writable
  • Layer 2: WorkspaceManager validates projectDir not empty
  • Layer 3: WorktreeManager refuses git init outside tmpdir in tests
  • Layer 4: Stack trace logging before git init

Result: All 1847 tests passed, bug impossible to reproduce

Key Insight

All four layers were necessary. During testing, each layer caught bugs the others missed:

  • Different code paths bypassed entry validation
  • Mocks bypassed business logic checks
  • Edge cases on different platforms needed environment guards
  • Debug logging identified structural misuse

Don't stop at one validation point. Add checks at every layer.