Skip to content

Evidence Integrity: unvalidated-handler ratchet counts wraps, not verified round-trips — VALID-03 regressed 4 handlers behind a passing number #7

Description

@qmarcelle

The unvalidated-handler ratchet measures presence of validatedHandler() wrapping, not verified behavioral correctness. A true, correctly-computed number measured the wrong property, and that gap regressed 4 live daemon endpoints behind a ratchet that went the right direction.

What happened, mechanically

Commit 2c5ab726e8 (2026-06-04, "VALID-03 Batch B") wrapped 13 session/* IPC handlers in validatedHandler(Schema, () => svc.method(params)), dropping the unvalidated-handler ratchet 114 → 102. Ratchet moved the correct direction. PR merged.

The same commit broke 4 of those 12 wrapped handlers, because wrapping a handler in a Zod schema check is not the same claim as "this handler's actual return value satisfies that schema" — and nobody verified the second claim before counting the first. Full handler-by-handler audit and fix is on the sibling issue (VALID-03 regression, four handlers). The count here is what let it through:

  • Ratchet: 114 → 102. Moved.
  • Actual validated-and-correct handlers: went from N (whatever the true unbroken count was before) to N − 4. Moved the other way.
  • The ratchet cannot tell these apart, because it counts validatedHandler( call sites in source, not observed round-trips.

Why this is Evidence Integrity's second real counter

counter.workspace_json_field_gap (#4) catches spec-declared fields absent from the live artifact — a real measurement of a real property, still capable of drifting from what it's meant to represent if the check is shallow. This is the same failure class one level up in the validation-tooling itself: the instrument's own success was the regression. 102 is not a hardcoded constant dressed as a measurement — it's a true, computed count. It measures "handler wrapped in a validator," not "handler validated correctly." Those are different claims, and the gap between them was exactly 4 handlers wide.

The acceptance test — written now, while it's sharp, because it's recursive

The replacement counter cannot be "count the handlers that have a test referencing their schema." That is the identical failure one level deeper — reading test source to make a claim about behavior, rather than observing the behavior. This is precisely how the 4 regressed handlers hid for 36 days: two existing tests reference SessionCurrentResult at all, and both redefine the type locally for their own purposes (type SessionCurrentResult = SessionType | null; in session-restore.test.ts and restore-lifecycle.integration.test.ts) and call the service method directly, bypassing protocol.ts's routing and the real validatedHandler entirely. A test existing is not evidence. A test passing is not evidence, if it never touched the real path.

The counted unit is: a handler that actually round-trips green through the real validatedHandler path, in an executed test run, observed this run — not a handler that has a test, not a handler that is wrapped in source. Concretely: a test that calls the handler through createProtocolHandler() (or equivalent), exercising the real schema import and the real service method, and asserts the result parses. If that test is skipped, the counter must not count it. If the test redefines the schema type locally instead of importing the real one, the counter must not count it — that's not a hypothetical, it's the exact mechanism that hid these 4 for over a month.

Scope

  1. Locate/build the mechanism that can observe "this handler round-tripped through real validation, this run" — likely a coverage/instrumentation hook on validatedHandler() itself, correlated against which session/* (and eventually all namespace) actions were actually exercised by the test suite with real schemas.
  2. Re-baseline the ratchet against the true count once the 4 regressed handlers (sibling issue) are fixed and behaviorally proven.
  3. Retitle/redefine the ratchet's semantics in its own documentation so "unvalidated" cannot silently mean "wrapped but unverified" again.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions