Files
deepseek-harness/docs/rfc/implemented/architecture/2026-06-11-runtime-arg-validation.md
Tianyi Cui e6fad266a6 docs(rfc): define and enforce a uniform RFC format; adopt it across the corpus
Define the in-file RFC contract in docs/rfc/README.md § The file format:
the header block (`# RFC: <title>` plus a dateless Status enum
cross-checked against the lifecycle folder), the per-lifecycle body
skeleton (a Problem opener everywhere; Proposal/Alternatives considered/
Acceptance criteria/Risks in proposed/; present-tense Decision/
Consequences with proposal-era headings banned in implemented/; the
frozen proposal shape in rejected/), and a mandatory Alternatives
considered section with a date-fenced grandfather comment for pre-format
RFCs whose alternatives are not reconstructible from the record.

Enforce it with a new doc-sync gate, scripts/verify-rfc-format.ts, and
normalize all 112 RFCs to it: ~15 Status-line spellings collapse to the
enum, 29 Context openers become Problem, the 39 legacy-format XXX debt
markers are resolved and banned from reappearing, proposal-era sections
in implemented RFCs are rewritten to shipped reality (including the
web/fs/subagent seam RFCs' migration plans and test checklists, closing
the doc-tiers deferred-work item on the web seam), every RFC gains an
Alternatives considered section or the grandfather comment, and the
bilingual pair is re-mirrored and re-recorded.

Move the generated index tables out of README.md into a fully generated
docs/rfc/INDEX.md — gen-rfc-index now writes the whole file, and
verify-rfc-classification checks its freshness and rejects index-shaped
rows in the curated README — which makes room for the format contract to
live in the README front door instead of a separate FORMAT.md.

The decision record, and the first RFC written in the new format, is
docs/rfc/implemented/process/2026-07-05-uniform-rfc-format.md.
2026-07-05 22:58:25 +08:00

2.3 KiB

RFC: Runtime arg validation at the model boundary

Status: implemented

Problem

defineTool (the custom schema DSL) gives tool authors a typed execute(args) via the InferArgs<S> mapping. But that type is a compile-time claim about a value that arrives at runtime as model-generated JSON: nothing forced the model to honor the schema, so a malformed call — missing a required key, a string where a number was declared, an enum value outside the set — reached execute typed-in-name-only. The tool body then either crashed on the bad shape (a generic stack trace the model can't act on) or, worse, silently misbehaved. Meanwhile the converter already encodes the exact structure a validator would need to walk.

Decision

validateArgs(spec, args): string[] interprets a SchemaSpec over a runtime value, returning human-readable violations (empty = valid), and is total (never throws). defineTool runs it before the typed body; on violations it throws ToolArgsError (code: 'INVALID_ARGS', message listing the violations), which the registry's existing execute-waterfall catch turns into an isError result the model reads and self-corrects from.

The validator mirrors schemaSpecToJsonSchema semantics exactly — same structure walked, same rules: top level must be a non-array object; required keys come only from required: true; extra keys are allowed (no additionalProperties: false); default is not applied; an object/array prop without properties/items only type-checks; enum is membership. Raw-registered (MCP) tools are not touched — they validate their own input.

Consequences

  • The model gets actionable feedback on its own malformed calls instead of an opaque crash, closing the gap between InferArgs's promise and runtime reality.
  • The validator and InferArgs must stay in agreement; a property test generates args satisfying a spec and asserts they pass validateArgs (with targeted corruptions rejected), closing that drift risk mechanically.
  • ToolArgsError is a plain Error with a code field for now; if a harness-wide error taxonomy lands it becomes a subclass without changing callers that read .message.
  • Validation cost is negligible next to a model call.