{
  "track_id": "data_oriented_error_handling_20260606",
  "name": "Data-Oriented Error Handling (Fleury Pattern)",
  "initialized": "2026-06-06",
  "owner": "tier2-tech-lead",
  "priority": "high",
  "status": "active",
  "type": "refactor + convention + documentation",
  "scope": {
    "new_files": [
      "src/result_types.py",
      "conductor/code_styleguides/error_handling.md",
      "tests/test_result_types.py",
      "tests/test_mcp_client_paths.py",
      "tests/test_ai_client_result.py",
      "tests/test_rag_engine_result.py",
      "tests/test_deprecation_warnings.py"
    ],
    "modified_files": [
      "src/mcp_client.py",
      "src/ai_client.py",
      "src/rag_engine.py",
      "conductor/product-guidelines.md",
      "conductor/workflow.md",
      "docs/guide_ai_client.md",
      "docs/guide_mcp_client.md",
      "pyproject.toml",
      "tests/conftest.py"
    ]
  },
  "blocked_by": ["startup_speedup_20260606", "test_batching_refactor_20260606", "qwen_llama_grok_integration_20260606"],
  "blocks": ["public_api_migration_20260606"],
  "estimated_phases": 5,
  "spec": "spec.md",
  "plan": "plan.md",
  "priority_order": "A (foundation patterns + 3-file refactor) > B (deprecation + Result API) > C (convention docs) > D (plan follow-up)",
  "fleury_patterns_applied": [
    "Nil struct pointer (Python: frozen dataclass singleton + nil-sentinel methods)",
    "Zero-initialization (Python: @dataclass field defaults)",
    "Fail early (Python: same principle; assert + early return)",
    "AND over OR (Python: Result dataclass with data + side-channel errors list)",
    "Error info as side-channel (Python: list[ErrorInfo] in Result, accumulates per call)"
  ],
  "python_mappings": {
    "nil_struct_pointer": "@dataclass(frozen=True) class Nil: pass; NIL = Nil() (module-level singleton); frozen=True prevents runtime mutation",
    "zero_initialization": "@dataclass with field defaults; field(default_factory=list) for mutables",
    "fail_early": "assert + early return at entry points; try/finally as Python's analog to goto defer",
    "and_over_or": "Result[T] = Result(data: T, errors: list[ErrorInfo]) where data is the happy-path value and errors is a side-channel list (zero-initialized = success)",
    "error_side_channel": "list[ErrorInfo] in Result struct accumulates all errors per call (richer than C's single errno slot)"
  },
  "result_data_model": {
    "ErrorInfo": "@dataclass(frozen=True) class ErrorInfo: kind: ErrorKind; message: str; source: str; original: BaseException | None",
    "ErrorKind": "@enum.Enum: NETWORK, AUTH, QUOTA, RATE_LIMIT, BALANCE, PERMISSION, NOT_FOUND, INVALID_INPUT, NOT_READY, UNKNOWN, CONFIG, INTERNAL",
    "Result": "@dataclass(frozen=True) class Result(Generic[T]): data: T; errors: list[ErrorInfo] = field(default_factory=list); @property ok(self) -> bool; with_error(err); with_errors(errs_batch); with_data(new_data)",
    "NilPath": "@dataclass(frozen=True) singleton with exists=False, read_text='', errors=[]",
    "NilRAGState": "@dataclass(frozen=True) singleton with enabled=False, is_empty_result=True, errors=[]"
  },
  "refactor_targets": {
    "src/mcp_client.py": {
      "pattern_replaced": "(p, err) tuple returns + 'if err or p is None: return err' (~30 sites) + 'assert p is not None' chain (~30+ sites)",
      "new_pattern": "Result[Path] + Result[str] with nil-sentinel Path; read_file() returns Result[str]",
      "test_impact": "tests/test_mcp_client.py passes unchanged; new test_mcp_client_paths.py covers the new return types"
    },
    "src/ai_client.py": {
      "pattern_replaced": "ProviderError exception + _classify_*_error() raises + _send_<vendor>() returns str (8 vendors post-qwen_track)",
      "new_pattern": "ErrorInfo dataclass + _classify_*_error() returns ErrorInfo (value) + _send_<vendor>_result() returns Result[str]; ProviderError removed entirely",
      "breaking_changes": "All _send_<vendor>() renamed to _send_<vendor>_result() with new return type; send() marked @deprecated; send_result() added",
      "test_impact": "Most tests call send() and pass unchanged (with deprecation warning); _send_* direct callers (rare) need update"
    },
    "src/rag_engine.py": {
      "pattern_replaced": "RAGEngine methods raise ImportError/ValueError or set self.collection=None on failure",
      "new_pattern": "RAGEngine methods return Result[None] or Result[T] with side-channel ErrorInfo; NilRAGState sentinel for unconfigured state",
      "test_impact": "tests/test_rag_engine.py passes unchanged; new test_rag_engine_result.py covers the new return types"
    }
  },
  "deprecation_strategy": {
    "marked_deprecated": "ai_client.send() (public API returning str)",
    "new_api": "ai_client.send_result() (returns Result[str, ErrorInfo])",
    "mechanism": "typing_extensions.deprecated decorator (Python 3.11+ backport of @warnings.deprecated); emits DeprecationWarning at first call per site (cached)",
    "removal_timeline": "Removed in follow-up track public_api_migration_20260606 (planned in this spec's §12.1)"
  },
  "inter_track_coordination": {
    "post_startup_speedup_state": "src/ai_client.py has lazy SDK imports via _require_warmed; src/app_controller.py has _io_pool; scripts/audit_main_thread_imports.py is a CI gate",
    "post_test_batching_state": "tests/test_categories.toml populated; conftest.py registers pytest_collection_order plugin; new tests auto-classified by the categorizer",
    "post_qwen_track_state": "src/vendor_capabilities.py + src/openai_compatible.py + src/qwen_adapter.py exist; 8 _send_<vendor>() functions all return str (Qwen, Llama, Grok, MiniMax, Gemini, Anthropic, DeepSeek, Gemini CLI); MiniMax uses the shared helper; send_openai_compatible raises ProviderError at the SDK boundary",
    "phase_1_baseline_check": "Verify all 3 pending tracks merged before starting the data-oriented refactor (git log + file existence check)"
  },
  "documentation_strategy": {
    "new_file": "conductor/code_styleguides/error_handling.md (~400 lines; the canonical reference)",
    "modified_files": [
      "conductor/product-guidelines.md (new 'Data-Oriented Error Handling' section)",
      "conductor/workflow.md (note in Code Style section linking to the new styleguide)",
      "docs/guide_ai_client.md (new section on Result API + deprecation note)",
      "docs/guide_mcp_client.md (new section on Result return types)"
    ],
    "rationale": "Establish the convention in the canonical styleguide so future plans can incrementally migrate the remaining src/ files"
  },
  "architectural_invariant": "All new code uses Result dataclasses (not Optional/exceptions) for recoverable errors. The Result generic is over the success data T (not over the error type E); errors are always list[ErrorInfo]. Exceptions are reserved for the SDK boundary (where they're caught and converted to ErrorInfo). Nil-sentinel dataclasses are used instead of None for missing data.",
  "threading_constraint": "Same as existing pattern: Result dataclasses are frozen and thread-safe (immutable). The error list is built via `with_error()` which produces a new Result (no mutation). The deprecation warning uses Python's `warnings.warn` which is thread-safe.",
  "verification_criteria": [
    "src/result_types.py:Result and ErrorInfo exist with the documented fields; NilPath and NilRAGState are module-level singletons",
    "src/result_types.py:Result is generic over T (Python 3.11+ Generic syntax)",
    "src/result_types.py:Result.with_error(), with_errors(), and with_data() produce modified copies (frozen semantics)",
    "src/result_types.py:ErrorKind enum includes NOT_READY (for _require_warmed failures) in addition to the 11 base values",
    "src/mcp_client.py:_resolve_and_check returns Result[Path] (not tuple); no 'assert p is not None' chain",
    "src/mcp_client.py:read_file, list_directory, search_files, get_file_summary, etc. return Result[str]",
    "src/ai_client.py:ProviderError class is removed (no longer raised; ErrorInfo replaces it)",
    "src/ai_client.py:6 classifier functions return ErrorInfo (not raise): 5 in src/ai_client.py + 1 shared in src/openai_compatible.py + classify_dashscope_error in src/qwen_adapter.py",
    "src/ai_client.py:8 _send_<vendor>() functions are renamed to _send_<vendor>_result() and return Result[str] (per-vendor atomic commits per plan Tasks 3.4.1-3.4.8)",
    "src/ai_client.py:send() is decorated with @typing_extensions.deprecated (no double-warn; pick one of decorator or manual warnings.warn)",
    "src/ai_client.py:send_result() is the new public API returning Result[str]; mirrors send()'s full signature (13+ params including 8 callbacks, read with manual-slop_py_get_definition before implementing)",
    "src/ai_client.py:_send_<vendor>_result() catches _require_warmed failures and returns Result with ErrorKind.NOT_READY",
    "src/rag_engine.py:RAGEngine methods return Result (not raise ImportError/ValueError)",
    "src/rag_engine.py:NilRAGState is used for unconfigured state; _get_state() returns a NilRAGState instance (not the class); tests assert values not identity",
    "tests/test_result_types.py:11+ tests pass (Result construction, with_error, with_data, with_errors batch, NilPath singleton, ErrorKind enum including NOT_READY, frozen semantics)",
    "tests/test_mcp_client_paths.py:6+ tests pass (new Result return types)",
    "tests/test_ai_client_result.py:8+ tests pass (new Result API, deprecation warning)",
    "tests/test_rag_engine_result.py:4+ tests pass (new Result return types; test_is_empty asserts value, not identity)",
    "tests/test_deprecation_warnings.py:send() emits DeprecationWarning; send_result() does not",
    "tests/mcp_dispatch_no_log_when_no_infra: when mcp_client has no comms log, async_dispatch just returns result.data (no error path)",
    "tests/test_mcp_client.py (existing): no regressions",
    "tests/test_ai_client.py (existing): no regressions",
    "tests/test_minimax_provider.py, test_qwen_provider.py, test_llama_provider.py, test_grok_provider.py (existing): no regressions",
    "tests/test_rag_engine.py (existing): no regressions",
    "conductor/code_styleguides/error_handling.md: documented with the 5 patterns, Python mappings, decision tree, 'Hard Rules' section (Optional[T] forbidden in 3 files), examples",
    "conductor/product-guidelines.md: new 'Data-Oriented Error Handling' section added",
    "conductor/workflow.md: new note in Code Style section",
    "docs/guide_ai_client.md: updated with Result API + deprecation note",
    "docs/guide_mcp_client.md: updated with Result return types",
    "conductor/tracks.md: data_oriented_error_handling_20260606 entry added; public_api_migration_20260606 placeholder added (separate track, not this one)",
    "pyproject.toml: typing_extensions>=4.5.0 dependency added",
    "import src.result_types < 50ms (no heavy imports at top level; verified by scripts/audit_main_thread_imports.py)",
    "scripts/audit_optional_in_3_files.py: exists; --strict mode fails CI on new Optional[X] in the 3 refactored files",
    "No new threading.Thread calls in src/ (per project invariant)",
    "No new Optional[X] in the 3 refactored files (verified by ripgrep at every phase checkpoint)"
  ],
  "links": {
    "backlog_entry": "conductor/tracks.md (to be added)",
    "code_styleguide": "conductor/code_styleguides/error_handling.md (to be created in Phase 1)",
    "testing_guide": "docs/guide_testing.md",
    "ai_client_guide": "docs/guide_ai_client.md",
    "mcp_client_guide": "docs/guide_mcp_client.md",
    "workflow_pitfalls": "conductor/workflow.md#known-pitfalls-2026-06-05",
    "related_tracks": [
      "conductor/tracks/startup_speedup_20260606/",
      "conductor/tracks/test_batching_refactor_20260606/",
      "conductor/tracks/qwen_llama_grok_integration_20260606/",
      "conductor/tracks/regression_fixes_20260605/",
      "conductor/tracks/live_gui_test_hardening_v2_20260605/"
    ],
    "external_docs": [
      "https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors (Fleury article)"
    ]
  }
}
