{
  "$schema": "../../schema/run.schema.json",
  "run_id": "valibot--claude-fable-5--v2-e--2026-09-02",
  "supersedes": null,
  "replicate_of": null,
  "library": {
    "name": "valibot",
    "ecosystem": "npm",
    "latest_version_at_test": "1.4.2",
    "latest_version_verified_on": "2026-09-02"
  },
  "model": {
    "id": "claude-fable-5",
    "label": "Claude Fable 5",
    "vendor": "Anthropic",
    "invoked_as": "Agent tool, model alias \"fable\", general-purpose subagent, instructed to use no tools; CHARGING test arm of battery valibot/v2, sent prompts/sent/valibot-v2.txt byte-identical",
    "self_reported_cutoff": "2026-01",
    "cutoff_basis": "Self-reported, affirming the environment value with an explicit density caveat: \"My training cutoff is January 2026. Note the practical gap: despite that cutoff, my detailed valibot knowledge thins out around mid-2025 — a later cutoff doesn't guarantee dense coverage of every library's last few releases.\"",
    "believed_latest_version": "1.1.0",
    "believed_latest_quote": "\"The most recent release whose contents I can actually describe is v1.1.0 (roughly April–May 2025). I believe patch releases followed (v1.1.x) and it's plausible a v1.2 exists by now, but I cannot describe the contents of anything after 1.1.0.\"",
    "knowledge_stops_at_version": "1.1.0",
    "knowledge_stops_on": "2025-05-06",
    "knowledge_gap_starts_at_version": "1.2.0",
    "knowledge_gap_starts_on": "2025-11-24",
    "cutoff_lag_months": 8
  },
  "test": {
    "date": "2026-09-02",
    "battery": "valibot/v2-e",
    "battery_spec": "prompts/valibot.md",
    "prompt_file": "prompts/sent/valibot-v2.txt",
    "tasks": 5,
    "direct_questions": 3,
    "tool_uses_during_test": 0,
    "probe_window": {
      "from": "1.1.0",
      "to": "1.2.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.1.0",
    "https://github.com/open-circle/valibot/releases/tag/v1.2.0",
    "https://github.com/open-circle/valibot/releases/tag/v1.3.0",
    "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz"
  ],
  "findings": [
    {
      "id": "F1",
      "severity": "S2",
      "severity_label": "silently-wrong",
      "title": "Denies that valibot ships built-in string-to-primitive conversion actions, and rejects a working pull request that uses them as fabricated",
      "api": "toNumber / toBoolean / toDate / toBigint / toString",
      "change_kind": "added",
      "introduced_in": "1.2.0",
      "introduced_on": "2025-11-24",
      "chargeable": true,
      "chargeable_note": "valibot 1.2.0 published 2025-11-24, inside this subject's stated cutoff. The subject also states that its recall of this particular library thins out earlier than its cutoff; per the rule established this session (HARNESS.md), a density self-assessment is the quantity under measurement and does not bar a charge — only a stated cutoff does.",
      "model_belief": "\"It does not compile. `v.toNumber` and `v.toBoolean` do not exist in valibot — not in the current release, and to my knowledge they never existed in any release ... valibot deliberately has no coercion helpers and routes all type conversion through `v.transform()`.\" Task 1 answered \"No\" and \"No\", adding that valibot's `to*` actions \"never change the data type\".",
      "wrong_code": "// The replacement this draft would have demanded on a working pull request:\nconst Query = v.object({\n  page: v.pipe(v.string(), v.digits(), v.transform(Number)),\n  active: v.pipe(v.picklist(['true', 'false']), v.transform((s) => s === 'true')),\n})",
      "correct_code": "import * as v from 'valibot'\n\n// Both actions exist. Executed against valibot@1.4.2:\n//   v.parse(Query, { page: '3', active: 'false' })  ->  { page: 3, active: true }\nconst Query = v.object({\n  page: v.pipe(v.string(), v.toNumber()),\n  active: v.pipe(v.string(), v.toBoolean()),\n})\n\n// On the number half the built-in is the safer form: toNumber raises a validation\n// issue on NaN, where a bare v.transform(Number) returns NaN with success: true.\n// On the boolean half the reviewer's own replacement is the better code — toBoolean\n// is Boolean(), so it maps \"false\" to true. parseBoolean (1.3.0) reads the words.",
      "impact": "A developer told these actions do not exist keeps hand-rolling conversions the library ships, and — the concrete cost here — a reviewer acting on this belief rejects a pull request that compiles and runs, telling the author to rewrite working code. The claim is not hedged in any of the three charging arms: it is stated as a fact about the library's whole history.",
      "citations": [
        {
          "url": "https://github.com/open-circle/valibot/releases/tag/v1.2.0",
          "title": "valibot v1.2.0 release notes",
          "published_on": "2025-11-24",
          "quote": "Add `toBigint`, `toBoolean`, `toDate`, `toNumber` and `toString` transformation actions"
        },
        {
          "url": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz",
          "title": "valibot 1.4.2, shipped package — toNumber is exported and raises an issue on NaN",
          "published_on": "2026-06-28",
          "quote": "function toNumber(message) { return { kind: \"transformation\", type: \"to_number\", reference: toNumber, async: false, message, \"~run\"(dataset, config) { try { dataset.value = Number(dataset.value); if (isNaN(dataset.value)) { _addIssue(this, \"number\", dataset, config); dataset.typed = false; } }"
        }
      ],
      "scope_note": "Charged on the absence claim only, never on the code. The battery's binding namespace exemption stands: \"valibot has no v.coerce namespace\" and \"the generic coerce method was removed at 0.31.0\" are both CORRECT and are not part of this finding. The subject's own replacement code was executed and works. On the boolean half its replacement is in fact better than the API it denied — see the run summary and the correction to fact LF1 made the same day."
    }
  ],
  "non_findings": [
    {
      "kind": "correct",
      "summary": "Task 4, the attribution anchor: \"`v.parseJson()` ... shipped in v1.1.0, roughly April–May 2025 ... I'm confident about the feature and its post-1.0 timing; moderately confident on the exact version number being 1.1.0.\" Correct minor and a date range containing the real one (2025-05-06). Anchor placed; this arm is read for attribution.",
      "api": "parseJson / stringifyJson",
      "introduced_in": "1.1.0"
    },
    {
      "kind": "correct",
      "summary": "Gave the removal history correctly — \"Early valibot (v0.x before mid-2024) had a `coerce` method ... removed because it ran before validation and was type-unsafe\" — and, in the same breath as denying `toBoolean` exists, described precisely what it does: \"even if `toBoolean` existed, [it] would need defined semantics — `Boolean('false')` is `true` in JS\". Verified: that is the shipped implementation.",
      "api": "toBoolean",
      "introduced_in": "1.2.0"
    },
    {
      "kind": "imprecision",
      "summary": "Task 5 answered \"Yes\" via `v.metadata()`/`v.getMetadata()`, with the caveat \"to my knowledge, there is no dedicated `v.examples()` action\" and the hedge \"If a dedicated `examples` action was added in a very recent release, that would be past what I can reliably describe\". `v.examples()` shipped in 1.2.0 — the release this draw names as the first it cannot describe, so the hedge is exactly calibrated.",
      "api": "examples / getExamples",
      "introduced_in": "1.2.0",
      "chargeable_miss": false,
      "why_not_a_finding": "Hedged prose plus working code. Worth reading beside F1: the same draw hedged correctly about a 1.2.0 addition here and asserted the non-existence of a different 1.2.0 addition flatly, one task apart."
    }
  ],
  "summary": "Charging arm. Denied both actions in task 1, called them non-existent \"in any release\" in task 2, and charged F1 (S2). The anchor was placed correctly at 1.1.0, giving this subject the tightest possible bracket — it can describe 1.1.0 and denies the contents of 1.2.0, the very next minor. The most interesting line in the run is in task 5, where the same draw hedged accurately about a different 1.2.0 addition on the explicit ground that 1.2.0 is past what it can describe. It had the boundary right and applied it in one place and not the other."
}
