Status: implemented — part 1 (arg validation) → ADR 0011; part 3 (dev invariants) → ADR 0012; part 2 (error taxonomy) → ADR 0015
Three gaps where compile-time guarantees stop:
defineTool's InferArgs<S> claim is only as true as the model's output. Today a malformed call reaches execute untyped-in-practice.execute, validate parsed args against the SchemaSpec (the converter already encodes the structure — a small interpreter walks it: presence of required keys, primitive type checks, enum membership, recursion into objects/arrays). On mismatch, return an isError ToolExecutionResult describing the violation — the model can self-correct. Raw-registered tools (MCP) keep validating their own input.HarnessError (name, code, cause chaining). ToolExecutionResult gains optional error: { name, code } alongside the model-facing text. The loop's errorData consumes it; session error events carry the code. This also properly fixes the non-Error-throw message degradation found in review.dsh-invariants debug plugin (everything is a plugin — it's just listeners) asserting, when enabled: session seq strictly increases; step/start precedes its chunks; turn/start/turn/end pair and nest; tool/call has a matching tool/result; status transitions are legal. Enabled in tests and the demo; off in production. Doubles as executable documentation of the event contract. (As implemented, the tool rule is one-directional — a tool/result requires a prior tool/call, but NOT the converse: a throwing tools/execute waterfall ends a step with no result. See ADR 0012.)2 first (taxonomy is a dependency of 1's error shape), then 1, then 3. Property tests (RFC 001) then close the loop: generated args ↔ validator ↔ InferArgs agreement.
Validator/InferArgs drift — covered by the RFC 001 composition property. Validation cost per call is negligible next to a model call.