typescript-runtime-boundary-modeling
Own trust-boundary shaping in strict-mode TypeScript backends. Use whenever the task is about turning request, config, external API, database, cache, JSON, or caught-error data from `unknown` or weakl
SKILL.md.claude/skills/typescript-runtime-boundary-modeling/SKILL.md
GitHubTypeScript Runtime Boundary Modeling
Purpose
Own the narrow seam where runtime data stops being merely present and starts being trustworthy.
This skill is about how untrusted or weakly typed values become trusted internal representations through real runtime checks, normalization, and explicit boundary placement.
It is not a general TypeScript style guide, not public API contract design, not advanced type-level modeling after parsing, and not storage-engine semantics.
Use it to reason like a boundary specialist:
- name the exact source of untrusted data
- name the exact point where trust changes
- define the smallest surface that must be runtime-checked before the next layer can rely on it
- choose a concrete parsing or validation shape instead of generic tooling slogans
- keep assumptions, confidence, and residual trust-leak risk explicit
Specialist Stance
This skill should reason more narrowly and more rigorously about runtime trust boundaries, not just repeat generic type-safety advice.
The durable advantage of this skill must come from forcing a better reasoning path:
- smaller and more explicit trusted claims
- sharper separation between validated, normalized, and truly trusted shapes
- stricter rejection of accidental trust leakage
- explicit assumptions, confidence, and rejected shortcuts
- pressure-testing the boundary before accepting the first plausible parser
If a broad but competent TypeScript answer would still look interchangeable with the result, this skill is not doing enough work.
Expert Standard
Do not spend time restating that TypeScript types disappear at runtime or that schema libraries exist.
The value of this skill is narrower and more defensible boundary judgment, not broader TypeScript trivia.
Its job is to force deeper specialist thinking:
- do not say "use zod", "add types", or "validate it" without naming the exact boundary, trusted claim, unknown-key policy, and output shape
- do not say "treat it as
unknown" unless you also say where it stops beingunknown - validate the exact surface the next layer relies on, not a smaller prefix and not an unjustifiably larger object
- keep "validated" separate from "normalized" and separate again from "trusted internal"
- say what is observed in the code or config versus what is inferred
- lower confidence when the real parser,
tsconfig, lint rules, or data shape are not visible - name the most tempting unsafe shortcut and explain why it leaks trust
- name the omission that matters most here: an over-trusted shape, an unspoken policy, or a boundary that is too wide
If the answer could be rewritten as a generic "TypeScript safety" blog post with only small wording changes, it is still too shallow for this skill.
Expert Target
Keep this skill durable over time.
That means:
- optimize for better boundary decisions, not for surprising factual trivia
- encode a disciplined reasoning sequence so important checks are harder to skip
- require the answer to expose the omitted trust claim, policy choice, or boundary edge
- make the result more falsifiable through exact trusted claims, policy choices, and rejected alternatives
- reject answers that are merely competent and broad when the skill can be narrow and exact
Quality Bar
Reject vague or decorative guidance.
A good answer from this skill must:
- identify the primary boundary source:
request, config, external API, persistence, cache, JSON parse, or
catch - state the trust transition in concrete terms:
untrusted -> validated -> normalized -> trusted internal - define the minimal checked surface that supports the trusted claim
- choose a concrete mechanism: manual guard, assertion function, schema-derived parser, or boundary mapper
- choose concrete policies when they matter: throw versus result, reject versus strip versus passthrough, sync versus async parse, transform location
- name the trusted output shape and which layer owns it
- call out at least one trust-leak risk, rejected shortcut, or hidden assumption
- compare the strongest tempting broader answer and explain why it still trusts too much, checks too little, or hides a key policy decision
- separate observed facts from assumptions and give an honest confidence level
If any of those are missing, the answer is probably merely topical, not expert.
Scope
unknownversusanyat runtime boundaries- request, config, external API, persistence, cache, and
catchas sources of untrusted values - parser functions, guards, assertion functions, schema-derived validation, and normalization layers
- explicit separation of transport DTOs, records, cached shapes, and trusted internal representations
- unknown-key handling, transform placement, parse result shape, and boundary ownership
- strict compiler and lint guardrails only where they materially affect boundary honesty
Read These References When You Need Them
- the required step-by-step design pass for this seam:
references/boundary-design-workflow.md - the compact trade-off guide for mechanism and policy choices:
references/policy-decision-guide.md - the concrete TS, lint, Node, and validator anchors that reject
plausible-but-wrong boundary advice:
references/stack-specific-hard-anchors.md - the source-by-source default boundary map:
references/source-surface-matrix.md - concrete parser, guard, assertion, and normalization shapes:
references/parser-shape-rules.md - red flags that indicate accidental trust leakage:
references/trust-leak-smells.md - how to audit an unfamiliar repository for real trust boundaries:
references/unfamiliar-codebase-checklist.md - the pressure-test that turns a plausible answer into a stronger specialist
answer:
references/reasoning-pressure-test.md
Relationship To Shared Research
Start with the local references in this skill.
Load references/boundary-design-workflow.md by default.
Load references/reasoning-pressure-test.md for every non-trivial task or
when the first draft feels plausible but too generic.
Load references/policy-decision-guide.md when the hard part is choosing
between guards versus schemas, throw versus result, reject versus strip, or
how much of the raw shape should become trusted.
Load references/stack-specific-hard-anchors.md when the recommendation turns
on concrete TypeScript compiler flags, typescript-eslint no-unsafe-*
guardrails, Node process.env behavior, catch variable semantics, or
validator-specific caveats like unknown-key defaults and transform or async
parse behavior.
Load the focused reference that matches the current question. Do not load everything unless the task genuinely crosses several runtime-boundary sources.
Load ../_shared-hyperresearch/deep-researches/typescript-runtime-boundary-modeling.md
only when:
- the task depends on version-sensitive TypeScript or validator semantics
- the local references are not enough to resolve a boundary decision
- the codebase is unfamiliar and you need the deeper investigation map
- the choice between manual guards, schema-derived parsing, and layered normalization is still ambiguous
Version anchor: TypeScript 5.9 strict-mode Node.js/backend code. If the repo or task depends on a different TS version or a materially different runtime stack, say so explicitly.
Relationship To Neighbor Skills
- Use
typescript-language-corewhen the main issue is ordinary narrowing, optionality, orunknownsemantics without a real runtime-boundary design decision. - Use
typescript-public-api-designorapi-contract-designer-specwhen the hard question is which public request or response shape should exist, rather than how to make an already-chosen input trustworthy. - Use
typescript-advanced-type-modelingwhen the difficult work starts after normalization inside the trusted internal model. - Use
prisma-postgresql-data-specwhen relational semantics, migrations, or query behavior dominate beyond generic record-to-internal shaping. - Use
redis-runtime-specwhen cache semantics, TTLs, or Redis data behavior dominate beyond generic cache-value distrust. - Use
external-integration-adapter-specwhen the real problem is provider adapter ownership rather than local parsing and trust conversion.
If a task crosses seams, keep this skill focused on trust conversion and hand off the rest explicitly.
Input Sufficiency
Before answering, identify the minimum missing facts:
- is this greenfield boundary design, refactor, or audit of existing code
- what is the real source surface and raw shape
- do you see the actual parser or only the symptom
- do you know the effective
tsconfigand type-aware lint guardrails - is the goal to trust the whole object or only a smaller internal claim
If those facts are missing, say what you are assuming and reduce confidence. Do not talk as if the real boundary has been observed when it has not.
Trust Model
Treat each value at a runtime boundary as moving through four states:
UntrustedRaw runtime data. This should usually be modeled asunknownor a weak raw shape.ValidatedStructural checks have proved the fields and forms the next step depends on.NormalizedThe validated data has been coerced, trimmed, defaulted, or mapped into the canonical local form.Trusted InternalInternal code may rely on the shape and invariants that the boundary really established.
Important rule:
- "trusted" means "this exact claim was runtime-checked or produced by code that only runs after runtime-checks"
- it does not mean "we wrote an interface" or "TypeScript accepted the cast"
Minimal Checked Surface
Use the smallest fully checked surface that the next layer actually relies on.
That means:
- if the next layer needs only
id,status, andexpiresAt, validate and normalize exactly that surface and keep the rest opaque - if the next layer receives the full object as trusted internal state, then the full object must be checked according to the chosen policy
- do not validate a top-level object and then trust unvalidated nested fields
Healthy Boundary Ownership
A healthy runtime boundary usually has:
- one obvious parser, decoder, or mapper entrypoint
- one obvious place where unknown-key or extra-shape policy is chosen
- normalization in the same boundary layer or immediately after structural validation
- a trusted output type that the core can consume without importing request DTOs, DB records, or cache wire shapes
Workflow
1. Confirm Topic Fit
- decide whether the request is really about runtime trust conversion
- if the main problem is contract design, domain typing, or store semantics, hand off instead of stretching this skill
2. Locate The Real Boundary
Name:
- the source surface
- the raw form that enters
- the module or function where trust should change
- the layer that will consume the trusted output
Do not speak abstractly about "validation somewhere near the edge."
3. Define The Trusted Claim
Before choosing a tool, say exactly what the next layer is allowed to believe.
Examples:
- "service may rely on
portas a normalized integer in range X" - "domain code may rely on
emailandrole, but raw provider metadata stays opaque" - "cache reader may trust only the decoded envelope header, not the embedded payload"
4. Choose The Mechanism
Pick the smallest mechanism that can fully prove the trusted claim:
- manual guards for tiny, local, stable shapes
- assertion functions when failure should throw and the runtime proof is local
- schema-derived parsing when nesting, unknown-key policy, reuse, or clear trusted output matters
- explicit mappers when transport or record shapes must be separated from the trusted internal representation
Do not choose a library by brand recognition alone.
5. Choose The Boundary Policies
State the policy choices that affect real trust:
throwversus structured resultreject,strip, orpassthroughfor unknown keys- sync versus async parsing when transforms or external checks exist
- where normalization happens and whether it is pure and centralized
If the answer does not name these choices where relevant, it is still too hand-wavy.
6. Shape The Trusted Output
Define:
- the trusted output type or object shape
- whether it is DTO-like, record-like, or true internal representation
- which raw shapes remain outside the trusted zone
- whether core code can stay isolated from transport and persistence types
Prefer output signatures like:
parseX(input: unknown): TrustedXparseX(input: unknown): Result<TrustedX, Issues>assertX(input: unknown): asserts input is TrustedX
Use asserts only when a real runtime proof happens inside that function.
7. Pressure-Test Trust Leakage
Before finalizing, ask:
- what fields are still untrusted?
- where could
any,!, oras unknown assmuggle trust across the seam? - are extra keys or nested values silently surviving without policy?
- is truthiness-based narrowing hiding valid empty values?
- is normalization happening ad hoc in several places instead of once?
- what observed facts support the answer, and what is still assumed?
8. Omission Check
State which boundary omission is still unresolved here, then state what it would still miss:
- a trusted claim that is too wide for the proof
- a policy choice that stayed implicit
- a raw shape that leaked into core code
- a shortcut that looks clean but bypasses runtime evidence
If you cannot name that omission, the answer may still be too generic.
Preferred Defaults
- treat every external value as
unknownuntil a boundary parser proves otherwise - keep one obvious parse or normalize entrypoint per boundary source
- prefer schema-derived parsing when the shape is nested, reused, or policy sensitive
- prefer manual guards only for small shapes where the full proof stays easy to review
- make unknown-key policy explicit
- keep transforms and defaults centralized in the boundary layer
- treat
process.envas string input that must be parsed once at startup - treat
catch (err)as a boundary and narrow fromunknown - use strict compiler and
no-unsafe-*lint rules as containment aids, not as substitutes for runtime checks
Failure Smells
as any,as unknown as T, or postfix!near external input- a parser that checks the top-level object but trusts nested fields
- "we validate it in middleware" without naming the trusted output that leaves the middleware
- silent passthrough of extra keys without an intentional policy
- transforms that throw unexpectedly or run before structural assumptions are established
- domain or core modules importing transport DTOs or DB record types as if they were already trusted internal models
- config parsing spread across the codebase instead of one startup boundary
anyleaking from SDKs, JSON, cache reads, or third-party helpers into typed code
Deliverable Shape
Design or audit answers should normally use this structure:
Boundary SourceObserved Facts / Missing FactsTrust TransitionMechanism And PoliciesTrusted Internal ShapeTrust-Leak Risks / Rejected ShortcutConfidence
Inside Mechanism And Policies, explicitly cover:
- the parser or guard shape
- the checked surface
- unknown-key handling if relevant
- normalization location
- throw versus result behavior if relevant
Escalate When
Escalate if:
- the real question is which public API contract should exist
- the trusted internal model needs advanced type-level design beyond the boundary
- persistence or cache semantics dominate the decision
- the recommended parser depends on library-specific performance or ecosystem trade-offs that are central to the answer
- the codebase hides the real parser,
tsconfig, or lint boundary so heavily that confidence is low