agentsclimarketplace

Type safety

Skill jacob-balslev/skill-graph/marketplace/skills/type-safety

Skills that know your codebase. Repo-grounded, contract-validated, agent-routable.

Install
npx -y skills add jacob-balslev/skill-graph --skill type-safety

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when reasoning about types as a quality property of code: what guarantees the type system actually provides, the difference between sound and unsound systems, structural vs nominal typing, type narrowing and exhaustiveness, the runtime/compile-time boundary, and where validation must happen because the type system cannot. Covers TypeScript, Flow, Hindley-Milner languages, and gradual typing in general. Do NOT use for runtime input validation library choice (use api-design for API surface validation; use individual library docs for library mechanics), for SQL type mapping (use entity-relationship-modeling), or for type system implementation (compilers — out of scope). Do NOT use for implement HMAC verification for an inbound webhook (use webhook-integration). Do NOT use for design the JSON shape of an API endpoint (use api-design).

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

18.8 KB, ~3.5k tokens by cl100k_base, as published. Nobody here has run it

Type Safety

Concept of the skill

Two-layer model: a compile-time layer where the type checker verifies internal consistency (claims about values are coherent across the codebase) and a runtime layer where values from outside the program (HTTP responses, environment variables, parsed JSON, untrusted user input) have NO type until parsed, regardless of what type annotation sits next to them. The discipline is the boundary contract between these two layers: validate at I/O boundaries (parse, never assert), then trust the type inside. Layered on top: soundness (does the system rule out ALL type errors of the kinds it tracks, or only some via escape hatches?), structural vs nominal typing (do shapes match by structure or by name?), narrowing (refine a broad type by control-flow evidence), and exhaustiveness checking (force the compiler to flag uncovered cases in a discriminated union).

Distinguishes a syntactic claim from a semantic guarantee. Without type-safety as a discipline (not just a compiler flag), JSON.parse(x) as User looks identical to a validated parse — the cast is decoration, not verification. The alternative — "the compiler said it was fine, so it's fine" — fails because gradual systems like TypeScript are unsound by design (escape hatches: any, as, function bivariance, ambient declarations) and untrusted input arrives un-typed regardless of what annotation sits next to it. Type-safety replaces the "compiler-blessed" mental model with "compile-time guarantees stop at the I/O boundary, runtime validation takes over there."

Distinct from api-design, which owns the external request/response surface shape — type-safety owns the discipline of expressing internal program correctness as types, and where the type system stops at the boundary api-design defines. Distinct from testing-strategy, which owns runtime verification of behavior — type-safety owns compile-time verification of structure, and the two cover different failure modes (a function can be type-safe and behaviorally wrong, or behaviorally correct and type-unsafe). Distinct from entity-relationship-modeling, which owns persistence and entity shape — type-safety owns the in-memory type contracts that consume that shape. Distinct from input validation libraries (Zod, Yup, io-ts), which provide the runtime parsing mechanism — type-safety is the discipline that decides where parsing must happen because the type system cannot. Type safety is to programs what a passport check is to international travel — the document (type annotation) certifies identity within the issuing country's records, but on the way through customs (the I/O boundary), the document is re-verified against the actual traveler, and any mismatch is rejected before they enter the trusted zone. The wrong mental model is that a TypeScript as cast is a form of validation. It is not. The cast is a programmer-asserted claim that compiles unchecked at runtime, and JSON.parse(x) as User produces a value typed as User with zero verification that it actually has the fields a User must have. The misconception conflates two layers — the compile-time claim (which the compiler accepts) and the runtime guarantee (which the cast does nothing to establish). The discipline is to use runtime validators (Zod, io-ts, manual parse functions) at every I/O boundary and treat as as an explicit, justified, rare escape hatch — not as the default way to silence a type error.

Coverage

The discipline of using a type system to rule out classes of runtime errors before they occur. Covers what soundness means and where TypeScript (and other gradual systems) is unsound, structural vs nominal typing, type narrowing and exhaustiveness checking, the runtime boundary problem, the difference between any and unknown, when to use type assertions (rarely) and when to validate (always at I/O boundaries), and the connection to runtime validation libraries.

Philosophy of the skill

Types are claims about values; type-checking is proof-checking. A program that compiles is a program whose claims have been internally consistent — but a program is more than its compiler. Values that arrive from outside the program (HTTP responses, environment variables, parsed JSON, untrusted user input) have no type until you parse them, no matter what type annotation sits next to them.

The discipline of type-safety is to take the compile-time guarantees seriously and to know exactly where they stop. A codebase that pretends JSON.parse(x) as User is safe has confused a syntactic claim with a semantic guarantee. A codebase that validates at the boundary and trusts the type inside has correctly aligned the two layers.

