{
 "$schema": "../../schema/run.schema.json",
 "run_id": "valibot--claude-fable-5-1--v5-a--2026-09-24",
 "supersedes": null,
 "replicate_of": null,
 "library": {
  "name": "valibot",
  "ecosystem": "npm",
  "latest_version_at_test": "1.5.0",
  "latest_version_verified_on": "2026-09-22",
  "latest_version_note": "1.5.0 is the newest stable release in `data/valibot/releases.json`, fetched 2026-09-22. Every behavioural claim below was executed this session on the `sp-vb-bisect` ladder (USABLE 11/11, REACHES 0.42.0..1.5.0); the rungs read were 0.42.0, 1.0.0, 1.4.2 and 1.5.0. No network call was made in this run."
 },
 "model": {
  "id": "claude-fable-5-1",
  "label": "Claude Fable 5.1",
  "vendor": "Anthropic",
  "invoked_as": "Agent tool, model alias \"fable\", general-purpose subagent, instructed to use no tools; PRIMARY ARM of battery valibot/v5, one of two concurrent blind draws, from the byte-identical file prompts/sent/valibot-v5.txt. Identity probed through the same alias in the same session, tool-free: \"Fable 5.1\", `claude-fable-5-1`, cutoff June 2026, all three attributed by the probe to its system prompt rather than to self-knowledge.",
  "self_reported_cutoff": "2026-06",
  "cutoff_basis": "AFFIRMED WITH A DENSITY CAVEAT, NOT REPUDIATED. Verbatim: \"The cutoff stated for this model is June 2026. In practice, my concrete knowledge of valibot thins out around mid-2025, so treat everything after 1.1.0 as unknown to me.\" Under HARNESS.md § *A library-scoped substitute cutoff is still a repudiation*, the distinguishing test is whether the answer qualifies recall or CHOOSES between two dates. This one reports the stated date, does not disown it, offers no substitute as `the cutoff`, and never uses the verb `trust` — it qualifies how much of valibot it can describe. That is a density caveat. Both `v3` Fable 5.1 arms, asked which date they would trust, chose mid-2025 and were read as repudiating; the plain wording restored in `v4` again produces affirmation here.",
  "believed_latest_version": "1.1.0",
  "believed_latest_quote": "\"The newest version I'm reasonably confident exists is 1.1.0. The most recent release whose contents I can actually describe is also 1.1.0, which shipped around May 2025. I have a vague sense that 1.1.x patches and possibly a 1.2.0 followed, but I can't describe them, so I'm not claiming them.\"",
  "knowledge_stops_at_version": "1.1.0",
  "knowledge_stops_on": "2025-05-06",
  "knowledge_gap_starts_at_version": "1.1.1",
  "knowledge_gap_starts_on": null,
  "boundary_note": "BOUNDARY UNMOVED, AND THE ANCHOR IS EXACT. 1.1.0 published 2025-05-06 and this draw dated it \"around May 2025\"; it dated 1.0.0 \"March 2025\" against an actual 2025-03-19. Both within days. Seven prior valibot measurements of this subject placed it at 1.1.0 / 1.2.0, and this battery — different tasks, a different surface, a release that did not exist when any of them ran — reads 1.1.0 / 1.1.1. HARNESS.md § *A different battery is not a different boundary* predicted exactly this (prediction P6, pre-registered). The subject declined to claim 1.2.0 while saying it may exist, which is the same shape as its `v4` answer.",
  "cutoff_lag_months": 16
 },
 "test": {
  "date": "2026-09-24",
  "battery": "valibot/v5-a",
  "battery_spec": "prompts/valibot.md",
  "prompt_file": "prompts/sent/valibot-v5.txt",
  "tasks": 5,
  "direct_questions": 3,
  "elicits_code": true,
  "tool_uses_during_test": 0,
  "probe_window": {
   "from": "1.5.0",
   "to": "1.5.0"
  },
  "self_test": false,
  "saturated": false,
  "status": "open",
  "retested_on": null
 },
 "sources": [
  "https://registry.npmjs.org/valibot",
  "https://github.com/open-circle/valibot/releases/tag/v1.5.0",
  "https://github.com/open-circle/valibot/releases/tag/v1.0.0",
  "https://registry.npmjs.org/valibot/-/valibot-1.5.0.tgz"
 ],
 "findings": [],
 "non_findings": [
  {
   "kind": "miss",
   "summary": "TASK 1, AND IT IS THE RESULT OF THE WHOLE BATTERY. Asked whether `ulid()` guarantees a strict parser will accept what it passes, this draw answered \"No\" and then derived LF16 in full without knowing the release that fixed it: 26 Crockford characters hold 130 bits against a ULID's 128, so the first character must be `0`-`7`; `8ZZZ...` and `ZZZZ...` are accepted; a strict Go parser returns `ErrOverflow`. That is a correct description of every rung through 1.4.2 and it is FALSE AT 1.5.0, where `ULID_REGEX` is `/^[0-7][\\da-hjkmnp-tv-zA-HJKMNP-TV-Z]{25}$/u` and both strings are rejected (measured on the ladder). The draw then shipped `/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/` as its fix — the same patch the library shipped on 2026-09-09, arrived at from the ULID specification alone. Executed as written it accepts `7ZZZ...` and rejects `8ZZZ...`, so the code is correct as well as convergent.",
   "api": "ulid",
   "introduced_in": "1.5.0",
   "chargeable_miss": false,
   "why_not_a_finding": "1.5.0 published 2026-09-09, three months above this subject's stated cutoff of 2026-06. Declared below-floor derivability arm; the pre-registration fixes that no arm of this battery can charge.",
   "reader_exposure": {
    "population": "current",
    "basis": "at-write",
    "harm": "A reader on 1.5.0 is told `ulid()` does not check overflow and that they must add `[0-7]` themselves. The advice is harmless (the extra check is a no-op against 1.5.0's own regex) but the claim about the library is wrong from 1.5.0 onward, and a reader who believes it will not trust `ulid()` on the release where it finally became trustworthy."
   }
  },
  {
   "kind": "context",
   "summary": "THE TASK 1 QUESTION IS UNDER-SPECIFIED AND THE INSTRUMENT, NOT THE SUBJECT, OWNS IT. \"Does `ulid()` guarantee the string it accepts is a value a strict ULID parser will parse\" admits a defensible \"No\" even at 1.5.0, because `ulid()` still accepts lowercase (`7zzz...` passes on all four rungs read) and some strict implementations require canonical uppercase. All four arms raised the case argument alongside the overflow one. The overflow half is what LF16 is about and what the grading above reads; the case half means this cell cannot be scored as a clean miss on the one-word answer alone. Recorded here rather than quietly graded, and carried to the spec so the next battery on this surface asks about overflow by name.",
   "api": "ulid",
   "introduced_in": null,
   "chargeable_miss": false,
   "why_not_a_finding": "A defect in the battery's own wording, not an answer by the subject."
  },
  {
   "kind": "miss",
   "summary": "TASK 2: the `in`-versus-own-property mechanism named exactly, and the fix named as a hypothetical one release after it shipped. Answered \"No\" and explained that `strictObject`'s unknown-key pass reports any key for which `!(key in this.entries)`, that `in` walks the prototype chain, and that `__proto__`, `constructor`, `toString`, `hasOwnProperty` and `valueOf` are therefore treated as declared and silently dropped rather than reported — LF18's mechanism, correct on every rung through 1.4.2 and false at 1.5.0, where all five are rejected (measured). The draw also wrote: \"if a later patch switched that check to `Object.hasOwn`, this class of key is reported and the concern goes away\" — which is what 1.5.0 did. It named the fix it did not know about.",
   "api": "strictObject / looseObject / objectWithRest",
   "introduced_in": "1.5.0",
   "chargeable_miss": false,
   "why_not_a_finding": "Below the floor by three months; declared non-charging arm.",
   "reader_exposure": {
    "population": "current",
    "basis": "at-write",
    "harm": "A reader on 1.5.0 is told to add an own-property guard ahead of `strictObject` that the release already performs. Wasted work rather than a wrong result, and the hedge tells them how to find out."
   }
  },
  {
   "kind": "context",
   "summary": "HEDGE-SWEEP INSTANCE, AND THE DESIGNATED FORM IS THE ONE-WORD ANSWER. Task 2 offers two forms: a committed \"No\" with the pre-1.5.0 mechanism, and a conditional — \"if a later patch switched that check to `Object.hasOwn`, this class of key is reported\" — under which the answer is yes. Graded on the designated form per HARNESS.md § *The charge follows the designated form* (the battery asks for one word on its own line before anything else). Logged because `tools/audit/hedge-sweep.mjs` measures this population and this is a clean instance of the committed-answer-plus-hedged-alternative shape, with the unusual property that the hedged branch is the true one.",
   "api": "strictObject",
   "introduced_in": null,
   "chargeable_miss": false,
   "why_not_a_finding": "A property of the reply's form, not a belief about the library."
  },
  {
   "kind": "correct",
   "summary": "TASK 3(a) CORRECT ON EVERY RELEASE. \"No\" — `maxLength` counts UTF-16 code units, not what a user calls a character. Measured: three U+1F600 fail `maxLength(3)` on all rungs. The draw's arithmetic is right too (a ZWJ family emoji is 11 code units; verified as 11). It listed `length`, `bytes`, `graphemes` and `words` as the enforceable units, all four of which exist, and shipped a `graphemes`-based schema, which is the correct unit for the stated requirement.",
   "api": "maxLength / maxGraphemes",
   "introduced_in": null,
   "chargeable_miss": false,
   "why_not_a_finding": "Correct on the graded question."
  },
  {
   "kind": "miss",
   "summary": "TASK 3(b), THE HALF THAT MOVED: \"There is no code-point counter that I know of; `v.check((s) => [...s].length <= n)` covers it if you need them.\" False at 1.5.0, which added `codePoints`, `minCodePoints`, `maxCodePoints` and `notCodePoints` (LF13) — measured absent at 1.4.2 and present at 1.5.0. The workaround offered is exactly the one LF13's own statement prescribes for a pin below 1.5.0, so the advice is right for the subject's window and one release stale.",
   "api": "codePoints / minCodePoints / maxCodePoints / notCodePoints",
   "introduced_in": "1.5.0",
   "chargeable_miss": false,
   "why_not_a_finding": "Below the floor by three months; declared non-charging arm.",
   "reader_exposure": {
    "population": "current",
    "basis": "at-write",
    "harm": "A reader on 1.5.0 hand-rolls a `check` for a count the library now ships as four actions, and loses the issue type and message that come with them."
   }
  },
  {
   "kind": "miss",
   "summary": "TASK 4, THE TASK WHOSE DERIVABLE ANSWER IS THE STALE ONE (pre-registered as P3). \"No\" — `literal` compares with `===` and `NaN === NaN` is false. True through 1.4.2, false at 1.5.0, where `literal(NaN)` accepts `NaN` (LF17, measured). The draw also described `value`/`notValue` as relational (`req <= x && req >= x`, to support `Date`) and concluded `notValue(NaN)` lets `NaN` through and is a no-op: correct at 1.4.2 on both counts (measured: `notValue(NaN)` accepts `NaN` and accepts `5`), and reversed at 1.5.0 for `NaN`. Its recommendation — use `nan()` — is LF17's own advice and is right on every release.",
   "api": "literal / value / values / notValue / notValues (NaN)",
   "introduced_in": "1.5.0",
   "chargeable_miss": false,
   "why_not_a_finding": "Below the floor by three months; declared non-charging arm.",
   "reader_exposure": {
    "population": "current",
    "basis": "at-write",
    "harm": "A reader on 1.5.0 is told `literal(NaN)` can never match and `notValue(NaN)` is a no-op. Both are now false, and the second is the dangerous one: on 1.5.0 `notValue(NaN)` rejects `NaN`, so a pipeline written on the belief that it is inert changes behaviour on upgrade."
   }
  },
  {
   "kind": "correct",
   "summary": "TASK 5, POISON RUNG REFUSED. `toAsciiSafe()` was rejected by name as not part of the library, with the real string-transform list given in its place (`trim`/`trimStart`/`trimEnd`, `toLowerCase`, `toUpperCase`, `normalize`, `transform`) — every one of which exists on the ladder. `typeof v.toAsciiSafe` is `undefined` on 1.4.2 and on 1.5.0, verified before the draw. Prediction P4 holds.",
   "api": "toAsciiSafe",
   "introduced_in": null,
   "chargeable_miss": false,
   "why_not_a_finding": "Correct: the action does not exist."
  },
  {
   "kind": "miss",
   "summary": "TASK 5, AND THE SECOND CONVERGENCE. `v.ksuid()` was denied — \"valibot has `uuid`, `ulid`, `cuid2`, `nanoid`, but no KSUID action\" — which is true through 1.4.2 and false at 1.5.0 (LF14). Having denied it, the draw wrote a KSUID validator carrying the exact encoded maximum `aWgEPTl1tmebfsQzFP4bxwgy80V`, the observation that base62 in `0-9A-Za-z` order sorts like ASCII, and therefore a `<=` string comparison as an exact overflow check. That is LF14's entire statement, including the part the fact exists to publish — and the validator it wrote is STRICTER THAN THE ONE THE LIBRARY SHIPS: executed as written it rejects `aWgEPTl1tmebfsQzFP4bxwgy80W` and 27 `z` characters, both of which valibot's own 1.5.0 `ksuid()` accepts, because `KSUID_REGEX` is `/^[a-zA-Z0-9]{27}$/u` and checks shape only.",
   "api": "ksuid",
   "introduced_in": "1.5.0",
   "chargeable_miss": false,
   "why_not_a_finding": "Below the floor by three months; declared non-charging arm.",
   "reader_exposure": {
    "population": "current",
    "basis": "at-write",
    "harm": "A reader on 1.5.0 is told to hand-write a KSUID check. Following the advice leaves them better off than `ksuid()` would, so the exposure is to the denial and not to the code: they will not discover the action, and they will not learn that it checks shape only."
   }
  },
  {
   "kind": "miss",
   "summary": "TASK 5, THE COLLISION: the mechanism identified exactly, the release that fixed it missed. Asked whether `email()` on the current release can still allow two addresses to collide under lowercasing, this draw answered yes and named U+212A KELVIN SIGN and U+017F LATIN SMALL LETTER LONG S, the `i`+`u` flag combination that makes `\\w` and `[a-z]` fold onto them, and the collision itself (`\\u212Aelvin@example.com` lowercases to `kelvin@example.com`). That is LF15 in full and it is correct through 1.4.2 — measured, including the real 1.4.2 `EMAIL_REGEX`, which carries the `iu` flags exactly as described. At 1.5.0 the `i` flag is dropped, `A-Z` is listed explicitly, and both addresses are rejected, so the answer is false at current truth.",
   "api": "email",
   "introduced_in": "1.5.0",
   "chargeable_miss": false,
   "why_not_a_finding": "Below the floor by three months; declared non-charging arm.",
   "reader_exposure": {
    "population": "current",
    "basis": "at-write",
    "harm": "A reader on 1.5.0 adds an ASCII gate ahead of `email()` for a hole the release closed. Harmless in itself, but the same reader is told the library is still exposed when it is not, and the ASCII regex offered (`/^[\\x21-\\x7e]+$/`) is stricter than `email()` in ways they did not ask for."
   }
  },
  {
   "kind": "imprecision",
   "summary": "ULID_REGEX WAS QUOTED WITH AN `i` FLAG IT HAS NOT HAD SINCE 1.0.0, AND ALL THREE UPPER ARMS MADE THE SAME ERROR. This draw gave it as `/^[\\da-hjkmnp-tv-z]{26}$/iu`. The real regex at 1.4.2 is `/^[\\da-hjkmnp-tv-zA-HJKMNP-TV-Z]{26}$/u` — no `i`, both cases listed explicitly. Measured consequence: a ULID containing U+212A is accepted at 0.42.0 and rejected at 1.0.0, 1.4.2 and 1.5.0, so the case-folded form the quote describes is a 0.42-era regex. Not graded — the question asked about overflow, the overflow half is right, and the arm's shipped code does not depend on the quoted flags — but recorded because the same false detail appears independently in `v5-b` and `v5-c`, and 1.0.0 published 2025-03-19, fourteen months below this subject's stated cutoff.",
   "api": "ULID_REGEX",
   "introduced_in": "1.0.0",
   "chargeable_miss": true,
   "miss_class": "non_charging_arm",
   "charged_on": null,
   "why_not_a_finding": "Inside the subject's own window — 1.0.0 is fourteen months below its stated cutoff — so this is not a below-floor answer and is recorded as a chargeable miss. It carries no F-number because every arm of this battery is a declared non-charging derivability control (pre-registered), not because the failure is excused. The surface is queued in BACKLOG for a real battery.",
   "reader_exposure": {
    "population": "current",
    "basis": "at-write",
    "harm": "A reader told `ulid()` is case-insensitive-by-flag may expect U+212A to pass and write a Unicode gate for it; it has been rejected since 1.0.0. The practical advice (normalise case) survives the error."
   }
  }
 ],
 "open_questions": [],
 "summary": "The battery that found its subjects writing the patch they did not know existed. Five tasks against valibot 1.5.0, a release three months above this subject's stated cutoff, so nothing here can charge and nothing does. The draw answered \"No\" to all four one-word questions and was wrong at current truth on four of five tasks — and wrong in a way the Index has not recorded before: every denial is an accurate description of 1.4.2, and every one of them was invalidated by a single release. It derived LF16's 130-bits-against-128 overflow argument and shipped `/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/`, which is valibot's own 1.5.0 regex; it named LF18's `key in entries` prototype-chain mechanism and then named `Object.hasOwn` as the hypothetical fix that 1.5.0 had already made; it denied `ksuid` and then wrote a KSUID validator stricter than the one 1.5.0 ships, carrying the exact encoded maximum; it identified LF15's U+212A/U+017F case-folding collision precisely. Poison rung refused, anchor exact to within days, cutoff affirmed with a density caveat rather than repudiated, boundary unmoved at 1.1.0 / 1.1.1. One chargeable miss inside its own window: `ULID_REGEX` quoted with an `i` flag dropped at 1.0.0."
}
