Skip to content

[Feature] Type-assertion debt audit (as unknown as T / as any) #256

Description

@pathosDev

Size / Priority

  • Size: M — 50+ call sites; not a single PR.
  • Category: C.2 Simplifications & DRY (type-safety cleanup).
  • Risk: medium — as casts often hide latent type bugs; uncovering them may require type fixes.

Affected files

  • ~50+ sites across the codebase where as unknown as T or as any is used to coerce a value into a typed shape.

Background

as unknown as T is the TypeScript escape hatch for casts the compiler can't verify. It's currently used liberally:

  • Wire-message → typed shape(msg as WireMessage) after JSON.parse.
  • ActorRef → specific subclass(ref as RemoteActorRef).
  • Untyped config value → typed settings(config.get('x') as MySettings).
  • Generic-type juggling(crdt as Crdt<any>).

The framework has ~50 of these. Each one is an unchecked cast — a latent bug if the runtime shape doesn't match the type.

Audit pattern from Plan-Doc/A.5.1: malicious snapshot store returns events in wrong order → recovery corrupts state silently. Cast-debt makes such issues invisible at compile time.

Target

Per-cast cleanup with category-specific solutions:

Category 1: Wire-message decoding (JSON.parse result)

// Before:
const msg = JSON.parse(raw) as WireMessage;

// After: typed validator at the boundary
const msg = decodeWireMessage(raw);  // returns WireMessage, throws on shape mismatch

Use a typed JSON-validator (Zod, valibot, or hand-rolled discriminator-checks). Validate once at the wire boundary; downstream code sees typed values.

Category 2: ActorRef subtype-cast

// Before:
const local = ref as LocalActorRef;

// After: type-guard
if (!isLocalActorRef(ref)) throw new Error('expected LocalActorRef');
// local is now correctly narrowed.

Category 3: Untyped config → typed settings

Covered by #255 (resolveSettings<S>).

Category 4: Generic-juggling

Often these are honestly necessary (variance issues, etc.). Document each with a comment explaining why.

Implementation

This is a multi-PR cleanup, not a single change. Per-PR:

  1. Categorise a batch of casts.
  2. Apply the appropriate fix per category.
  3. Test each.

Recommend ~10 casts per PR, ~5-6 PRs total.

Integration / risk

  • Behavioural changes possible: if a cast was hiding a runtime bug, the fix may surface a new error path. Treat each surfaced bug as a separate issue.
  • TS strictness gain: removing casts forces type accuracy at the boundary; the rest of the code becomes more type-safe.

Test plan

  1. Per-PR regression — full test suite per change.
  2. New error paths exposed — investigate; either fix the underlying issue or document.
  3. TS strict mode — codebase should compile cleanly without the casts.

Acceptance criteria

  • Catalog of all as unknown as T + as any sites.
  • Per-category solutions documented.
  • ~50 casts removed across multiple PRs.
  • Each PR has tests + no regression.
  • Inline comments on remaining unavoidable casts explaining why.
  • No CHANGELOG entry needed (internal type-safety).

Pre-implementation note

This is a sustained effort. Consider tracking the cast-count over time as a code-health metric.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority: lowNice-to-have / niche / demand-driven

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions