Skip to content

LCORE-3095: Fix schema constraint loss in json_schema_updater recursive_update() - #3096

Closed
fullsend-ai-coder[bot] wants to merge 3 commits into
mainfrom
agent/3095-fix-schema-constraint-loss
Closed

fullsend-ai-coder[bot] wants to merge 3 commits into
mainfrom
agent/3095-fix-schema-constraint-loss

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Description

Fix two bugs in recursive_update() in src/utils/json_schema_updater.py that silently dropped schema constraints when converting Pydantic JSON Schema to OpenAPI 3.0:

  1. exclusiveMinimum conversion: Pydantic v2 emits {"exclusiveMinimum": 0} for PositiveInt (value > 0). The function incorrectly rewrote this to {"minimum": 0} (value ≥ 0), losing the exclusive semantics. Now correctly emits {"minimum": 0, "exclusiveMinimum": true} per OpenAPI 3.0.

  2. anyOf nullable handling: When converting anyOf: [{type, enum, ...}, {type: null}] to a nullable type, only type was extracted while all other constraints (enum, format, maxLength, items, etc.) were silently discarded. Now preserves all additional properties from the first anyOf item.

Updated existing unit tests to assert correct behavior, added new tests for enum preservation and nested items handling, and regenerated all documentation schema artifacts.

Type of change

  • Bug fix

Tools used to create PR

  • Generated by: fullsend-ai code agent

Related Tickets & Documents

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

Testing

  • All 21 unit tests in test_json_schema_updater.py pass (including 2 new tests)
  • All 10 related dumper tests pass
  • Regenerated docs/user_doc/config.json confirms:
    • streaming_output_check_interval_tokens now shows "minimum": 0, "exclusiveMinimum": true
    • AllowedModel.model_type now includes "enum": ["llm", "embedding", "rerank"]

Closes #3095

Post-script verification

  • Branch is not main/master (agent/3095-fix-schema-constraint-loss)
  • Secret scan passed (gitleaks — 7850c3ceb201ad75e889bead2e5bb41d1f216680..HEAD)
  • PR body secret scan passed (gitleaks — no-git)
  • Final PR description secret scan passed (gitleaks — no-git)

…ve_update()

Fix two bugs in recursive_update() that silently dropped schema constraints
when converting Pydantic JSON Schema to OpenAPI 3.0:

1. exclusiveMinimum conversion: Pydantic v2 emits {exclusiveMinimum: 0} for
   PositiveInt (value > 0). The function incorrectly rewrote this to
   {minimum: 0} (value >= 0), losing the exclusive semantics. Now correctly
   emits {minimum: 0, exclusiveMinimum: true} per OpenAPI 3.0.

2. anyOf nullable handling: When converting anyOf: [{type, enum, ...},
   {type: null}] to a nullable type, only the type was extracted while all
   other constraints (enum, format, maxLength, items, etc.) were silently
   discarded. Now preserves all additional properties from the first anyOf
   item, with recursive processing for nested dict values.

Updated unit tests to assert the corrected behavior and added new tests for
enum preservation and nested items handling. Regenerated all documentation
schema artifacts.

Closes #3095
@fullsend-ai-coder fullsend-ai-coder Bot added the ready-for-review Triggers review agent dispatch label Nov 1, 2026
@coderabbitai

coderabbitai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@fullsend-ai-review

fullsend-ai-review Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:39 PM UTC · Completed 12:56 PM UTC

Commit: 5d26089 · View workflow run →

Runtime: pi · Model: opus → claude-opus-4-6 · Effort: high · Cost: $5.25

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Nov 1, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Low-moderate risk. The large line count (9192) is almost entirely regenerated JSON doc artifacts; actual code change is ~14 lines in one utility function plus ~96 lines of tests. No protected paths, no security-sensitive files, no CI/dependency changes. Bot author, low churn on source files. Issue scope aligns with PR. Signals unchanged from prior review.

Previous run

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Low-moderate risk. The large line count (9186) is almost entirely regenerated JSON doc artifacts; actual code change is ~10 lines in one utility function plus ~96 lines of tests. No protected paths, no security-sensitive files, no CI/dependency changes. Bot author, low churn on source files. Issue scope aligns with PR.

