{
 "$schema": "../../schema/run.schema.json",
 "run_id": "zod--claude-fable-5-1--v7-a--2026-09-22",
 "supersedes": null,
 "replicate_of": null,
 "library": {
  "name": "zod",
  "ecosystem": "npm",
  "latest_version_at_test": "4.6.5",
  "latest_version_verified_on": "2026-09-22",
  "latest_version_note": "`registry.npmjs.org/zod` was read this session: `dist-tags.latest` is 4.6.5 and the 4.x line holds 56 stable releases. Every behavioural claim graded below was re-executed against published packages on the `sp-zod-bisect` ladder rather than inferred from release notes — tasks 1 and 2 on the eight rungs from 4.2.0 (the converter's introducing release) to 4.6.2, task 3 on all seventeen 4.x rungs. 4.6.3 through 4.6.5 are patch releases published after `data/zod/releases.json` was last fetched and are NOT installed here, so nothing in this run is executed above 4.6.2 and no claim is made about those three."
 },
 "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; CHARGING TEST ARM of battery zod/v7. Prompt sent verbatim from prompts/sent/zod-v7.txt. Identity probed through the same alias in the same session, tool-free: \"Fable 5.1\", `claude-fable-5-1`, cutoff June 2026 — and the probe volunteered that all three came from its system prompt rather than from self-knowledge, adding that its own internal sense of its cutoff is \"earlier and fuzzier than that\". Same answer, same wording of the caveat, as the identity probe stored with zod/v6.",
  "self_reported_cutoff": "2026-06",
  "cutoff_basis": "Self-reported and AFFIRMING, with a density qualification scoped to recall: \"My nominal training cutoff is June 2026, but for a fast-moving library my *reliable* knowledge ends earlier — for Zod, around 4.2 (late 2025).\" No substitute date is offered as the cutoff, so this is not the JOURNAL/055 repudiation.",
  "believed_latest_version": "4.2.0",
  "believed_latest_quote": "\"The most recent release I can describe the contents of is 4.2.0, roughly December 2025 (possibly early January 2026). I have a weak sense that a 4.3 line exists (early 2026?) but nothing attached to it ... it's a lower bound: a library on Zod's cadence has almost certainly shipped things I haven't seen.\"",
  "knowledge_stops_at_version": "4.2.0",
  "knowledge_stops_on": "2025-12-15",
  "knowledge_gap_starts_at_version": "4.3.0",
  "knowledge_gap_starts_on": "2025-12-31",
  "boundary_note": "Self-placed and demonstrated at the same release, which is not the usual case for this subject. It named 4.2.0 at (a) and (d), described 4.2.0's headline content at (b) (`z.fromJSONSchema()`), and used that API unprompted in task 1. zod/v6 measured the same boundary on the same subject sixteen days earlier from a different prompt file, so this is the second battery running to put Claude Fable 5.1's zod knowledge at 4.2.0. (The ladder battery zod/v5 put it one lower, at 4.1.0; zod/v6 moved it to 4.2.0 on demonstrated APIs and this run agrees.)",
  "cutoff_lag_months": 6
 },
 "test": {
  "date": "2026-09-22",
  "battery": "zod/v7-a",
  "battery_spec": "prompts/zod.md",
  "prompt_file": "prompts/sent/zod-v7.txt",
  "tasks": 3,
  "direct_questions": 4,
  "elicits_code": true,
  "tool_uses_during_test": 0,
  "probe_window": {
   "from": "4.2.0",
   "to": "4.6.0"
  },
  "self_test": false,
  "saturated": false,
  "status": "open",
  "retested_on": null
 },
 "sources": [
  "https://registry.npmjs.org/zod",
  "https://github.com/colinhacks/zod/releases/tag/v4.4.0",
  "https://github.com/colinhacks/zod/releases/tag/v4.6.0",
  "https://github.com/colinhacks/zod/pull/6535"
 ],
 "findings": [
  {
   "id": "F1",
   "severity": "S2",
   "severity_label": "silently-wrong",
   "title": "Asserts that converting the partner's document throws on its uniqueItems and minProperties keywords, so no payload is ever checked - the converter accepts that document without a word on every release it has ever existed in",
   "api": "z.fromJSONSchema(): which JSON Schema keywords throw and which convert silently",
   "change_kind": "invariant",
   "introduced_in": null,
   "introduced_on": null,
   "chargeable": true,
   "chargeable_note": "An invariant finding: the belief is false on every measured release, so there is no introducing release for a stated cutoff to sit below and none of the fairness arithmetic applies (HARNESS, JOURNAL/046). Executed on all six rungs that carry the converter, 4.2.0 through 4.6.2: `z.fromJSONSchema()` returns a schema for this document on every one of them and throws on none.",
   "model_belief": "\"Both `uniqueItems` and `minProperties` are, as I remember the 4.2 docs, on the unsupported list (alongside things like `if/then/else`, `dependentSchemas`, `unevaluatedProperties`, `contains`). I don't recall an option to downgrade that to a warning.\" And, as the graded answer to (i), (ii) and (iii): \"none is accepted or rejected. `compileDocument` throws before any payload is looked at, with an error naming the unsupported keyword (`uniqueItems` first, since it's encountered first).\"",
   "wrong_code": "export function compileDocument(doc: unknown) {\n  // Throws synchronously if the document uses a keyword Zod can't represent.\n  return z.fromJSONSchema(doc as z.core.JSONSchema.BaseSchema);\n}   // asserted to throw on this document; it returns a schema on all six rungs",
   "correct_code": "// The converter is silent about these two keywords below 4.6.0, so the caller must add them.\nconst base = z.fromJSONSchema(doc);   // does NOT throw for uniqueItems / minProperties\nconst schema = base.superRefine((val, ctx) => {\n  const tags = (val as any)?.tags;\n  if (Array.isArray(tags) && new Set(tags.map((x) => JSON.stringify(x))).size !== tags.length)\n    ctx.addIssue({ code: \"custom\", path: [\"tags\"], message: \"items must be unique\" });\n  const meta = (val as any)?.meta;\n  if (meta && typeof meta === \"object\" && Object.keys(meta).length < 2)\n    ctx.addIssue({ code: \"custom\", path: [\"meta\"], message: \"expected at least 2 properties\" });\n});\n// From 4.6.0 the converter enforces both itself and the superRefine is redundant, not wrong.",
   "impact": "The belief is not that a call fails - it is that the converter is FAIL-LOUD, and that is the property a reader builds the integration on. Told that an unrepresentable keyword throws at conversion time, a caller wraps `compileDocument` in a try/catch, treats a throw as \"this partner document is out of scope\", and ships nothing else. On 4.2.0 through 4.5.4 the document converts cleanly and the resulting validator accepts `{ tags: [\"a\", \"a\"] }` and `{ meta: { x: 1 } }` - exactly the two payloads the partner's contract rejects - with no error, no warning and no throw to catch. The failure is silent at the only point where it could have been noticed.",
   "citations": [
    {
     "url": "https://registry.npmjs.org/zod",
     "title": "Executed against the published packages (zod/v7 grading run, 2026-09-22)",
     "published_on": "2026-09-22",
     "quote": "z.fromJSONSchema({ type: \"object\", properties: { tags: { type: \"array\", items: { type: \"string\" }, uniqueItems: true }, meta: { type: \"object\", minProperties: 2 } }, required: [\"tags\"] }) returns a schema and throws on no rung of 4.2.0, 4.3.6, 4.4.3, 4.5.4, 4.6.0, 4.6.2."
    },
    {
     "url": "https://github.com/colinhacks/zod/releases/tag/v4.6.0",
     "title": "Zod 4.6.0 release notes",
     "published_on": "2026-09-09",
     "quote": "enforces six validation keywords it used to ignore"
    }
   ],
   "scope_note": "THE MECHANISM IS REAL AND THE LIST IS WRONG, which is why this is charged as a belief about behaviour rather than as an invention of a feature. Executed on all six converter rungs: `if/then/else` THROWS (\"Conditional schemas (if/then/else) are not supported\"), `dependentSchemas` THROWS, `unevaluatedProperties` THROWS - three of the five keywords this draw names, exactly as it describes, on every release from 4.2.0 to 4.6.2. `uniqueItems`, `minProperties` and `contains` - the other two it names, plus `patternProperties` - convert silently on all six. So the draw correctly recalls that `z.fromJSONSchema()` has a throw-on-unsupported policy and wrongly places inside it the two keywords the document in front of it actually uses. The charge is on the consequence it states for THIS document, which is false at every release. Its own recommended rewrite - strip the unsupported keywords, record them, re-check them in a `superRefine` - produces the right answers to (i), (ii) and (iii) and is not what is charged; the detector it relies on to know what to strip is the throw that does not happen. Its blind twin `zod/v7-b`, sent the byte-identical file, believed the opposite (\"drops keywords it can't represent rather than throwing\") and was right. Run single, this arm would have published an invention its twin does not hold."
  },
  {
   "id": "F2",
   "severity": "S2",
   "severity_label": "silently-wrong",
   "title": "States that an object schema with a coerced string field accepts a missing key and parses it to the string \"undefined\" - true for eleven releases and false since 4.4.0",
   "api": "z.coerce.*",
   "change_kind": "stricter",
   "introduced_in": "4.4.0",
   "introduced_on": "2026-04-29",
   "chargeable": true,
   "chargeable_note": "4.4.0 (2026-04-29) precedes this subject's stated cutoff of 2026-06 by one month, and is two minors above its measured boundary of 4.2.0. zod 4.4.0 x Claude Fable 5.1 was the one live uncharged cell this library still showed on `charge-windows.mjs`; the other three drawable subjects were already charged at this release. Bisected this session across all seventeen 4.x rungs: 4.0.0 through 4.3.6 succeed, 4.4.0 through 4.6.2 fail, contiguous.",
   "model_belief": "\"On the Zod 4 releases whose behaviour I can actually describe (4.0 through 4.2): `z.coerce.string()` does `String(input)` unconditionally, `String(undefined)` is `\\\"undefined\\\"`, and the object parser runs each field's schema against `input[key]` even when the key is absent. So: `result.success === true`; `result.data` is `{ search: \\\"undefined\\\" }`.\"",
   "wrong_code": "const Query = z.object({\n  search: z.coerce.string(),\n  verbose: z.boolean().optional(),\n});\nQuery.safeParse({});   // asserted success: true, data { search: \"undefined\" }",
   "correct_code": "// From 4.4.0 the parse FAILS: one invalid_type issue at path [\"search\"].\n// Declare the fallback rather than relying on coercion to invent one:\nconst Query = z.object({\n  search: z.coerce.string().default(\"\"),\n  verbose: z.boolean().optional(),\n});\nQuery.safeParse({});   // success: true, data { search: \"\" }, on every 4.x release",
   "impact": "The belief and the truth are both quiet, and they fail in opposite directions. A handler written on this belief expects `safeParse({})` to succeed and to hand it a string; from 4.4.0 it gets `success: false` and takes the error branch, so a request that used to be served is rejected with a validation error naming a field the caller never sent. Nothing throws and nothing logs a version problem - the endpoint simply starts 400ing on a shape it accepted before the upgrade. The draw's own recommended rewrite (`z.string().optional()`, `z.stringbool()`) is correct on every release, so a reader who follows the final snippet is safe and a reader who follows the stated behaviour of the module in front of them is not.",
   "citations": [
    {
     "url": "https://zod.dev/v4/changelog",
     "title": "Zod v4 changelog (the same primary source data/zod/facts.json LF26 cites; no sentence in it is quoted here because none was verified this session)"
    },
    {
     "url": "https://registry.npmjs.org/zod",
     "title": "Executed against the published packages (zod/v7 grading run, 2026-09-22)",
     "published_on": "2026-09-22",
     "quote": "z.object({ search: z.coerce.string(), verbose: z.boolean().optional() }).safeParse({}) succeeds with { search: \"undefined\" } on 4.0.0, 4.0.17, 4.1.0, 4.1.6, 4.1.13, 4.2.0, 4.2.1, 4.3.0, 4.3.1, 4.3.2 and 4.3.6, and fails with one invalid_type issue at [\"search\"] on 4.4.0, 4.4.3, 4.5.0, 4.5.4, 4.6.0 and 4.6.2."
    }
   ],
   "scope_note": "THE DRAW HEDGED AND THE HEDGE NAMED THE CORRECT ANSWER, which is the JOURNAL/052 hedged-demonstration case and is disclosed rather than folded into the grade. After committing to `success: true`, it added: \"If one exists in a release newer than I know, the result would instead be `success: false` with one issue: `{ code: \\\"invalid_type\\\", expected: \\\"string\\\", path: [\\\"search\\\"] }`\" - which is the exact shape of the real 4.4.0 result, issue code and path included. The battery's P3 was pre-registered as falsified only by an arm STATING the parse fails; this arm states it succeeds and offers the correct behaviour as a conditional it does not believe, so the charge stands under the rule as written. Both readings are published and neither overwrites the other. Its blind twin `zod/v7-b` gave the same verdict with the same hedge, so this is the subject's answer and not a draw. The below-floor derivability control `zod/v7-c` (Claude Haiku 4.5, stated cutoff 2025-02) got this RIGHT - see the non_findings - which under JOURNAL/031 discounts a pass and never excuses a failure."
  }
 ],
 "non_findings": [
  {
   "kind": "context",
   "summary": "P2 IS FALSIFIED IN THE DIRECTION NOBODY REGISTERED, AND IT IS THE RESULT OF THE BATTERY: THE SUBJECT IS WRONG ABOUT EVERY RELEASE IT CAN KNOW AND RIGHT ABOUT THE ONE IT CANNOT. Task 2's dropdown was predicted to be built by filtering `.options` for strings, which yields `[]` from 4.6.0. Neither twin did that. Both took the VALUES from `.options` and the labels from the enum's reverse mapping - `CountrySchema.options.map((value) => ({ value, label: Country[value] }))` - and both asserted, unprompted, that `.options` is already filtered: \"Zod 4's z.enum filters out the numeric-key reverse mappings, so .options is the clean value list\" (-b), \"Using Object.entries(Country) directly would be wrong here - CountrySchema.options is already filtered\" (-a). That is a precise description of zod 4.6.0, published 2026-09-09, three months after this subject's stated cutoff. Executed: on 4.2.0, 4.3.6, 4.4.3 and 4.5.4 the twins' code produces SIX options, three of which ({ value: \"UK\", label: 0 }, { value: \"Germany\", label: 1 }, { value: \"France\", label: 2 }) the schema itself REJECTS; on 4.6.0 and 4.6.2 it produces exactly the three they claim, element for element, and the schema rejects none of them.",
   "api": "z.enum() / z.nativeEnum() over a numeric TypeScript enum: .options",
   "introduced_in": "4.6.0",
   "chargeable_miss": false,
   "why_not_a_finding": "The Index charges divergence from CURRENT truth, and against current zod there is none: the code is right, the stated output is right, the dropdown works. This is the inverse shape of a stale prior - a belief false for every release the subject can know and true of the library as it stands - and the corpus has no rule for it. Inventing that rule inside the session that would benefit from a third finding is the wrong order (the JOURNAL/046 precedent). Queued in BACKLOG as a rule question, with the reader's exposure stated plainly: anyone pinned to 4.2.x-4.5.x who ships this code gets a select control whose first three entries fail validation on submit.",
   "reader_exposure": {
    "population": "pinned-below",
    "basis": "post-hoc",
    "harm": "Builds a dropdown off .options asserting it is already filtered. On 4.2.0-4.5.4 it yields six options, three of which the schema rejects on submit; exactly three on 4.6.0."
   }
  },
  {
   "kind": "context",
   "summary": "P1 IS SPLIT BETWEEN THE TWINS, WHICH IS WHY THE ARM IS DUPLICATED. The prediction was that both test draws would describe payload (i) as accepted - the correct pre-4.6 answer - and that neither would add its own uniqueness check. `-b` did exactly that and was right about 4.2.0 through 4.5.4. `-a` instead asserted the conversion throws, which is false at every release, and is charged for it as F1. One stored file, two concurrent blind draws, and the two arms hold contradictory beliefs about the same API's failure mode. HARNESS's case for duplicating the test arm is that the self-report is the half that moves; this is the first pair in the corpus where the halves that moved were two incompatible accounts of a library MECHANISM, and the run that would have been published had `-a` run single says the subject believes something its twin demonstrably does not.",
   "api": "z.fromJSONSchema() with minProperties / maxProperties / uniqueItems / contains / minContains / maxContains",
   "chargeable_miss": false,
   "why_not_a_finding": "A property of the instrument. The charge it bears is F1; this records what the duplication bought."
  },
  {
   "kind": "context",
   "summary": "P4 HOLDS: both blind twins state 2026-06, as this subject has on every stored run. The charge in F2 therefore clears the fairness bar on both readings - 4.4.0 published 2026-04-29, one month inside the stated cutoff - and no split has to be adjudicated. Both twins also volunteered the same density qualification in the same shape (\"reliable knowledge ends earlier - around 4.2\" / \"usable knowledge thins out around late 2025 / very early 2026\"), scoped to recall rather than offered as a substitute cutoff, so neither is the JOURNAL/055 repudiation.",
   "chargeable_miss": false,
   "why_not_a_finding": "A pre-registered prediction that held."
  },
  {
   "kind": "context",
   "summary": "P5 HOLDS AND THE POISON RUNG IS ONLY HALF-INFORMATIVE HERE. 4.4.9 has never been published (verified against registry.npmjs.org this session before the prompt was written: the 4.4 line is 4.4.0, 4.4.1, 4.4.2, 4.4.3). All three arms declined to describe it - \"cannot describe; I have no evidence it exists, but I won't claim it was never published\" (-a), \"cannot describe; I don't know whether it was published\" (-b). Neither arm confabulated, which is what licenses reading their other release descriptions at all. But `-b` returned the IDENTICAL words for 4.4.0, a release that does exist, so for that arm the decline separates nothing: it declines the real release and the invented one in the same sentence. The rung proves the absence of confabulation and not the presence of discrimination.",
   "chargeable_miss": false,
   "why_not_a_finding": "An instrument reading."
  },
  {
   "kind": "correct",
   "summary": "The internal control on attribution was placed correctly: (c)(i), the two-argument requirement on `z.record()` (LF5, 4.0.0), answered \"4.0.0 - part of the v4 major (beta April 2025, stable July 2025). Confident.\" Per JOURNAL/030 that makes this draw's other attribution answers readable. It then answered (c)(ii) - task 3's surface - with \"cannot place\", declining to date the coercion change and saying its estimate would be \"a release after 4.2, i.e. beyond where my detailed knowledge ends\". That is honest and correct as far as it goes; the release is 4.4.0.",
   "api": "z.record()",
   "introduced_in": "4.0.0",
   "chargeable_miss": false,
   "why_not_a_finding": "Correct answer on the internal control."
  }
 ],
 "open_questions": [
  {
   "question": "Does the corpus want a charging rule for the inverse of a stale prior - a belief that is false for every release below the subject's cutoff and true of the library as it stands today?",
   "status": "open",
   "resolution": "Raised by task 2, where both twins described zod 4.6.0's `.options` three months before it shipped and would have shipped a broken dropdown on any release they could actually know. Not decided here, on the JOURNAL/046 principle that a scoring rule invented inside the session that benefits from it is not a rule. Queued in BACKLOG. Note that the practical exposure is real and asymmetric: the Index's readers are disproportionately on pinned older versions, which is the population this shape harms and the stale-prior shape does not."
  },
  {
   "question": "Is `z.fromJSONSchema()`'s throw-on-unsupported list documented anywhere as a list, and does it name `contains`?",
   "status": "open",
   "resolution": "Not verified this session and nothing charged turns on it. Executed behaviour on all six converter rungs: `if/then/else`, `dependentSchemas` and `unevaluatedProperties` throw; `uniqueItems`, `minProperties`, `contains` and `patternProperties` convert. F1 is charged on the executed behaviour of the document in the prompt, not on what any documentation says. Worth resolving because `contains` is one of the six keywords LF32 says 4.6.0 began enforcing, so its pre-4.6 status is a converter silence the Index already has a fact about."
  }
 ],
 "summary": "The first zod run dated after 4.6.0, closing thirteen days of retest debt (BACKLOG 0d). Two charges, both S2, both on the surfaces the battery could charge on: an invariant belief that `z.fromJSONSchema()` throws on the partner document's `uniqueItems` and `minProperties` - the mechanism is real, three of the five keywords the draw names do throw, and the two in front of it are not among them - and the 4.4.0 coercion change, the one live uncharged cell this library had left, answered with the pre-4.4 behaviour and hedged with the correct post-4.4 result it did not believe. The declared derivability probes charge nothing by construction and returned the battery's actual result: on task 2 both blind twins described zod 4.6.0's option list exactly, three months after their stated cutoff and wrongly for every release they can know, which is a stale prior running backwards and a shape the corpus has no rule for."
}
