BUG-HUNT: [type-safety] Unsafe type casting in tier configuration access bypasses static type checking #7300

Open
opened 2026-04-10 15:38:14 +00:00 by HAL9000 · 3 comments
Owner

Background and Context

The _build_header and _generate_inventory_markdown functions in hooks/adr_hooks.py perform unsafe type casting operations on tier configuration data without proper type guards. The functions use runtime isinstance checks as a substitute for proper static type annotations, which violates the project's strict static typing requirements.

According to the project specification and CONTRIBUTING.md:

  • "All code must be statically typed and pass nox -e typecheck (Pyright)"
  • "Never use # type: ignore or disable type checking"
  • "Full type annotations on every function signature, variable, and return type"

The current approach relies on runtime isinstance checks rather than proper TypedDict definitions and static type constraints, meaning the type system cannot catch structural mismatches at analysis time.

Current Behavior

File: hooks/adr_hooks.py
Functions: _build_header, _generate_inventory_markdown
Lines: 218–223, 468–475

Two unsafe type handling patterns exist:

1. _build_header (lines 218–223) — isinstance check without typed contract:

tier_info = tiers_config.get(tier, {})
if isinstance(tier_info, dict):
    tier_title = tier_info.get("title", "")  # Assumes dict after isinstance check

The tiers_config parameter is typed as dict (unparameterised), so tier_info resolves to Any. Pyright cannot verify that .get("title", "") is valid — the isinstance guard is invisible to the static type checker.

2. _generate_inventory_markdown (lines 468–475) — inconsistent type handling:

tier_info = tiers_config.get(tier_num, {})
tier_title = (
    tier_info.get("title", f"Tier {tier_num}")
    if isinstance(tier_info, dict)
    else str(tier_info)  # But what if tier_info is complex type?
)
tier_desc = (
    tier_info.get("description", "") if isinstance(tier_info, dict) else ""
)

The else str(tier_info) branch silently coerces any unexpected type to a string, masking structural errors in the configuration. There is no static guarantee about what tier_info can be.

Expected Behavior

Per the project specification's cross-milestone architectural invariant:

"Type safety: Full Pyright strict compliance. No # type: ignore."

The tier configuration structure should be expressed as a TypedDict, and function signatures should use it explicitly so Pyright can validate all access patterns at analysis time — not at runtime.

Acceptance Criteria

  1. A TypedDict (e.g., TierConfig) is defined to represent the structure of a single tier configuration entry.
  2. tiers_config parameters in _build_header and _generate_inventory_markdown are annotated as dict[str | int, TierConfig] (or equivalent).
  3. All isinstance(tier_info, dict) runtime guards are replaced with proper static type annotations.
  4. Configuration structure is validated at load time (in the caller that reads config.get("extra", {}).get("adr_tiers", {})) rather than at every access site.
  5. nox -s typecheck passes with zero Pyright errors on hooks/adr_hooks.py.
  6. No # type: ignore comments are introduced.

