## Summary
- preserve boolean, empty, null, type-array, enum, const, and
scalar-constraint semantics across every Python conversion entry point
- intersect Zod enum and const values with declared types and
constraints, including compound JSON values
- default unversioned exact validation to Draft 7 and apply inclusive
and numeric exclusive bounds independently
- run one byte-identical corpus through Python, Zod, and Effect so
accepted and rejected inputs stay aligned
- keep exact JSON Schema acceptance separate from Pydantic default
materialization
## Review follow-up (second push)
- Python: exact Draft 7 acceptance now wraps all three entry points
(`json_schema_to_pydantic_type`, `json_schema_to_model`,
`pydantic_model_from_param_schema`), so they can no longer disagree
- Python: draft-4 boolean `exclusiveMinimum`/`exclusiveMaximum` (OpenAPI
3.0 style) no longer crash conversion — exact validation falls back to
Draft 4, and the library input is translated to the numeric spelling
- Python: ECMA-only regex patterns (look-around) no longer crash
pydantic model builds — Rust-incompatible patterns fall back to Python
`re`
- Python: type arrays with sibling constraints no longer raise
`TypeError` on valid input — constraints are scoped per member before
the library sees them
- Python: integral floats satisfy `integer`, `const` intersects `enum`,
annotation-only schemas accept anything, and an optional property with
an empty `enum` tolerates absence
- Zod: typeless scalar constraints apply per instance type, and string
lengths count Unicode code points instead of UTF-16 code units
- Effect: draft-4 boolean exclusive bounds are enforced instead of
silently ignored
- `multipleOf` uses decimal scaling in all three converters (declared
`divergesFromJsonSchema` on the corpus case)
- shared corpus grows by 13 primitive cases; new property-based tests
check acceptance against real Draft 7 oracles (hypothesis + `jsonschema`
in Python, fast-check + Ajv in TypeScript)
## Verification
- Python `make chk` (ruff + mypy)
- Python pytest: 1,572 passed (5 langchain-extra tests need an env this
sandbox lacks; unchanged from base)
- `@composio/json-schema-to-zod`: 187 passed incl. 300-run fast-check
property test; typecheck + build
- `@composio/json-schema-to-effect-schema`: 133 passed; typecheck
- `@composio/core` corpus ingress tests: 61 passed
- shared Python/TypeScript corpus files are byte-identical
(shasum-verified)
- `git diff --check`
## Contributor context
This replaces four narrow proposals after independent local
reproduction:
- [#4301](https://github.com/ComposioHQ/composio/pull/4301) ·
[Glen](https://app.tryglen.com/ComposioHQ/composio/pull/4301)
- [#4302](https://github.com/ComposioHQ/composio/pull/4302) ·
[Glen](https://app.tryglen.com/ComposioHQ/composio/pull/4302)
- [#4303](https://github.com/ComposioHQ/composio/pull/4303) ·
[Glen](https://app.tryglen.com/ComposioHQ/composio/pull/4303)
- [#4307](https://github.com/ComposioHQ/composio/pull/4307) ·
[Glen](https://app.tryglen.com/ComposioHQ/composio/pull/4307)
---------
Co-authored-by: simpleqt <89645338+simpleqt@users.noreply.github.com>
The dynamic-key reference walker descended into every nested dict, so a
$ref-shaped value stored under const, default, enum, or examples was
treated as a schema reference and raised while the tool was wrapped.
Restrict the walk to Draft 7 schema positions.
`json_schema_to_model` and `json_schema_to_pydantic_type` now share one
compiled object policy, so provider-facing Pydantic models stop silently
discarding valid free-form arguments and stop ignoring `patternProperties` and
explicit `additionalProperties` controls. Accepted dynamic keys are written back
into `__pydantic_extra__`, which is what `LangchainProvider.wrap_tool` re-reads
with `getattr`.
Acceptance and rejection are asserted from the shared cross-SDK corpus, so the
Python entry points cannot diverge from the TypeScript converters.
### Summary
Preserves JSON Schema boolean-schema semantics throughout the Python
schema converter:
- an unsatisfiable `allOf` now propagates through nested combiners
instead of being dropped;
- impossible array items, object properties, and local definition
references remain rejecting;
- composed impossible schemas use a Pydantic type that rejects every
value, including JSON `null`;
- the existing `json_schema_to_pydantic_type(False) is None`
compatibility behavior remains unchanged.
### Problem
The original fix handled only a literal `false` directly inside one
`allOf`. Recursive filtering still interpreted the same `None` value as
both "discard this union branch" and "the whole schema is impossible."
That widened nested `allOf` schemas to `str`, removed impossible
properties/items/definitions, and allowed JSON `null` because Pydantic
treats a bare `None` annotation as `NoneType`.
### Fix
- Add a private unsatisfiable marker with Pydantic core- and JSON-schema
hooks.
- Drop that marker only from `anyOf`/`oneOf`; propagate it through
`allOf`.
- Preserve rejecting semantics for nested object properties, array
items, and local `$defs`/`definitions` references.
- Emit `{"not": {}}` from the rejecting Pydantic type.
- Correct the related empty-`allOf` and all-false-`anyOf` expectations.
### Testing
- `uv run pytest tests/test_schema_converter.py
tests/test_schema_parser.py -q` - 106 passed
- `uv run pytest -m schema -q` - 71 passed
- `make tst` - 928 passed
- `make chk` - Ruff and mypy passed across core, providers, tests, and
scripts
Regression coverage includes direct and nested `allOf`, JSON `null`,
empty arrays with impossible items, optional and required impossible
properties, local definition references, generated JSON Schema, and
shared model consumers.
### Note
#3876 adds coverage for the same function's `anyOf` handling but does
not change this `allOf` propagation path.
---------
Co-authored-by: jkomyno <alberto@composio.dev>
The `return t.Optional[t.Any] if has_null else str` line introduced in
630038ab8 returns a union of `<typing special form>` and `type[str]`,
which mypy can't reconcile with the function's declared `t.Type` return.
Same `# type: ignore` pattern already used three other times in this
function (lines 280, 285, 287) for the identical reason — `t.Optional`
isn't a `type[T]`, it's a typing special form.
Unblocks Checks/common-checks on this PR and any other PR cut after
630038ab8 landed on next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>