Previous run (2)

<!-- fullsend:risk-assessment -->
Risk Assessment: moderate (2/5)

Details

Low-moderate risk. The large line count (9140) is almost entirely regenerated JSON doc artifacts; actual code change is ~80 lines in one utility function plus tests. No protected paths, no security-sensitive files, no CI/dependency changes. Bot author, low churn on source files. Issue scope aligns with PR.

@fullsend-ai-review

fullsend-ai-review Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Looks good to me

Earlier findings

  • src/utils/json_schema_updater.py:14 — incomplete-doc — resolved by change: docstring updated to accurately describe constraint preservation and recursive processing of anyOf variants
  • src/utils/json_schema_updater.py:51 — logic-error — resolved by change (prior review)
  • tests/unit/utils/test_json_schema_updater.py:468 — missing-test — resolved by change (prior review)
  • src/utils/json_schema_updater.py:12 — stale-doc — resolved by change (prior review)
Previous run

<!-- fullsend:review-findings-v2:eyJ2ZXJzaW9uIjoyLCJhY3Rpb24iOiJyZXF1ZXN0LWNoYW5nZXMiLCJmaW5kaW5ncyI6W3sic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6ImluY29tcGxldGUtZG9jIiwiZmlsZSI6InNyYy91dGlscy9qc29uX3NjaGVtYV91cGRhdGVyLnB5IiwiaWQiOiJmXzEyNDIwNjAwM2VkYTU0ZWEiLCJsaW5lIjoxNCwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6ImhpZ2giLCJjYXRlZ29yeSI6ImxvZ2ljLWVycm9yIiwiZmlsZSI6InNyYy91dGlscy9qc29uX3NjaGVtYV91cGRhdGVyLnB5IiwiaWQiOiJmXzEyYTIyOWIxNjBhMTY2OWUiLCJsaW5lIjo1MSwiYWN0aW9uYWJsZSI6dHJ1ZX0seyJzZXZlcml0eSI6Im1lZGl1bSIsImNhdGVnb3J5IjoibWlzc2luZy10ZXN0IiwiZmlsZSI6InRlc3RzL3VuaXQvdXRpbHMvdGVzdF9qc29uX3NjaGVtYV91cGRhdGVyLnB5IiwiaWQiOiJmXzMyOWNmZmI4MmIxZDhiNTUiLCJsaW5lIjo0NjgsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6InN0YWxlLWRvYyIsImZpbGUiOiJzcmMvdXRpbHMvanNvbl9zY2hlbWFfdXBkYXRlci5weSIsImlkIjoiZl8yNWM2ODUxYzNlOTFmNjI2IiwibGluZSI6MTIsImFjdGlvbmFibGUiOnRydWV9XSwiZGlzcG9zaXRpb25zIjpbeyJpZCI6ImZfMTJhMjI5YjE2MGExNjY5ZSIsInN0YXR1cyI6InJlc29sdmVkX2J5X2NoYW5nZSJ9LHsiaWQiOiJmXzMyOWNmZmI4MmIxZDhiNTUiLCJzdGF0dXMiOiJyZXNvbHZlZF9ieV9jaGFuZ2UifSx7ImlkIjoiZl8yNWM2ODUxYzNlOTFmNjI2Iiwic3RhdHVzIjoicmVzb2x2ZWRfYnlfY2hhbmdlIn1dfQ== -->

Review

Findings

Medium

  • [incomplete-doc] src/utils/json_schema_updater.py:14 — The docstring bullet for anyOf handling says it converts the pattern into a single "type": X with "nullable": True, but the new code also preserves all other constraints from the first anyOf variant (e.g. format, minLength, enum, items, additionalProperties) and recursively processes them through the same OpenAPI 3.0 conversion. This key behavioral change is not reflected in the docstring.
    Remediation: Update the anyOf bullet to mention constraint preservation, e.g.: converts patterns like anyOf: [{"type": X, ...constraints}, {"type": "null"}] into a single "type": X with "nullable": True, preserving all additional constraints from the first variant and recursively applying OpenAPI 3.0 fixes to them.

