Agent Skill — SKILL.md
schema-boundary-typing
Introduce or refine runtime schema validation at untrusted boundaries so static TypeScript types stay truthful. Use when API, storage, or parsing input flows into typed code without runtime validation.
Compatibility
Agent Skills-compatible coding agents.SKILL.md
Schema boundary typing
Use this skill when
- The user wants runtime validation and static typing to agree at an API, storage, or parsing boundary.
- Untrusted input currently flows into application code with weak typing.
- The repository already uses a schema or validator library, or the boundary needs either a simple guard or a reusable schema pattern.
Do not use this skill when
- The boundary is already validated elsewhere and the task is only to reuse an existing type.
- The task is mainly about internal domain modeling rather than input validation at the edge.
Inputs to gather
Required before editing
- The untrusted boundary, such as request input, JSON parsing, or persisted records.
- Any existing schema or validator library already used by the repository.
- The expected validated shape and the code that consumes it.
- Error-handling expectations when validation fails.
Helpful if present
- Existing parsers, decoders, or helper wrappers around the same boundary.
- Tests for invalid payloads.
- Whether the validated type should be derived from the schema or mapped into a separate domain type.
First move
- Find the boundary where untrusted data first enters typed code.
- Reuse the repository's existing schema or validator approach before adding anything new.
- If no shared schema tool exists, decide whether a simple guard is enough or whether the boundary is important enough to justify a reusable schema pattern.
- Define the validation close to the boundary, not deep inside consumers.
Workflow
- Model the external shape with the repository's existing schema or validator tool.
- Parse or validate at the first boundary that can reject bad input cleanly.
- When a simple guard is enough, keep the solution local and do not introduce a full schema dependency.
- When a reusable schema exists or is justified, derive or expose the validated TypeScript type from it when the library supports that pattern.
- Keep unvalidated data as
unknownuntil it passes validation. - Map into a separate domain type only when the repository already distinguishes transport and domain models.
- Update downstream consumers to rely on the validated type instead of re-checking ad hoc.
Guardrails
- Must not claim a precise type for untrusted input without a matching runtime check.
- Must not introduce a new schema library when the repository already has a standard one or when a simple local guard is sufficient.
- Should validate once at the edge and pass the validated type inward.
- Should preserve existing error mapping and boundary behavior.
- Should prefer a simple local guard when the boundary is small, one-off, and unlikely to be shared.
- May keep a separate domain model when transport shapes and internal models differ materially.
Routing boundary
- Route here from
typescript-any-eliminatorwhen replacinganyrequires runtime boundary validation to make types truthful. - After boundary types are stable, route compile-time contract locking to
type-test-authoring.
Validation
Run targeted tests for valid and invalid boundary inputs.
Verify invalid payloads produce the expected failure shape: field errors, result-object errors, or thrown boundary errors that match the repository's convention.
Re-run typecheck after deriving or re-exporting the validated type.
Confirm consumers no longer rely on unvalidated
unknownor ad hoc casts.Keep
references/boundary-validation-scenarios.mdin sync when the repo's error-handling convention, schema library, or transport-to-domain boundary changes.Smoke test:
- should trigger: "Validate this JSON request body at the API edge and export the safe type."
- should not trigger: "Add compile-time tests for this inferred API type." (→
type-test-authoring)
Examples
Beforeconst payload: User = JSON.parse(raw);Afterconst payload = UserSchema.parse(JSON.parse(raw));Failure shape(custom result wrapper when callers branch on success or failure){ ok: false, errors: [ { path: ['body', 'email'], message: 'Invalid email address' }, ], }Beforeexport function readConfig(value: any) { return value.mode; }Afterexport function readConfig(value: unknown) { return ConfigSchema.parse(value).mode; }
Reference files
references/schema-patterns.md- patterns for schema-first validation, derivation, and transport-to-domain boundaries.references/boundary-validation-scenarios.md- compact checklist for success, failure, and error-shape validation at the boundary.