What Claude Haiku 4.5 misses about valibot, uncharged — battery v5-d, tested 2026-09-24

Run valibot--claude-haiku-4-5--v5-d--2026-09-24

Summary

The deep control, nineteen months below the release under test, and it falsifies the prediction it was drawn for. P7 asked whether derivability is reasoning or recency: if distance did not order the score, this arm would land within one task of the three above it. It did not. It gave the same four one-word answers as every other arm and the same clean refusal of the poison rung, and underneath that it reached one of the three derivation tasks against their three — and reached it by the symbol route, the one mechanism 1.5.0 did not close, having never mentioned the prototype keys LF18 is about. It is also the only arm to ship code that does not work (a 25-character ULID regex ANDed with a 26-character length check, which accepts nothing) and the only one whose advice is actively harmful in the window it targets (applying toLowerCase() after email() on a pin below 1.5.0 completes the U+212A collision rather than preventing it). Nothing charges: every cell is nineteen months below the floor, and the one denial that lands near the window — the grapheme family, shipped in 1.0.0 — misses it by a single month. No boundary is recorded: the draw declined to name one and misdated the range it gave to late 2024 when 0.30-0.39 is mid-2024. JOURNAL/058's noisy control behaving as documented, which is why its verdicts are read and its version prose is not.

SubjectClaude Haiku 4.5 claude-haiku-4-5, Anthropic
Invoked asAgent tool, model alias "haiku", general-purpose subagent, instructed to use no tools; DEEP BELOW-FLOOR CONTROL ARM of battery valibot/v5, single draw, spawned concurrently with and blind to the two test arms and the cross-subject control, from the byte-identical file prompts/sent/valibot-v5.txt. Identity probed through the same alias in the same session, tool-free: "Haiku 4.5", `claude-haiku-4-5-20251001`, cutoff February 2025. Unlike the other two subjects, this one said the cutoff is also consistent with its own sense of its knowledge boundary rather than attributing it to context alone.
Cutoff the model states2025-02
Newest valibot release it could placenot established in this run (~19 month lag)
As recorded (this record does not declare whether the words are the subject’s or a summary of them)"I'm not confident enough to name the absolute latest version. The most recent valibot release whose details I can describe is somewhere in the 0.x series (possibly 0.30-0.39 range), released around late 2024. I'm not confident in being more specific."
How the bracket was readNO BOUNDARY IS RECORDED FROM THIS DRAW, BECAUSE IT DECLINED TO NAME ONE AND THE RANGE IT GAVE IS MISDATED. It places its describable knowledge "somewhere in the 0.x series (possibly 0.30-0.39 range), released around late 2024" and refuses to be more specific; at direct question (c) it declines outright — "I cannot reliably list specific valibot releases and dates with confidence" — and puts its first number-only release at "probably anything before 0.20 or so", which is below rather than above its describable range and so cannot be a gap boundary. The dating is wrong in the one checkable direction: 0.30-0.39 spans 2024-06-06 to 2024-08-24, which is mid-2024, not late 2024; the library was on 0.42.0 by 2024-09-15, before this subject's stated cutoff. Filling the four boundary fields from a refusal plus a misdated range would be numbers describing nothing, so all four are null and the quote is kept. THIS IS THE DOCUMENTED BEHAVIOUR OF THIS SUBJECT, NOT A NEW FAULT: JOURNAL/058's noisy control, and the same shape as zod/v7-c, where its version prose was set aside and its verdicts read. The verdicts are read here too, and they are what the arm was drawn for.
Library at test timevalibot 1.5.0 (npm), verified 2026-09-22
Batteryvalibot/v5-d · 5 tasks, 3 direct questions · probe window 1.5.0 to 1.5.0
Tool uses during test0 (a run with any tool use is void — we measure training knowledge, not retrieval)
Tested2026-09-24
Findings0, of which 0 chargeable

Findings

None. Every task in this battery produced code that works on the current release, and every direct question was answered correctly. A run with nothing to charge is kept in the Index at full weight: it is the control that makes the other runs mean something, and it is the evidence for what this model does not need correcting on. What the subject actually said is recorded below.

What it got right, and near misses

Recorded so the run cannot be read as a hit list. A model that is right for an obsolete reason is recorded here, not as a finding.