Earlier findings

  • src/utils/json_schema_updater.py:51 — logic-error: resolved by change — anyOf handling now uses dict comprehension with recursive_update, fixing constraint-loss bug
  • tests/unit/utils/test_json_schema_updater.py:468 — missing-test: resolved by change — new test test_anyof_nullable_with_top_level_exclusive_minimum added at line 496
  • src/utils/json_schema_updater.py:12 — stale-doc: resolved by change — docstring updated to describe exclusiveMinimum → minimum + exclusiveMinimum: true conversion

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

<!-- fullsend:review-findings-v2:eyJ2ZXJzaW9uIjoyLCJhY3Rpb24iOiJyZXF1ZXN0LWNoYW5nZXMiLCJmaW5kaW5ncyI6W3sic2V2ZXJpdHkiOiJoaWdoIiwiY2F0ZWdvcnkiOiJsb2dpYy1lcnJvciIsImZpbGUiOiJzcmMvdXRpbHMvanNvbl9zY2hlbWFfdXBkYXRlci5weSIsImlkIjoiZl8xMmEyMjliMTYwYTE2NjllIiwibGluZSI6NTEsImFjdGlvbmFibGUiOnRydWV9LHsic2V2ZXJpdHkiOiJtZWRpdW0iLCJjYXRlZ29yeSI6Im1pc3NpbmctdGVzdCIsImZpbGUiOiJ0ZXN0cy91bml0L3V0aWxzL3Rlc3RfanNvbl9zY2hlbWFfdXBkYXRlci5weSIsImlkIjoiZl8zMjljZmZiODJiMWQ4YjU1IiwibGluZSI6NDY4LCJhY3Rpb25hYmxlIjp0cnVlfSx7InNldmVyaXR5IjoibWVkaXVtIiwiY2F0ZWdvcnkiOiJzdGFsZS1kb2MiLCJmaWxlIjoic3JjL3V0aWxzL2pzb25fc2NoZW1hX3VwZGF0ZXIucHkiLCJpZCI6ImZfMjVjNjg1MWMzZTkxZjYyNiIsImxpbmUiOjEyLCJhY3Rpb25hYmxlIjp0cnVlfV19 -->

Review

Findings

High

  • [logic-error] src/utils/json_schema_updater.py:51 — When converting anyOf nullable types, non-dict properties from the first item are copied verbatim without applying conversion rules. If the first anyOf item contains exclusiveMinimum (e.g., from Optional[PositiveInt]), the value (an integer like 0) is copied as-is instead of being converted to {"minimum": 0, "exclusiveMinimum": true}. This produces invalid OpenAPI 3.0 (where exclusiveMinimum must be boolean). The generated docs/user_doc/config.json confirms the bug: max_infer_iters, max_tool_calls, and MCP timeout fields all emit "exclusiveMinimum": 0 instead of the correct "minimum": 0, "exclusiveMinimum": true. The root cause is that the iteration for k, v in first_item.items() bypasses the exclusiveMinimum conversion branch in the main loop.
    Remediation: Process the first item through recursive_update before merging its properties, so that exclusiveMinimum (and any other convertible keys) are handled.

Medium

  • [missing-test] tests/unit/utils/test_json_schema_updater.py:468 — There is no test for the Optional[PositiveInt] pattern — an anyOf whose first item directly contains exclusiveMinimum at the top level (not nested inside items). The existing test_anyof_nullable_preserves_nested_items only tests exclusiveMinimum nested inside a child items dict. A test with {"anyOf": [{"type": "integer", "exclusiveMinimum": 0}, {"type": "null"}]} would have caught the logic error above.
    Remediation: Add a test case for anyOf with top-level exclusiveMinimum in the first variant.

  • [stale-doc] src/utils/json_schema_updater.py:12 — The docstring for recursive_update() states it 'rewrites exclusiveMinimum to minimum', but the updated implementation now also emits "exclusiveMinimum": True alongside "minimum". The docstring no longer accurately describes the output.
    Remediation: Update the docstring bullet to: - rewrites "exclusiveMinimum" to "minimum" plus "exclusiveMinimum": true (OpenAPI 3.0 boolean form)


Labels: Bug fix in schema generation utility with small review effort.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added bug Something isn't working Review effort 1/5 labels Nov 1, 2026
@fullsend-ai-coder

