{
  "$schema": "../../schema/run.schema.json",
  "run_id": "zod--claude-fable-5--v4-a--2026-09-02",
  "supersedes": null,
  "replicate_of": null,
  "library": {
    "name": "zod",
    "ecosystem": "npm",
    "latest_version_at_test": "4.5.4",
    "latest_version_verified_on": "2026-09-02",
    "latest_version_note": "Read from https://registry.npmjs.org/zod/latest at test time. Every behavioural claim in this run was re-executed against the published zod@4.5.4 package rather than inferred from release notes."
  },
  "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 zod/v4",
    "self_reported_cutoff": "2026-01",
    "cutoff_basis": "Self-reported: \"January 2026, as best I know it.\" Same value this subject stated in zod/v2, zod/v3-d and every better-auth run.",
    "believed_latest_version": "4.1.x (patch numbers only)",
    "believed_latest_quote": "\"The latest I know of is the 4.1.x patch line, with patches continuing through roughly October-November 2025. The most recent release whose contents I can actually describe is 4.1.0 (~August 2025) - the codecs release.\"",
    "knowledge_stops_at_version": "4.1.0",
    "knowledge_stops_on": "2025-08-23",
    "knowledge_gap_starts_at_version": "4.2.0",
    "knowledge_gap_starts_on": "2025-12-15",
    "cutoff_lag_months": 5
  },
  "test": {
    "date": "2026-09-02",
    "battery": "zod/v4-a",
    "battery_spec": "prompts/zod.md",
    "prompt_file": "prompts/sent/zod-v4.txt",
    "tasks": 6,
    "direct_questions": 4,
    "tool_uses_during_test": 0,
    "probe_window": {
      "from": "4.2.0",
      "to": "4.3.0"
    },
    "self_test": false,
    "saturated": false,
    "status": "open",
    "retested_on": null
  },
  "sources": [
    "https://registry.npmjs.org/zod/latest",
    "https://github.com/colinhacks/zod/releases/tag/v4.2.0",
    "https://github.com/colinhacks/zod/releases/tag/v4.3.0",
    "https://cdn.jsdelivr.net/npm/zod@4.5.4/v4/classic/schemas.d.ts",
    "https://cdn.jsdelivr.net/npm/zod@4.5.4/v4/classic/from-json-schema.d.ts"
  ],
  "findings": [
    {
      "id": "F1",
      "severity": "S1",
      "severity_label": "breaks-build",
      "title": "States that deriving a sub-schema from a refined object schema loads without error and silently drops the refinement - the exact behaviour 4.3.0 replaced with a throw",
      "api": ".pick() / .omit() on a schema with refinements",
      "change_kind": "now-throws",
      "introduced_in": "4.3.0",
      "introduced_on": "2025-12-31",
      "chargeable": true,
      "chargeable_note": "4.3.0 (2025-12-31) precedes this subject's stated cutoff of 2026-01.",
      "model_belief": "\"In Zod 4, refinements live inside the schema - .refine() on an object returns a ZodObject with a check attached - so the module loads without error, and PasswordOnly is a real ZodObject whose shape is { password: ZodString }. The cross-field refinement is not carried over to the picked schema: .pick/.omit/.extend rebuild from the shape and drop object-level refinements (which is fortunate here - a carried-over check would compare password against a now-missing confirmPassword and always fail). So PasswordOnly.parse({ password: \\\"x\\\" }) succeeds.\" The draw added: \"The v4 docs call this dropping out explicitly.\"",
      "wrong_code": "const PasswordOnly = Signup.pick({ password: true });   // asserted to load and yield ZodObject<{password}>",
      "correct_code": "const shape = { password: z.string(), confirmPassword: z.string() };\nconst Signup = z.object(shape).refine((d) => d.password === d.confirmPassword);\nconst PasswordOnly = z.object(shape).pick({ password: true });   // derive from the unrefined base",
      "impact": "The belief is a description of pre-4.3 behaviour stated as current. On any zod at or above 4.3.0 the line throws at module evaluation, so a service that imports the module fails to start - not at the first parse, at load. The draw's own remedy is the right one and it wrote it for a different reason (error path placement), which means a reader who follows the prose rather than the final snippet ships the broken form.",
      "citations": [
        {
          "url": "https://github.com/colinhacks/zod/releases/tag/v4.3.0",
          "title": "Zod 4.3.0 release notes",
          "published_on": "2025-12-31",
          "quote": "Using `.pick()` or `.omit()` on object schemas with refinements now throws an error. Previously, this would silently drop the refinements, leading to unexpected behavior."
        }
      ],
      "scope_note": "Re-executed against the published zod@4.5.4: the module in the task throws `Error: .pick() cannot be used on object schemas containing refinements`. The draw's premise that refinements moved inside the schema in v4 is correct; what is stale is the consequence it draws from it."
    },
    {
      "id": "F2",
      "severity": "S2",
      "severity_label": "silently-wrong",
      "title": "Denies that the library has any way to make an object key omittable while rejecting an explicit undefined value",
      "api": ".exactOptional()",
      "change_kind": "added",
      "introduced_in": "4.3.0",
      "introduced_on": "2025-12-31",
      "chargeable": true,
      "chargeable_note": "4.3.0 (2025-12-31) precedes this subject's stated cutoff of 2026-01.",
      "model_belief": "\"Plainly: Zod 4 has no first-class 'exact optional' wrapper. z.string().optional() means both 'key may be absent' and 'value undefined is accepted' - the two are not separable in the released API.\" Restated at (d)(ii): \"an exact-optional wrapper ... does not exist in any released Zod version I know of.\"",
      "wrong_code": "z.object({ id: z.string(), nickname: z.string().optional() })\n  .superRefine((val, ctx) => { /* hand-rolled presence check */ });",
      "correct_code": "z.object({ id: z.string(), nickname: z.string().exactOptional() });",
      "impact": "A reader ships a superRefine that duplicates a built-in, and carries the belief that the library cannot express the contract their tsconfig is enforcing. The cost is not a broken parse - it is a wrong architectural conclusion about the library, restated confidently at (d)(ii) as a property of every released version.",
      "citations": [
        {
          "url": "https://github.com/colinhacks/zod/releases/tag/v4.3.0",
          "title": "Zod 4.3.0 release notes",
          "published_on": "2025-12-31",
          "quote": "A new wrapper that makes a property *key-optional* (can be omitted) but does **not** accept `undefined` as an explicit value."
        },
        {
          "url": "https://cdn.jsdelivr.net/npm/zod@4.5.4/v4/classic/schemas.d.ts",
          "title": "zod@4.5.4 published type declarations, v4/classic/schemas.d.ts",
          "quote": "exactOptional(): ZodExactOptional<this>;"
        }
      ],
      "scope_note": "The charge is on the denial, not on the workaround. The hand-rolled superRefine was executed against zod@4.5.4 and it works: the omitted key passes and the explicit undefined is rejected. Under the JOURNAL/030 rule a working workaround is not a finding and a denial is, which is why this is charged and task 6 is not. The draw's claim that a trailing-`?` key constructor existed in the Zod 4 betas and was cut before 4.0 stable is recorded as an open question, not charged - JOURNAL/027 verified only that no such constructor exists in the published 4.x line."
    },
    {
      "id": "F3",
      "severity": "S2",
      "severity_label": "silently-wrong",
      "title": "Denies that the library can consume a JSON Schema document at runtime and prescribes adding Ajv as a second validator",
      "api": "z.fromJSONSchema()",
      "change_kind": "added",
      "introduced_in": "4.2.0",
      "introduced_on": "2025-12-15",
      "chargeable": true,
      "chargeable_note": "4.2.0 (2025-12-15) precedes this subject's stated cutoff of 2026-01.",
      "model_belief": "\"Zod cannot do this. Zod 4 ships the reverse direction natively - z.toJSONSchema(schema) converts a Zod schema into JSON Schema - but there is no z.fromJSONSchema; Zod has no capability to consume an arbitrary JSON Schema document at runtime, in any release I know of.\" The draw then names the dependency it would add: \"What I'd actually do: add Ajv for those payloads, even though that's a second validation library in the tree.\"",
      "wrong_code": "import Ajv from \"ajv\";\nconst validate = new Ajv().compile(externalJsonSchemaDoc);",
      "correct_code": "const schema = z.fromJSONSchema(externalJsonSchemaDoc);\nschema.safeParse(payload);",
      "impact": "This is the costliest shape of stale prior the Index has catalogued: the model does not write broken code, it correctly names the API that does not solve the problem, and then routes the reader to a dependency the library made unnecessary nine months earlier. The draw names the exact API by its exact name while asserting it does not exist.",
      "citations": [
        {
          "url": "https://github.com/colinhacks/zod/releases/tag/v4.2.0",
          "title": "Zod 4.2.0 release notes",
          "published_on": "2025-12-15",
          "quote": "Implement `z.fromJSONSchema()`"
        },
        {
          "url": "https://cdn.jsdelivr.net/npm/zod@4.5.4/v4/classic/from-json-schema.d.ts",
          "title": "zod@4.5.4 published type declarations, v4/classic/from-json-schema.d.ts",
          "quote": "export declare function fromJSONSchema(schema: JSONSchema.JSONSchema | boolean, params?: FromJSONSchemaParams): ZodType;"
        }
      ],
      "scope_note": "Executed against zod@4.5.4: z.fromJSONSchema on the object/required/minLength document in the fact accepts the valid payload and rejects the invalid one. The draw's reservation that upstream coverage of the full JSON Schema spec is partial is fair and upstream itself calls the API experimental (fact LF6); what is charged is the flat denial that the direction exists at all."
    },
    {
      "id": "F4",
      "severity": "S2",
      "severity_label": "silently-wrong",
      "title": "Denies that the library has an exclusive union, calling the exactly-one-of contract something you must build yourself",
      "api": "z.xor()",
      "change_kind": "added",
      "introduced_in": "4.2.0",
      "introduced_on": "2025-12-15",
      "chargeable": true,
      "chargeable_note": "4.2.0 (2025-12-15) precedes this subject's stated cutoff of 2026-01.",
      "model_belief": "\"Note that z.union itself has no 'exactly one must match' semantics - it succeeds on the first matching branch. Exclusivity here comes entirely from strictObject making the both-keys case fail each branch.\" Restated at (d)(iii): \"an exclusive union (exactly one branch must match, more than one matching is a failure): does not exist in Zod. ... You build XOR semantics yourself (strict branches or a refine).\"",
      "wrong_code": "z.union([z.strictObject({ id: z.string() }), z.strictObject({ email: z.email() })]);",
      "correct_code": "z.xor([z.object({ id: z.string() }), z.object({ email: z.email() })]);",
      "impact": "The workaround the draw substitutes is load-bearing on strictness rather than on exclusivity, and it says so. That is a real constraint the reader inherits: the moment the payload legitimately carries an extra field, the union stops being exclusive and the contract silently weakens to at-least-one. The draw anticipates this and proposes a superRefine for it, which is a second hand-rolled layer over a built-in.",
      "citations": [
        {
          "url": "https://github.com/colinhacks/zod/releases/tag/v4.2.0",
          "title": "Zod 4.2.0 release notes",
          "published_on": "2025-12-15",
          "quote": "Implement `z.xor()`"
        },
        {
          "url": "https://github.com/colinhacks/zod/releases/tag/v4.3.0",
          "title": "Zod 4.3.0 release notes",
          "published_on": "2025-12-31",
          "quote": "A new exclusive union type that requires **exactly one** option to match."
        },
        {
          "url": "https://cdn.jsdelivr.net/npm/zod@4.5.4/v4/classic/schemas.d.ts",
          "title": "zod@4.5.4 published type declarations, v4/classic/schemas.d.ts",
          "quote": "export declare function xor<const T extends readonly core.SomeType[]>(options: T, params?: string | core.$ZodXorParams): ZodXor<T>;"
        }
      ],
      "scope_note": "The charge is on the denial. The union-of-strictObjects workaround was executed against zod@4.5.4 and gives the four stated outcomes correctly for the four inputs in the task. Its stated limitation - exclusivity depends on strictness, not on the union - is the draw's own and is accurate."
    },
    {
      "id": "F5",
      "severity": "S2",
      "severity_label": "silently-wrong",
      "title": "States that an intersection of two strict object schemas is unsatisfiable, where 4.3.0 made it parse to the merged object",
      "api": "intersections involving z.strictObject()",
      "change_kind": "behavior-changed",
      "introduced_in": "4.3.0",
      "introduced_on": "2025-12-31",
      "chargeable": true,
      "chargeable_note": "4.3.0 (2025-12-31) precedes this subject's stated cutoff of 2026-01.",
      "model_belief": "\"C.parse({ a: \\\"x\\\", b: \\\"y\\\" }) throws a ZodError. ... z.intersection of strict objects is effectively unsatisfiable for any object with keys from both sides. The fix is to build one object instead.\"",
      "wrong_code": "C.parse({ a: \"x\", b: \"y\" });   // asserted: ZodError, unrecognized_keys from both sides",
      "correct_code": "C.parse({ a: \"x\", b: \"y\" });   // 4.3+: { a: \"x\", b: \"y\" }",
      "impact": "The reader is told a working composition is impossible and is sent to rewrite the schema as one flat object - which discards whatever made the two halves separate. The belief also has a defensive shape: it is stated as a known gotcha, which makes it less likely to be tested.",
      "citations": [
        {
          "url": "https://github.com/colinhacks/zod/releases/tag/v4.3.0",
          "title": "Zod 4.3.0 release notes",
          "published_on": "2025-12-31",
          "quote": "Zod 4 now only rejects keys that are unrecognized by *both* sides of the intersection."
        }
      ],
      "scope_note": "Re-executed against zod@4.5.4: the schema exactly as written in the task parses to { a: \"x\", b: \"y\" }. Read alongside the control arm's context non-finding on the same probe - Claude Haiku 4.5, eighteen months below the floor, stated the correct output from incorrect reasoning, so this probe's OUTCOME is derivable. A derivable outcome makes a pass unreadable; it does not excuse a failure, and this draw stated the wrong outcome."
    }
  ],
  "non_findings": [
    {
      "kind": "correct",
      "summary": "THE INTERNAL CONTROL PASSED. (d)(i) placed the two-argument requirement on the dictionary constructor at \"first stable in zod@3.25.0's zod/v4 subpath (May 2025), i.e. in zod@4.0.0 as a standalone major (July 2025). Not a patch; it was a headline breaking change of the major.\" That is correct on both the release and the shape of the release (fact LF5), and it is the second library at which JOURNAL/030's rule holds: where a subject holds the capability, it dates a MAJOR correctly. The rest of question (d) is therefore readable for this arm.",
      "api": "z.record()",
      "introduced_in": "4.0.0",
      "why_not_a_finding": "It is the control, and it passed."
    },
    {
      "kind": "miss",
      "summary": "Task 6, the slug probe: hand-rolled a lowercase/normalize/replace chain where z.slugify() has existed since 4.3.0 (fact LF17). The code works - executed against zod@4.5.4, the built-in returns \"hello-world\" for \"Hello, World!\" and so does the hand-rolled chain. The draw never denied a built-in existed; it simply did not reach for one.",
      "api": "z.slugify()",
      "introduced_in": "4.3.0",
      "chargeable_miss": false,
      "why_not_a_finding": "JOURNAL/030's rule: the capability probe charges denial, not workarounds. There is no severity level for correct code that a first-class API supersedes, and inventing one is what the scale resists. This probe was written expecting to land here."
    },
    {
      "kind": "context",
      "summary": "The two blind Fable 5 draws agree on all six probes, including all five charged surfaces, and on the boundary (4.1.0 / 4.2.0). Set against zod/v3 and better-auth/v2, where the -b twin held the better answer three batteries running, this is the first duplicated test arm in the Index that produced no disagreement at all to report.",
      "why_not_a_finding": "It is a property of the instrument, not of the library."
    },
    {
      "kind": "context",
      "summary": "Third battery, same boundary. This subject placed its zod attribution boundary at 4.1.0 / 4.2.0 in zod/v2 (2026-08-29), in zod/v3-d (2026-09-02) and again here, across three batteries that share no wording. That is JOURNAL/030's 'a different battery is not a different boundary' result, now shown at a second library.",
      "why_not_a_finding": "The boundary belongs to the run that first measured it; a later battery reproducing it is context. Never re-charged."
    }
  ],
  "open_questions": [
    {
      "question": "Did a trailing-`?` key-optional object constructor (z.interface()) ever ship in a published Zod 4 beta, as both Fable 5 draws assert - \"the Zod 4 betas had this, via z.interface() and its 'nickname?' key syntax, and it was removed before 4.0 stable\"?",
      "status": "open",
      "resolution": "JOURNAL/027 verified that no such constructor exists in the published 4.x line, and that the betas reachable on the CDN from 3.25.20 to 3.25.29 do not carry it. The versions that would settle whether it existed earlier are not published. Under the zod/v3 scope limit this claim is therefore not charged against either Fable draw. Both draws made it independently and in near-identical terms, which raises rather than lowers the value of settling it."
    }
  ],
  "summary": "The charging arm of the battery that finally books what zod/v3 could only watch happen. Five findings, all inside the 4.2.0-4.3.0 window and all comfortably inside this subject's stated 2026-01 cutoff: one S1 (deriving from a refined object schema is asserted to load and silently drop the refinement, where 4.3.0 made it throw at module load) and four S2, three of which are flat denials that a capability exists - exact-optional keys, exclusive unions, and reading a JSON Schema document at runtime. The third of those is the costliest shape in the catalogue: the draw names z.fromJSONSchema by name while asserting it does not exist, and sends the reader to add Ajv instead. Every workaround the draw offered was executed against the published zod@4.5.4 and every one of them works, which is exactly why the charge rests on the denials and not on the code. The battery's own control deleted one probe outright - the slug task, where hand-rolling is simply correct - and complicated another, where a subject eighteen months below the floor stated the right parse output from wrong reasoning."
}
