Skip to content

refactor(schemas): convert CQRS schemas from discriminated unions to flat schemas#30

Merged
polaz merged 5 commits intomainfrom
refactor/29-ai-compatible-schemas
Jan 19, 2026
Merged

refactor(schemas): convert CQRS schemas from discriminated unions to flat schemas#30
polaz merged 5 commits intomainfrom
refactor/29-ai-compatible-schemas

Conversation

@polaz
Copy link
Copy Markdown
Member

@polaz polaz commented Jan 19, 2026

Summary

  • Convert all CQRS schemas from z.discriminatedUnion() to flat z.object() with .refine() validation
  • Fixes Claude API 400 errors caused by oneOf/allOf/anyOf at JSON Schema root level
  • All tool functionality remains unchanged - only schema structure is modified

Changes

Schema Pattern Change

Before (discriminated union):

const BrowseFilesSchema = z.discriminatedUnion("action", [
  z.object({ action: z.literal("tree"), project_id: z.string(), ... }),
  z.object({ action: z.literal("content"), project_id: z.string(), file_path: z.string(), ... }),
]);

After (flat schema with refines):

const BrowseFilesSchema = z.object({
  action: z.enum(["tree", "content"]),
  project_id: z.string(),
  file_path: z.string().optional(),
  ...
})
.refine(data => data.action !== "content" || data.file_path !== undefined, {
  message: "file_path is required for 'content' action",
  path: ["file_path"],
});

Affected Entities

Entity Schemas Updated
Files BrowseFilesSchema, ManageFilesSchema
MRs BrowseMergeRequestsSchema, BrowseMrDiscussionsSchema, ManageMergeRequestSchema, ManageMrDiscussionSchema, ManageMrDraftNotesSchema
Pipelines BrowsePipelinesSchema, ManagePipelineSchema, ManagePipelineJobSchema
Work Items BrowseWorkItemsSchema, ManageWorkItemSchema

Registry Updates

  • Added non-null assertions (!) for fields validated by .refine() at runtime
  • Replaced _exhaustive: never pattern with simple default: throw new Error()

Test plan

  • All 1575 tests pass
  • Build succeeds with no TypeScript errors
  • Integration tests verify real API behavior unchanged

Closes #29

…flat schemas

Replace z.discriminatedUnion() with flat z.object() + .refine() validation
for Claude API compatibility. The Claude API doesn't support oneOf/allOf/anyOf
at JSON Schema root level, which discriminated unions generate.

Changes:
- Convert all CQRS schemas (browse_*, manage_*) to flat schema pattern
- Add .refine() validators for required field validation per action
- Add .refine() validators to reject fields not valid for specific actions
- Update registry handlers with non-null assertions for refine-validated fields
- Update all tests to include action field in schema validation

Affected entities: files, mrs, pipelines, workitems

Closes #29
Copilot AI review requested due to automatic review settings January 19, 2026 16:22
@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 19, 2026

📊 Test Coverage Report

Overall Coverage: 86.53%

Coverage Details

Metric Percentage
Statements
Branches
Functions
Lines

📈 Coverage Report: View detailed coverage report

This report was generated automatically from your PR changes.

@codecov
Copy link
Copy Markdown

codecov bot commented Jan 19, 2026

Codecov Report

❌ Patch coverage is 96.06880% with 16 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/entities/core/registry.ts 82.60% 8 Missing ⚠️
src/entities/mrs/schema.ts 94.52% 4 Missing ⚠️
src/entities/pipelines/registry.ts 85.00% 0 Missing and 3 partials ⚠️
src/entities/mrs/schema-readonly.ts 98.59% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors all CQRS schemas from z.discriminatedUnion() to flat z.object() with .refine() validations to fix Claude API compatibility issues. The Claude API rejects JSON schemas with oneOf/allOf/anyOf at the root level, which is what Zod generates for discriminated unions.

Changes:

  • Converted all CQRS tool schemas (browse/manage for Files, MRs, Pipelines, Work Items) from discriminated unions to flat objects with runtime validation
  • Updated registries to use non-null assertions (!) for fields validated by .refine() at runtime
  • Replaced _exhaustive: never exhaustiveness checking pattern with simple default: throw new Error()
  • Renamed pipeline scope fields (scopejob_scope, trigger_scope) to avoid ambiguity in flat schema
  • Updated all integration and unit tests to include action field in test parameters

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/entities/workitems/schema.ts Converted ManageWorkItemSchema to flat schema with refines for create/update/delete actions
src/entities/workitems/schema-readonly.ts Converted BrowseWorkItemsSchema to flat schema for list/get actions
src/entities/workitems/registry.ts Added non-null assertions for validated fields, simplified exhaustiveness checking
src/entities/pipelines/schema.ts Converted ManagePipelineSchema and ManagePipelineJobSchema to flat schemas
src/entities/pipelines/schema-readonly.ts Converted BrowsePipelinesSchema to flat schema, renamed scope fields to job_scope/trigger_scope
src/entities/pipelines/registry.ts Updated to use non-null assertions and handle renamed scope fields
src/entities/mrs/schema.ts Converted all MR management schemas to flat with extensive refines for action-specific validation
src/entities/mrs/schema-readonly.ts Converted browse schemas to flat with comprehensive field validation per action
src/entities/mrs/registry.ts Added non-null assertions throughout for validated fields
src/entities/files/schema.ts Converted ManageFilesSchema to flat for single/batch/upload actions
src/entities/files/schema-readonly.ts Converted BrowseFilesSchema to flat for tree/content actions
src/entities/files/registry.ts Updated with non-null assertions, fixed Buffer to Uint8Array conversion
tests/* Updated all integration and unit tests to include action field in parameters

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Convert core entity schemas from discriminated unions to flat z.object()
with .refine() validation for Claude API compatibility:

- BrowseProjectsSchema (search, list, get actions)
- BrowseNamespacesSchema (list, get, verify actions)
- BrowseCommitsSchema (list, get, diff actions)
- BrowseEventsSchema (user, project actions)
- ManageRepositorySchema (create, fork actions)
- ManageTodosSchema (mark_done, mark_all_done, restore actions)

Add assertDefined() utility for type-safe field access after .refine()
validation, replacing non-null assertions in all registry handlers:
- core/registry.ts
- files/registry.ts
- mrs/registry.ts
- pipelines/registry.ts
- workitems/registry.ts

Fix switch default cases to properly cast action field to string.
Remove unused imports across all refactored files.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

polaz added 2 commits January 19, 2026 19:17
- Add 7 tests for pipelines error handling (100% line coverage)
- Add 15 tests for assertDefined and requiredId utilities
- Fix coverage-pages.yml heredoc to execute shell commands
- Fix wrong repo URLs in coverage workflow (project-nexus-mcp -> gitlab-mcp)
- Add istanbul ignore for unreachable code in mrs/registry.ts
Buffer is already a Uint8Array subclass in Node.js, no need to wrap it.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Document why assertDefined only checks undefined (not empty strings)
- Clarify trailing ? behavior in URL construction test
@polaz polaz merged commit e889bcc into main Jan 19, 2026
14 checks passed
@polaz polaz deleted the refactor/29-ai-compatible-schemas branch January 19, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(schemas): Replace discriminated unions with flat schemas for Claude API compatibility

2 participants