fullsend-ai-coder Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 12:57 PM UTC · Completed 1:07 PM UTC

Commit: 5d26089 · View workflow run →

Runtime: pi · Model: opus → claude-opus-4-6 · Effort: high · Cost: $2.40

When converting anyOf nullable types, properties from the first item
(e.g., exclusiveMinimum) were copied verbatim instead of being processed
through recursive_update. This produced invalid OpenAPI 3.0 output where
exclusiveMinimum was an integer instead of a boolean.

Changes:
- Process first_item constraints through recursive_update so that
  exclusiveMinimum (and any other convertible keys) are properly handled
- Update docstring to reflect that exclusiveMinimum is rewritten to
  minimum plus exclusiveMinimum: true (OpenAPI 3.0 boolean form)
- Add test for Optional[PositiveInt] pattern (anyOf with top-level
  exclusiveMinimum in the first variant)
- Regenerate affected doc schema artifacts

Addresses #3096
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (bot-triggered)

All three review findings addressed with a single root-cause fix: the anyOf handler now processes first-item properties through recursive_update instead of copying them directly, ensuring exclusiveMinimum (and any other convertible keys) are properly converted to OpenAPI 3.0 form. Added a test for the Optional[PositiveInt] pattern, updated the docstring, and regenerated affected doc schema artifacts.

Fixed (3):

  1. anyOf nullable conversion copies exclusiveMinimum as integer instead of converting to OpenAPI 3.0 boolean form (src/utils/json_schema_updater.py): Changed the anyOf handler to process first-item properties through recursive_update instead of copying them directly, so exclusiveMinimum and other convertible keys are properly transformed
  2. No test for Optional[PositiveInt] pattern with top-level exclusiveMinimum in anyOf first variant (tests/unit/utils/test_json_schema_updater.py): Added test_anyof_nullable_with_top_level_exclusive_minimum covering the anyOf pattern where the first item directly contains exclusiveMinimum
  3. Docstring inaccurately describes exclusiveMinimum conversion as only rewriting to minimum (src/utils/json_schema_updater.py): Updated docstring to state that exclusiveMinimum is rewritten to minimum plus exclusiveMinimum: true (OpenAPI 3.0 boolean form)

Tests: passed

CI inspections (12):

  1. unit_tests (passing, success): Unit tests passing on all Python versions (3.12, 3.13, 3.14).
  2. integration_tests (passing, success): Integration tests passing on all Python versions.
  3. Pylinter (passing, success): Pylint checks passing.
  4. Pyright (passing, success): Type checking passing.
  5. black (passing, success): Code formatting checks passing.
  6. ruff (passing, success): Ruff linting passing.
  7. pydocstyle (passing, success): Docstyle checks passing.
  8. spectral (passing, success): OpenAPI spec validation passing.
  9. bandit (passing, success): Security scan passing.
  10. PR Title Checker (passing, success): PR title format check passing.
  11. E2E Tests (passing, success): Most E2E test shards passing; two still in progress.
  12. Konflux (pending, pending): Konflux builds are queued or in progress; not a PR code issue.

Conflict update: skipped; strategy none; forge MERGEABLE; target main

Decision points
  • All three findings share one root cause: the anyOf handler bypasses conversion rules (alternatives: Patch each convertible key individually in the anyOf loop, Process remaining properties through recursive_update; rationale: Processing through recursive_update is the minimal fix that handles exclusiveMinimum and any future convertible keys in one pass)

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:09 PM UTC · Completed 1:25 PM UTC

Commit: cea4d46 · View workflow run →

Runtime: pi · Model: opus → claude-opus-4-6 · Effort: high · Cost: $5.67

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 1:27 PM UTC · Completed 1:32 PM UTC

Commit: cea4d46 · View workflow run →

Runtime: pi · Model: opus → claude-opus-4-6 · Effort: high · Cost: $1.16

Update the recursive_update() docstring to accurately describe the
anyOf handling behavior: all additional constraints from the first
variant are preserved and recursively processed through OpenAPI 3.0
conversion, not just the type field.

Addresses #3096
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 2 (bot-triggered)

