STET

agent_patch

runs/2026-02-27__21-30-28__gpt-5-3-codex/flux-pr-4811/flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/agent-logs/agent.patch

11657 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/docs/content/json-schema.mdx b/app/packages/docs/content/json-schema.mdx
index 958aa6d..c6a2f09 100644
--- a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/docs/content/json-schema.mdx
+++ b/app/packages/docs/content/json-schema.mdx
@@ -214,8 +214,9 @@ Below is a quick reference for each supported parameter. Each one is explained i
 interface ToJSONSchemaParams {
   /** The JSON Schema version to target.
    * - `"draft-2020-12"` — Default. JSON Schema Draft 2020-12
-   * - `"draft-7"` — JSON Schema Draft 7 */
-  target?: "draft-7" | "draft-2020-12";
+   * - `"draft-7"` — JSON Schema Draft 7
+   * - `"draft-4"` — JSON Schema Draft 4 */
+  target?: "draft-4" | "draft-7" | "draft-2020-12";
 
   /** A registry used to look up metadata for each schema. 
    * Any schema with an `id` property will be extracted as a $def. */
@@ -250,6 +251,7 @@ To set the target JSON Schema version, use the `target` parameter. By default, Z
 ```ts
 z.toJSONSchema(schema, { target: "draft-7" });
 z.toJSONSchema(schema, { target: "draft-2020-12" });
+z.toJSONSchema(schema, { target: "draft-4" });
 ```
 
 ### `metadata`
diff --git a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts b/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
index f6e18bb..33bb061 100644
--- a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
+++ b/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
@@ -539,6 +539,65 @@ describe("toJSONSchema", () => {
     `);
   });
 
+  test("draft-4 target", () => {
+    expect(z.toJSONSchema(z.string(), { target: "draft-4" })).toMatchInlineSnapshot(`
+      {
+        "$schema": "http://json-schema.org/draft-04/schema#",
+        "type": "string",
+      }
+    `);
+
+    expect(z.toJSONSchema(z.number().gt(5).lt(10), { target: "draft-4" })).toMatchInlineSnapshot(`
+      {
+        "$schema": "http://json-schema.org/draft-04/schema#",
+        "exclusiveMaximum": true,
+        "exclusiveMinimum": true,
+        "maximum": 10,
+        "minimum": 5,
+        "type": "number",
+      }
+    `);
+
+    expect(z.toJSONSchema(z.record(z.string(), z.boolean()), { target: "draft-4" })).toMatchInlineSnapshot(`
+      {
+        "$schema": "http://json-schema.org/draft-04/schema#",
+        "additionalProperties": {
+          "type": "boolean",
+        },
+        "type": "object",
+      }
+    `);
+
+    expect(z.toJSONSchema(z.literal("hello"), { target: "draft-4" })).toMatchInlineSnapshot(`
+      {
+        "$schema": "http://json-schema.org/draft-04/schema#",
+        "enum": [
+          "hello",
+        ],
+        "type": "string",
+      }
+    `);
+
+    expect(
+      z.toJSONSchema(z.string().startsWith("a").endsWith("z"), { target: "draft-4" })
+    ).toMatchInlineSnapshot(`
+      {
+        "$schema": "http://json-schema.org/draft-04/schema#",
+        "allOf": [
+          {
+            "pattern": "^a.*",
+            "type": "string",
+          },
+          {
+            "pattern": ".*z$",
+            "type": "string",
+          },
+        ],
+        "type": "string",
+      }
+    `);
+  });
+
   test("arrays", () => {
     expect(z.toJSONSchema(z.array(z.string()))).toMatchInlineSnapshot(`
       {
diff --git a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/core/json-schema.ts b/app/packages/zod/src/v4/core/json-schema.ts
index 4b3abfb..0fa7af5 100644
--- a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/core/json-schema.ts
+++ b/app/packages/zod/src/v4/core/json-schema.ts
@@ -45,7 +45,10 @@ export type Schema =
 export type _JSONSchema = boolean | JSONSchema;
 export type JSONSchema = {
   [k: string]: unknown;
-  $schema?: "https://json-schema.org/draft/2020-12/schema" | "http://json-schema.org/draft-07/schema#";
+  $schema?:
+    | "https://json-schema.org/draft/2020-12/schema"
+    | "http://json-schema.org/draft-07/schema#"
+    | "http://json-schema.org/draft-04/schema#";
   $id?: string;
   $anchor?: string;
   $ref?: string;
@@ -75,9 +78,9 @@ export type JSONSchema = {
   not?: _JSONSchema;
   multipleOf?: number;
   maximum?: number;
-  exclusiveMaximum?: number;
+  exclusiveMaximum?: number | boolean;
   minimum?: number;
-  exclusiveMinimum?: number;
+  exclusiveMinimum?: number | boolean;
   maxLength?: number;
   minLength?: number;
   pattern?: string;
diff --git a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/core/to-json-schema.ts b/app/packages/zod/src/v4/core/to-json-schema.ts
index 001f195..4c5153d 100644
--- a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/core/to-json-schema.ts
+++ b/app/packages/zod/src/v4/core/to-json-schema.ts
@@ -10,8 +10,9 @@ interface JSONSchemaGeneratorParams {
   metadata?: $ZodRegistry<Record<string, any>>;
   /** The JSON Schema version to target.
    * - `"draft-2020-12"` — Default. JSON Schema Draft 2020-12
-   * - `"draft-7"` — JSON Schema Draft 7 */
-  target?: "draft-7" | "draft-2020-12";
+   * - `"draft-7"` — JSON Schema Draft 7
+   * - `"draft-4"` — JSON Schema Draft 4 */
+  target?: "draft-4" | "draft-7" | "draft-2020-12";
   /** How to handle unrepresentable types.
    * - `"throw"` — Default. Unrepresentable types throw an error
    * - `"any"` — Unrepresentable types become `{}` */
@@ -71,7 +72,7 @@ interface Seen {
 
 export class JSONSchemaGenerator {
   metadataRegistry: $ZodRegistry<Record<string, any>>;
-  target: "draft-7" | "draft-2020-12";
+  target: "draft-4" | "draft-7" | "draft-2020-12";
   unrepresentable: "throw" | "any";
   override: (ctx: {
     zodSchema: schemas.$ZodTypes;
@@ -163,7 +164,7 @@ export class JSONSchemaGenerator {
               else if (regexes.length > 1) {
                 result.schema.allOf = [
                   ...regexes.map((regex) => ({
-                    ...(this.target === "draft-7" ? ({ type: "string" } as const) : {}),
+                    ...(this.target !== "draft-2020-12" ? ({ type: "string" } as const) : {}),
                     pattern: regex.source,
                   })),
                 ];
@@ -178,21 +179,47 @@ export class JSONSchemaGenerator {
             if (typeof format === "string" && format.includes("int")) json.type = "integer";
             else json.type = "number";
 
-            if (typeof exclusiveMinimum === "number") json.exclusiveMinimum = exclusiveMinimum;
-            if (typeof minimum === "number") {
-              json.minimum = minimum;
-              if (typeof exclusiveMinimum === "number") {
-                if (exclusiveMinimum >= minimum) delete json.minimum;
-                else delete json.exclusiveMinimum;
+            const hasExclusiveMinimum = typeof exclusiveMinimum === "number";
+            const hasMinimum = typeof minimum === "number";
+            const effectiveMinimum =
+              hasExclusiveMinimum && (!hasMinimum || exclusiveMinimum >= minimum)
+                ? exclusiveMinimum
+                : hasMinimum
+                  ? minimum
+                  : undefined;
+            const minimumIsExclusive =
+              hasExclusiveMinimum && (!hasMinimum || exclusiveMinimum >= minimum);
+
+            if (typeof effectiveMinimum === "number") {
+              if (this.target === "draft-4") {
+                json.minimum = effectiveMinimum;
+                if (minimumIsExclusive) json.exclusiveMinimum = true;
+              } else if (minimumIsExclusive) {
+                json.exclusiveMinimum = effectiveMinimum;
+              } else {
+                json.minimum = effectiveMinimum;
               }
             }
 
-            if (typeof exclusiveMaximum === "number") json.exclusiveMaximum = exclusiveMaximum;
-            if (typeof maximum === "number") {
-              json.maximum = maximum;
-              if (typeof exclusiveMaximum === "number") {
-                if (exclusiveMaximum <= maximum) delete json.maximum;
-                else delete json.exclusiveMaximum;
+            const hasExclusiveMaximum = typeof exclusiveMaximum === "number";
+            const hasMaximum = typeof maximum === "number";
+            const effectiveMaximum =
+              hasExclusiveMaximum && (!hasMaximum || exclusiveMaximum <= maximum)
+                ? exclusiveMaximum
+                : hasMaximum
+                  ? maximum
+                  : undefined;
+            const maximumIsExclusive =
+              hasExclusiveMaximum && (!hasMaximum || exclusiveMaximum <= maximum);
+
+            if (typeof effectiveMaximum === "number") {
+              if (this.target === "draft-4") {
+                json.maximum = effectiveMaximum;
+                if (maximumIsExclusive) json.exclusiveMaximum = true;
+              } else if (maximumIsExclusive) {
+                json.exclusiveMaximum = effectiveMaximum;
+              } else {
+                json.maximum = effectiveMaximum;
               }
             }
 
@@ -379,11 +406,18 @@ export class JSONSchemaGenerator {
           case "record": {
             const json: JSONSchema.ObjectSchema = _json as any;
             json.type = "object";
-            json.propertyNames = this.process(def.keyType, { ...params, path: [...params.path, "propertyNames"] });
-            json.additionalProperties = this.process(def.valueType, {
+            const keySchema = this.process(def.keyType, {
+              ...params,
+              path: [...params.path, "propertyNames"],
+            });
+            const valueSchema = this.process(def.valueType, {
               ...params,
               path: [...params.path, "additionalProperties"],
             });
+            if (this.target !== "draft-4") {
+              json.propertyNames = keySchema;
+            }
+            json.additionalProperties = valueSchema;
             break;
           }
           case "map": {
@@ -432,7 +466,11 @@ export class JSONSchemaGenerator {
             } else if (vals.length === 1) {
               const val = vals[0]!;
               json.type = val === null ? ("null" as const) : (typeof val as any);
-              json.const = val;
+              if (this.target === "draft-4") {
+                json.enum = [val];
+              } else {
+                json.const = val;
+              }
             } else {
               if (vals.every((v) => typeof v === "number")) json.type = "number";
               if (vals.every((v) => typeof v === "string")) json.type = "string";
@@ -749,7 +787,7 @@ export class JSONSchemaGenerator {
 
         // merge referenced schema into current
         const refSchema = this.seen.get(ref)!.schema;
-        if (refSchema.$ref && params.target === "draft-7") {
+        if (refSchema.$ref && params.target !== "draft-2020-12") {
           schema.allOf = schema.allOf ?? [];
           schema.allOf.push(refSchema);
         } else {
@@ -776,6 +814,8 @@ export class JSONSchemaGenerator {
       result.$schema = "https://json-schema.org/draft/2020-12/schema";
     } else if (this.target === "draft-7") {
       result.$schema = "http://json-schema.org/draft-07/schema#";
+    } else if (this.target === "draft-4") {
+      result.$schema = "http://json-schema.org/draft-04/schema#";
     } else {
       // @ts-ignore
       console.warn(`Invalid target: ${this.target}`);