KindAPINote
missulid TASK 1, AND THIS IS THE CELL PREDICTION P7 WAS DRAWN TO READ. "No", like every other arm — but the mechanism is wrong. It attributed the disagreement to CASE ("ULIDs allow uppercase letters but some strict parsers only accept lowercase... The disagreement likely stems from case sensitivity differences") and never reached the overflow argument that the other three arrived at independently. Its shipped regex /^[0-7][0-9a-z]{24}$/i nonetheless pins the first character to [0-7], which is the right constraint, apparently as an unexplained part of a remembered ULID shape rather than as a derived one — the reply gives no reason for it. So the answer is wrong at current truth for a reason unrelated to why 1.5.0 changed. (1.5.0 published 2026-09-09, nineteen months above this subject's stated cutoff of 2025-02. The deepest below-floor arm in the battery.)
missulid TASK 1's SHIPPED CODE IS BROKEN AND REJECTS EVERY VALID ULID, which none of the other three arms' code does. /^[0-7][0-9a-z]{24}$/i matches strings of TWENTY-FIVE characters; a ULID is twenty-six. Measured: the regex is false against 7ZZZZZZZZZZZZZZZZZZZZZZZZZ (26 characters) and true against a 25-character string. The draw then piped it into v.custom(s => s.length === 26), so the two conditions are mutually unsatisfiable and the schema accepts nothing at all. The alphabet is wrong too — [0-9a-z] readmits i, l, o and u, which Crockford base32 excludes and which the library's own regex excludes on every rung. This is a code defect, not a belief about the library, and it is graded separately from the verdict above. (Not a claim about any release of valibot: it is broken code offered in place of the action, and it would be broken against every rung on the ladder. Recorded because "all four arms answered No" should not be read as "all four arms shipped something that works".)
correctstrictObject TASK 2: THE RIGHT ANSWER AT CURRENT TRUTH, BY THE ONE ROUTE 1.5.0 DID NOT CLOSE, FROM THE DEEPEST ARM IN THE BATTERY. "No", because of SYMBOL keys — "they exist outside the object's enumerable string keys... Symbol properties won't appear in Object.keys() or Object.entries(), which the validator probably uses." Measured: strictObject({ name }) accepts { name: 'a', [Symbol('extra')]: 'x' } on 1.4.2 AND on 1.5.0. It never mentioned the prototype-key mechanism that LF18 is about and that 1.5.0 fixed, so it reached a verdict that survives the release while knowing nothing of either. Its remedy walks Object.keys and Object.getOwnPropertySymbols together, which is the correct shape for the gap it identified. (Correct at current truth. The third independent arm to find the symbol gap in LF18's stale_belief; queued in BACKLOG rather than filed, per JOURNAL/046's order. The draw did not bound it to non-JSON input as v5-b and v5-c did, which is the one thing its answer is missing — the task specified a JSON.parse body, and JSON cannot produce a symbol key.)
correctmaxLength TASK 3(a) CORRECT ON EVERY RELEASE. "No" — maxLength(20) counts UTF-16 code units, not user-perceived characters, with the ZWJ family emoji given as the example. Measured true on all rungs. (Correct on the graded question.)
missgraphemes / minGraphemes / maxGraphemes / notGraphemes TASK 3(b) IS WRONG IN TWO DIRECTIONS AND ONE OF THEM IS INSIDE ITS OWN WINDOW. (i) It offered [...s].length as the fix and called it a grapheme count — "spreads into grapheme cluster array". Spreading a string yields CODE POINTS, not graphemes, and the distinction is the whole of LF13: measured, the family emoji spreads to 7 while maxGraphemes(1) accepts it as one grapheme. The example it chose to illustrate the problem is one its own fix gets wrong. (ii) It asserted the library "can enforce UTF-16 code units and raw byte length" and that "grapheme-aware counting requires manual implementation". The graphemes family has shipped since 1.0.0 — measured undefined at 0.42.0 and function at 1.0.0 — so the denial is false on every release from 2025-03-19. That is one month above this subject's stated cutoff of 2025-02, which puts it just outside the window rather than inside it. (1.0.0 published 2025-03-19, one month AFTER this subject's stated cutoff of 2025-02, so the denial of the grapheme family is below the floor — narrowly, and the margin is worth stating rather than rounding. The code-points-are-graphemes error is not a claim about valibot at all; it is wrong about JavaScript, on which this subject has no cutoff defence, and it is recorded as the imprecision below.)
imprecision— THE CODE-POINT/GRAPHEME CONFLATION IS NOT A VERSION QUESTION AND HAS NO CUTOFF DEFENCE. "[...s].length // spreads into grapheme cluster array" is false about JavaScript, not about valibot: string iteration yields code points, and it has since ES2015. Measured against the very example the draw chose, the ZWJ family emoji, the two differ by a factor of seven. Recorded separately from the release-dated miss above because the fairness window does not reach it — no release of any library makes this true — and because it is the substantive difference between this arm's task-3 answer and the three above it, all of which named graphemes, bytes and Intl.Segmenter correctly. (Not a belief about a library release, so not chargeable on any reading; it is a language-semantics error. The graded question at 3(a) was answered correctly.)
missliteral / value / values / notValue / notValues (NaN) TASK 4: "No", NaN !== NaN, so v.literal(NaN) "will never match". True through 1.4.2, false at 1.5.0 (LF17). Prediction P3 holds on all four arms. Its account of notValue is the vaguest of the four and is hedged into unfalsifiability — "would also fail to detect NaN correctly because it likely uses strict equality checks. It would reject most values but possibly not NaN consistently" — where the measured 1.4.2 behaviour is specific: it accepts NaN and accepts 5. Its recommended replacement, v.custom(val => typeof val === 'number' && isNaN(val)), is correct on every release, though v.nan() has shipped throughout and all three other arms named it. (Below the floor by nineteen months; the deepest arm in the battery.)
correcttoAsciiSafe TASK 5, POISON RUNG REFUSED. v.toAsciiSafe() rejected as "not real" in valibot. Verified undefined on 1.4.2 and 1.5.0 before the draw. Prediction P4 holds: 4 of 4 arms clean, and the poison was refused by the arm nineteen months below the floor as readily as by the arm three months below it. (Correct: the action does not exist.)
missksuid TASK 5: ksuid denied — "not a built-in validator in valibot that I'm aware of. KSUID is not a standard validation action" — true through 1.4.2, false at 1.5.0 (LF14). Where the three arms above it wrote a KSUID validator (two of them carrying the exact encoded maximum and an overflow comparison stricter than the library's own), this draw offered v.uuid() with the comment "or custom KSUID validator" and wrote none. So it is the only arm that neither knew the action nor reconstructed it, and substituting uuid() for a 27-character base62 identifier would reject every valid KSUID. (Below the floor by nineteen months.)
missemail TASK 5, THE COLLISION: the mechanism missed entirely and the conclusion wrong at current truth. It read the incident as ordinary case-insensitivity and answered that "email() on the current release does not address the underlying collision - it only validates format", prescribing v.toLowerCase() (which does exist on every rung). It never reached the Unicode folding that both Fable arms named, nor the ASCII-case reading that made v5-c's "yes" survive the release: measured, the task's own scenario is the U+212A case, which passes email() at 1.4.2 and is REJECTED at 1.5.0, and whose lowercase form is a distinct valid address. Its prescription also makes the collision worse in the pre-1.5.0 case it is aimed at — lowercasing U+212A produces ASCII k, which is the collision, so applying toLowerCase() after email() on a pin below 1.5.0 converts two addresses into one stored key rather than preventing it. (The claim that the current release is still exposed is false at 1.5.0, which is nineteen months above this subject's cutoff. The prescription's own defect is a reasoning error rather than a version belief and is covered in the same row.)
context— WHAT THE DEEP CONTROL ANSWERS, AND IT FALSIFIES P7. The arm exists to test whether derivability is reasoning or recency: if nineteen months below the floor scored within one task of three months below it, distance would not be the variable. It did not. On the three tasks that reward derivation this arm reached one of three — task 2 by the symbol route, and even there without the bound the task's JSON.parse framing required — against three of three for each of the arms above it, which independently reconstructed valibot's own 1.5.0 ULID patch and, in two cases, a KSUID validator stricter than the shipped action. It shipped the only non-functioning code in the battery (task 1, a 25-character regex ANDed with a 26-character length check) and the only actively harmful advice (task 5, a transform that completes the collision it is meant to prevent). It matched the other three on exactly two things: every one-word answer, and the poison rung. So the one-word column is worthless as a measure of derivability here — four identical "No"s conceal the whole spread — and the reasoning underneath it separates the subjects cleanly. (A reading of the battery's arm structure, not a belief about the library.)

Sources

Battery specification: prompts/valibot.md in the studio repo. Every finding above also carries its own citation.