Updated the recursive_update() docstring to accurately describe the anyOf nullable conversion behavior, including constraint preservation and recursive OpenAPI 3.0 processing

Fixed (1):

  1. Docstring anyOf bullet does not mention constraint preservation from first variant (src/utils/json_schema_updater.py): Updated the anyOf docstring bullet to describe that all additional constraints from the first variant are preserved and recursively processed through OpenAPI 3.0 conversion

Tests: passed

CI inspections (17):

  1. Unit tests (passing)
  2. Pydocstyle (passing)
  3. Black (passing)
  4. Ruff (passing)
  5. Pyright (passing)
  6. Type checks (passing)
  7. Python linter (passing)
  8. Bandit security scanner (passing)
  9. Shell check (passing)
  10. Radon (passing)
  11. OpenAPI (Spectral) (passing)
  12. Integration tests (passing)
  13. Check dependencies (passing)
  14. Check image building (passing)
  15. E2E Tests (passing)
  16. PR Title Checker (passing)
  17. List outdated dependencies (passing)

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:34 PM UTC · Completed 1:51 PM UTC

Commit: ece9a14 · View workflow run →

Runtime: pi · Model: opus → claude-opus-4-6 · Effort: high · Cost: $5.37

@fullsend-ai-review
fullsend-ai-review Bot dismissed stale reviews from themself November 1, 2026 13:51

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Nov 1, 2026
@tisnik tisnik closed this Nov 2, 2026
@fullsend-ai-retro

fullsend-ai-retro Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 8:07 AM UTC · Completed 8:17 AM UTC

Commit: ece9a14 · View workflow run →

Runtime: pi · Model: opus → claude-opus-4-6 · Effort: high · Cost: $2.25

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #3096 — Fix schema constraint loss in json_schema_updater

Timeline

Time (Nov 1) Agent Action Cost
12:21 retro (prior) Created issue #3095 from PR #3093 findings —
12:22–12:27 triage Triaged issue, provided test cases $1.01
12:28–12:37 code Created PR #3096 with fix + regenerated docs $1.92
12:39–12:56 review (1) Found high logic-error (anyOf handler doesn't process first-item properties through recursive_update), medium missing-test, medium stale-doc → requested changes $5.25
12:57–1:07 fix (1) Fixed all 3 findings $2.40
1:09–1:25 review (2) Found medium incomplete-doc (docstring doesn't describe constraint preservation) → requested changes $5.67
1:27–1:32 fix (2) Updated docstring $1.16
1:34–1:51 review (3) Approved, labeled ready-for-merge $5.37
Nov 2 07:56 human (tisnik) Closed PR without merging — no comment —

Total cost: $22.78 · 2 review→fix rework cycles · Outcome: PR closed, issue #3095 remains open

Review quality

The review agent performed well. Its first-pass finding was a genuine logic error: the code agent's anyOf handler copied first-item properties verbatim instead of processing them through recursive_update(), producing invalid OpenAPI 3.0 ("exclusiveMinimum": 0 instead of "minimum": 0, "exclusiveMinimum": true) for Optional[PositiveInt] fields. The suggested remediation was precise and the fix agent implemented it correctly.

Rework analysis

Cycle 1 (code agent miss): The issue description explicitly provided a code snippet showing recursive_update(v) if isinstance(v, dict) else v for processing first-item properties. The code agent implemented a simpler version without the recursive call. Cost: $7.65 (review + fix). This is consistent with the pattern tracked in fullsend-ai/fullsend#1737 — the code agent applies a fix but misses applying it across all relevant code paths.

Cycle 2 (fix agent miss): Fix 1 changed the anyOf handler to use recursive_update but didn't update the docstring to reflect the new constraint-preservation behavior. The review correctly caught this. Cost: $6.83 (review + fix). This cycle was reasonable — the finding was introduced by Fix 1's changes.

Evidence for existing issues

Autonomy readiness

Inconclusive. No human review was conducted — the human closed the PR without leaving any review comments or feedback. There is no meaningful delta between agent review and human review to analyze.

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ready-for-merge All reviewers approved — ready to merge ready-for-review Triggers review agent dispatch Review effort 1/5 risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix schema constraint loss in json_schema_updater.py recursive_update()

1 participant