Supporting Information

  • Related bug (same file, error-handling): #7282
  • Related bug (same file, security): #7288
  • Specification requirement: "All code must be statically typed and pass nox -e typecheck (Pyright)" (§Architecture, Cross-Milestone Invariant #3)

Metadata

  • Branch: bugfix/type-safety-tier-config-adr-hooks
  • Commit Message: fix(hooks): define TypedDict for tier config and replace runtime isinstance guards with static types
  • Milestone: (none — backlog)
  • Parent Epic: (to be linked — see orphan note below)

Subtasks

  • Define TierConfig TypedDict in hooks/adr_hooks.py (or a shared types module)
  • Update _build_header signature: annotate tiers_config with dict[str | int, TierConfig]
  • Update _generate_inventory_markdown signature: annotate tiers_config with dict[str | int, TierConfig]
  • Add load-time validation of tier configuration structure at the call sites in on_page_context and on_env
  • Remove all isinstance(tier_info, dict) runtime guards, replacing with static type flow
  • Write BDD scenario covering correct TypedDict usage and invalid config rejection
  • Verify nox -s typecheck passes with zero errors on the affected file
  • Update inline docstrings to reference the new TierConfig type

Definition of Done

  • TierConfig TypedDict defined and used in all tier config access sites
  • No isinstance(tier_info, dict) guards remain in _build_header or _generate_inventory_markdown
  • nox -s typecheck passes with zero Pyright errors
  • BDD test scenario added covering the type-safe tier config access
  • All nox stages pass
  • Coverage >= 97%

Backlog note: This issue was discovered during autonomous operation
on milestone v3.7.0. It does not block milestone completion and has been
placed in the backlog for human review and future milestone assignment.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: new-issue-creator

## Background and Context The `_build_header` and `_generate_inventory_markdown` functions in `hooks/adr_hooks.py` perform unsafe type casting operations on tier configuration data without proper type guards. The functions use runtime `isinstance` checks as a substitute for proper static type annotations, which violates the project's strict static typing requirements. According to the project specification and `CONTRIBUTING.md`: - *"All code must be statically typed and pass `nox -e typecheck` (Pyright)"* - *"Never use `# type: ignore` or disable type checking"* - *"Full type annotations on every function signature, variable, and return type"* The current approach relies on runtime `isinstance` checks rather than proper TypedDict definitions and static type constraints, meaning the type system cannot catch structural mismatches at analysis time. ## Current Behavior **File**: `hooks/adr_hooks.py` **Functions**: `_build_header`, `_generate_inventory_markdown` **Lines**: 218–223, 468–475 Two unsafe type handling patterns exist: **1. `_build_header` (lines 218–223) — isinstance check without typed contract:** ```python tier_info = tiers_config.get(tier, {}) if isinstance(tier_info, dict): tier_title = tier_info.get("title", "") # Assumes dict after isinstance check ``` The `tiers_config` parameter is typed as `dict` (unparameterised), so `tier_info` resolves to `Any`. Pyright cannot verify that `.get("title", "")` is valid — the isinstance guard is invisible to the static type checker. **2. `_generate_inventory_markdown` (lines 468–475) — inconsistent type handling:** ```python tier_info = tiers_config.get(tier_num, {}) tier_title = ( tier_info.get("title", f"Tier {tier_num}") if isinstance(tier_info, dict) else str(tier_info) # But what if tier_info is complex type? ) tier_desc = ( tier_info.get("description", "") if isinstance(tier_info, dict) else "" ) ``` The `else str(tier_info)` branch silently coerces any unexpected type to a string, masking structural errors in the configuration. There is no static guarantee about what `tier_info` can be. ## Expected Behavior Per the project specification's cross-milestone architectural invariant: > *"Type safety: Full Pyright strict compliance. No `# type: ignore`."* The tier configuration structure should be expressed as a `TypedDict`, and function signatures should use it explicitly so Pyright can validate all access patterns at analysis time — not at runtime. ## Acceptance Criteria 1. A `TypedDict` (e.g., `TierConfig`) is defined to represent the structure of a single tier configuration entry. 2. `tiers_config` parameters in `_build_header` and `_generate_inventory_markdown` are annotated as `dict[str | int, TierConfig]` (or equivalent). 3. All `isinstance(tier_info, dict)` runtime guards are replaced with proper static type annotations. 4. Configuration structure is validated at load time (in the caller that reads `config.get("extra", {}).get("adr_tiers", {})`) rather than at every access site. 5. `nox -s typecheck` passes with zero Pyright errors on `hooks/adr_hooks.py`. 6. No `# type: ignore` comments are introduced. ## Supporting Information - Related bug (same file, error-handling): #7282 - Related bug (same file, security): #7288 - Specification requirement: *"All code must be statically typed and pass `nox -e typecheck` (Pyright)"* (§Architecture, Cross-Milestone Invariant #3) ## Metadata - **Branch**: `bugfix/type-safety-tier-config-adr-hooks` - **Commit Message**: `fix(hooks): define TypedDict for tier config and replace runtime isinstance guards with static types` - **Milestone**: *(none — backlog)* - **Parent Epic**: *(to be linked — see orphan note below)* ## Subtasks - [ ] Define `TierConfig` TypedDict in `hooks/adr_hooks.py` (or a shared types module) - [ ] Update `_build_header` signature: annotate `tiers_config` with `dict[str | int, TierConfig]` - [ ] Update `_generate_inventory_markdown` signature: annotate `tiers_config` with `dict[str | int, TierConfig]` - [ ] Add load-time validation of tier configuration structure at the call sites in `on_page_context` and `on_env` - [ ] Remove all `isinstance(tier_info, dict)` runtime guards, replacing with static type flow - [ ] Write BDD scenario covering correct TypedDict usage and invalid config rejection - [ ] Verify `nox -s typecheck` passes with zero errors on the affected file - [ ] Update inline docstrings to reference the new `TierConfig` type ## Definition of Done - [ ] `TierConfig` TypedDict defined and used in all tier config access sites - [ ] No `isinstance(tier_info, dict)` guards remain in `_build_header` or `_generate_inventory_markdown` - [ ] `nox -s typecheck` passes with zero Pyright errors - [ ] BDD test scenario added covering the type-safe tier config access - [ ] All nox stages pass - [ ] Coverage >= 97% > **Backlog note:** This issue was discovered during autonomous operation > on milestone v3.7.0. It does not block milestone completion and has been > placed in the backlog for human review and future milestone assignment. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: new-issue-creator
Author
Owner

Verified — Type safety bug: unsafe type casting in tier configuration access. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Type safety bug: unsafe type casting in tier configuration access. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Type safety bug: unsafe type casting in tier configuration access. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Type safety bug: unsafe type casting in tier configuration access. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Type safety bug: unsafe type casting in tier configuration access. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Type safety bug: unsafe type casting in tier configuration access. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#7300
No description provided.