In gradual systems like TypeScript, the discipline includes treating escape hatches (any, as, // @ts-ignore) as exceptional, justified, and rare — not as the default response to a type error.

Soundness — What the System Actually Promises

SystemSoundnessWhere it leaks
TypeScriptUnsound (by design)any, as, function bivariance, ambient declarations, type assertions, Object.keys() typed as string[], array .find() returning a T not T | undefined (without strict flag), unchecked noUncheckedIndexedAccess, mutable arrays in covariant positions
FlowUnsoundSimilar escape hatches; less broadly adopted
Python + mypyUnsound (gradual)Any, cast, dynamic-only constructs
JavaSound for types, unsound for nullNullPointerException; generics erased at runtime
C#Mostly soundReflection, dynamic
GoSound for types, structural interfacesEmpty interface (interface{}) is the escape hatch; type assertions panic on failure
RustSound (memory + types)unsafe blocks are documented escape hatches
HaskellSound (within purity)unsafePerformIO, FFI

Practical TypeScript stance: Enable strict mode (strict: true), enable noUncheckedIndexedAccess, enable noImplicitAny. These flags close the most common leakage points. Without them, the system's guarantees are substantially weaker than developers assume.

Narrowing

Narrowing is the type checker's mechanism for refining a broad type based on control-flow evidence. Use it instead of casts.

function process(x: string | number | null) {
  if (x === null) return;            // narrows to string | number
  if (typeof x === 'string') {        // narrows to string
    return x.toUpperCase();
  }
  // here x is narrowed to number
  return x.toFixed(2);
}
Narrowing techniqueUse when
typeof x === '...'Distinguishing primitives
x instanceof ClassDistinguishing class instances
'field' in xDistinguishing discriminated objects
x === literalDistinguishing literal types
Discriminated union via tag fieldDesigned-in narrowing for ADTs
User-defined type guards (x is T)Custom predicates
Array.isArray(x)Array vs non-array

Discriminated unions are the strongest pattern:

type Result =
  | { ok: true; value: User }
  | { ok: false; error: string };

function handle(r: Result) {
  if (r.ok) return r.value;  // narrows; `r.error` is not accessible here
  return r.error;            // narrowed to the error branch
}

Exhaustiveness Checking

Force the compiler to verify that all cases of a union are handled. The pattern uses an unreachable never branch.

type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';

function describe(m: Method): string {
  switch (m) {
    case 'GET':    return 'safe';
    case 'POST':   return 'mutation';
    case 'PUT':    return 'idempotent replacement';
    case 'DELETE': return 'idempotent removal';
    default: {
      const _exhaustive: never = m;  // compile error if a case is missing
      throw new Error(`unhandled: ${_exhaustive}`);
    }
  }
}

When a new variant is added to Method, the never assignment fails to type-check — the compiler points at every missing branch. This converts a runtime "unhandled case" bug into a compile-time error.

The Runtime Boundary

Type information stops at the runtime boundary. Values crossing in must be parsed; values crossing out are serialized.

BoundaryRiskDiscipline
JSON.parse(networkResponse)Returns any (or unknown with stricter typing); no validationParse with a schema (Zod, io-ts) before trusting the type
process.env.XAll env vars are string | undefined, but TypeScript may type them as string via globalsValidate at startup with a typed env config object
localStorage.getItem(k)Returns string | null, but the contents are untypedParse + validate before use
fetch(url).then(r => r.json())The promise resolves with anyValidate against an expected schema
Database driver resultsLibrary-typed; trust depends on the library's contractVerify the library actually checks types at the boundary
Function(string) / evalArbitrary code; arbitrary typesAvoid; if necessary, type the result as unknown

The pattern: validate at the boundary; trust the type inside.

import { z } from 'zod';

const UserSchema = z.object({
  id: z.string(),
  email: z.string().email(),
  createdAt: z.coerce.date(),
});

type User = z.infer<typeof UserSchema>;

async function fetchUser(id: string): Promise<User> {
  const raw = await fetch(`/api/users/${id}`).then(r => r.json());
  return UserSchema.parse(raw);  // throws on validation failure
}
// Inside the rest of the program, User is trusted.

any vs unknown vs never

TypeSet of valuesWhat you can do with it
anyAll valuesAnything (escape hatch — no checking)
unknownAll valuesNothing until you narrow (safe escape hatch)
neverNo valuesNothing (used for exhaustiveness checks and unreachable code)
voidReturned, not consumedFunction return only; the value is "no value worth using"

Rule: prefer unknown over any always. The cost is one narrowing step; the benefit is type-safety preserved.

Verification

After applying this skill, verify:

  • TypeScript strict mode is enabled ("strict": true in tsconfig).
  • noUncheckedIndexedAccess is enabled; array/object access produces T | undefined.
  • No any appears in committed code without an inline comment explaining why unknown is insufficient.
  • No as Type cast appears without an inline comment explaining why narrowing is insufficient.
  • Every I/O boundary parses with a runtime validator (Zod, io-ts, valibot, etc.) before the value is treated as typed.
  • Discriminated unions have an exhaustiveness check at every consumer site.
  • Public API boundaries (exported functions, route handlers, library entry points) have explicit return types — not just inferred.
  • // @ts-ignore and // @ts-expect-error are accompanied by a justification and a tracking comment.

Do NOT Use When

Instead of this skillUseWhy
Designing the JSON shape of an API endpointapi-designapi-design owns the external surface contract; this skill owns internal type discipline
Verifying behavior at runtime with teststesting-strategytesting-strategy owns runtime verification; this skill owns compile-time
Designing database schema and column typesentity-relationship-modelingentity-relationship-modeling owns persistence shape; this skill owns in-memory type contracts
Choosing between Zod / io-ts / valibotindividual library docs + api-designLibrary choice is a tactical decision below this skill
Implementing the compiler / type-checkerlanguage compiler implementation referencesOut of scope — this skill is about using type systems, not building them

Key Sources

  • Pierce, B. C. (2002). Types and Programming Languages. MIT Press. The canonical textbook. Chapters 1-3 cover untyped lambda calculus, simple types, and the soundness/progress/preservation framework that underpins every type system.
  • Siek, J. G., & Taha, W. (2006). "Gradual Typing for Functional Languages." Scheme and Functional Programming 2006. The original gradual typing paper.
  • Siek, J. G., & Vachharajani, M. (2008). "Gradual typing with unification-based inference." Proceedings of the 2008 symposium on Dynamic languages. Formalizes the soundness vs ergonomics trade-off in gradual systems.
  • Microsoft. TypeScript Handbook. The reference for TypeScript's type system, including the documented unsoundness in Type Compatibility and Narrowing chapters.
  • Anders Hejlsberg et al. TypeScript Design Goals. Explicit acknowledgement that TypeScript trades soundness for ergonomics: "non-goals: apply a sound or 'provably correct' type system."
  • Curry, H. B., & Feys, R. (1958). Combinatory Logic, Volume I. Original work on the Curry-Howard correspondence — types as propositions, programs as proofs.

Skill Graph context

<!-- skill-graph-context:start (generated — do not edit by hand) -->

Classification

  • Subject: quality-assurance
  • Public: true
  • Domain: quality/types
  • Scope: Use when reasoning about types as a quality property of code: what guarantees the type system actually provides, the difference between sound and unsound systems, structural vs nominal typing, type narrowing and exhaustiveness, the runtime/compile-time boundary, and where validation must happen because the type system cannot. Covers TypeScript, Flow, Hindley-Milner languages, and gradual typing in general. Do NOT use for runtime input validation library choice (use api-design for API surface validation; use individual library docs for library mechanics), for SQL type mapping (use entity-relationship-modeling), or for type system implementation (compilers — out of scope).

When to use

  • review whether this discriminated union has an exhaustiveness check at the switch
  • decide whether to use any or unknown for this third-party JSON payload
  • explain why TypeScript's as cast doesn't actually validate at runtime
  • design where Zod (or any validator) parses at the application boundary
  • Triggers: is this type-safe, should this be anyorunknown``, exhaustiveness check, narrowing, where does validation belong

Not for

  • implement HMAC verification for an inbound webhook (use webhook-integration)
  • design the JSON shape of an API endpoint (use api-design)
  • choose between Postgres column types (use entity-relationship-modeling)

Related skills

  • Verify with: code-review, testing-strategy, client-server-boundary
  • Related: entity-relationship-modeling, api-design, testing-strategy, code-review, prompt-injection-defense

Concept

  • Mental model: |
  • Purpose: |
  • Boundary: |
  • Analogy: Type safety is to programs what a passport check is to international travel — the document (type annotation) certifies identity within the issuing country's records, but on the way through customs (the I/O boundary), the document is re-verified against the actual traveler, and any mismatch is rejected before they enter the trusted zone.
  • Common misconception: |

Keywords

  • type safety, TypeScript, sound type system, unsound type system, structural typing, nominal typing, type narrowing, exhaustiveness check, gradual typing, runtime boundary
<!-- skill-graph-context:end -->

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.