Skip to content

feat(identity): publish the identity-resolution OpenAPI contract and gate it - #2349

Merged
mozhaev-dev merged 7 commits into
mainfrom
feat/identity-openapi-spec
Aug 10, 2026
Merged

feat(identity): publish the identity-resolution OpenAPI contract and gate it#2349
mozhaev-dev merged 7 commits into
mainfrom
feat/identity-openapi-spec

Conversation

@mozhaev-dev

@mozhaev-dev mozhaev-dev commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Why

The committed docs/components/backend/identity-resolution/openapi.json was still the retired .NET contract: it declared routes the Rust service answers 404 for, omitted ones it serves, described none of the operator correction surface, and nothing in CI noticed. The service had no way to publish its own contract, so there was no path back to accuracy.

What

The service emits its own document. New openapi subcommand and api::openapi_document(), mirroring analytics and authenticator — offline, no backends, built from the very build_operations route table the live gear serves, so the document and the router cannot diverge. It is genuinely offline: the config is now loaded per command, so the emit does not depend on a file it never reads.

The contract is accurate again. 15 paths became 21. The seven operator correction routes and both persons-sync operations appear for the first time; /v1/persons/{email} and POST /v1/persons-seed are gone, the service having retired them; the subchart parameter is spelled {person_id}, as the route actually is. The internal S2S resolvers stay out, registered raw exactly so — a test holds that.

Parameters are described, not just paths. Every templated route named its parameters in the path and described none of them, which is not a valid document: a generated client has nothing to fill {source_id} from. All path parameters are declared now, along with the filters the list handlers accept (status/limit on both journals, person/role/active/limit on role assignments, viewer/viewed/active/limit on visibility grants, depth/valid_at on both subchart reads, limit on the review queue) and the optional revoke-reason body both DELETE routes accept. A test holds the templated-path invariant for the whole table.

It cannot go stale again. Drift gate added to openapi-specs.yml beside the analytics and authenticator ones.

The stand models are generated from it. generate_schemas.py carried a tripwire for exactly this moment — identity was listed as Untrusted because its document was the .NET one, and --check fails that entry the moment it stops being true. It now does, so the entry is promoted to Generated. The models stop being a transcription of the Rust DTOs and become a contract test: a validation failure now says the service and its published document disagree.

Two consequences handled here:

  • The /internal/persons/* resolvers are excluded from the document by design, so IdentityValue has no generated counterpart and moves to identity_internal.py, still hand-written.
  • The hand-written models carried behaviour as well as fields (walk(), emails(), in_force). Generated models carry the contract and nothing else, so that behaviour moves to free functions in stand/api/identity/views.py, beside the tests that use it. Deliberately not wrapper subclasses: those must re-declare the fields they keep, and a hand-maintained field list beside a generated one is the drift the generator exists to prevent.

The generated names are the contract's (SubchartResponse, not Subchart); the schemas package re-exports them under the names the suite already uses, so the rename stops at that package.

Verification

Check Result
OpenAPI drift gate matches the live spec
Generated models gate rc=0
ruff over tests/ passed
rustfmt / clippy clean
identity-resolution unit tests 128 passed
stand suite collection 293 collected
helpers against the generated models walk / emails / in_force exercised on instances

--config <missing> openapi now succeeds and emits the document; commands that do need a config still validate the path exactly as before.

Not verified locally: the four affected stand suites need a running stand, so they did not execute here — the layer that changed (models plus helpers) was verified by instantiating them. CI covers the rest.

Notes for review

  • Untrusted in generate_schemas.py now has no entries. Kept rather than deleted: it just did its job, and the next service arriving with a foreign contract is its user. Happy to remove it if reviewers prefer.
  • The coverage gate still does not check identity against its spec. The old reason (a .NET document) is gone; what remains is that .standard_errors stamps the full error set onto every operation, so gating would demand codes nothing observes. The stale justification in coverage.py is corrected; turning the gate on is a change to the gate (backend(analytics): OpenAPI spec over-declares boilerplate error codes and under-declares real ones (.standard_errors) #1669), not to this PR.
  • main.rs now loads config per command, which diverges from analytics and authenticator. The divergence is deliberate — they share the same wart, and pulling them along belongs in its own change.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added an offline command to generate the identity-resolution OpenAPI document as formatted JSON.
    • Expanded API documentation for identity resolution, roles, visibility, journals, and subcharts, including request and response details.
    • Added generated, strongly typed API schemas covering identity operations, profiles, roles, visibility, accounts, and errors.
  • Documentation

    • Added identity-resolution to automated OpenAPI drift checks, with stale-spec reporting and results in workflow summaries.
  • Tests

    • Updated identity API tests and shared helpers to reflect the documented API contract.

mozhaev-dev and others added 6 commits August 9, 2026 07:28
The service had no way to publish its own contract, so the committed
document under docs/components/backend/ stayed the retired .NET one: it
declared routes the service answers 404 for, omitted the ones it serves,
and knew nothing of the operator correction surface.

Add the `openapi` subcommand and `api::openapi_document`, mirroring
analytics and authenticator — same offline emit, reusing the very
`build_operations` route table the live gear serves, so the document and
the router cannot diverge.

Declare the path and query parameters while here. Every templated route
named its parameters in the path and described none of them, which is not
a valid document: a generated client has nothing to fill `{source_id}`
from. A test now holds that invariant for the whole table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
Regenerate the document from the service and add the drift gate beside the
analytics and authenticator ones, so it cannot go stale again — which is
the only reason the previous document survived the port.

What the refresh corrects: the seven operator correction routes and both
persons-sync operations appear for the first time; `/v1/persons/{email}`
and `POST /v1/persons-seed` are gone, the service having retired them; the
subchart parameter is spelled `{person_id}`, as the route actually is. The
internal S2S resolvers stay out, registered raw exactly so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
The generator carried a tripwire for exactly this moment: identity was
listed as Untrusted because its committed document was the .NET one, and
`--check` fails the entry the moment that stops being true. It now does,
so promote it to Generated.

The models stop being a transcription of the Rust DTOs and become a
contract test — a validation failure now says the service and its
published document disagree, which is the whole point of generating them.

Two consequences handled here. The `/internal/persons/*` resolvers are
excluded from the document by design, so `IdentityValue` has no generated
counterpart and moves to `identity_internal.py`, still hand-written. And
the two operation journals, field-identical when one hand-written model
served both, now have a model each — the admin listing test names the
right one per path.

The names are the contract's; the package re-exports them under the ones
the suite already uses, so the rename stops at the schemas package.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
The hand-written identity models carried more than fields: `walk()` and
`emails()` over a subchart, and `in_force` on the two temporal journals.
Generated models carry the contract and nothing else, so re-exporting them
under the old names left four suites calling methods that no longer exist
— an AttributeError at run time, invisible to collection.

Move that behaviour to free functions beside the tests that use it.
Deliberately not wrapper subclasses: those would have to re-declare the
fields they keep, and a hand-maintained field list beside a generated one
is the drift the generator exists to prevent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
Declaring only the path parameters left every list route's filters
undocumented, so a generated client could reach the routes but not narrow
them: `status`/`limit` on both operation journals, `person`/`role`/
`active`/`limit` on role assignments, `viewer`/`viewed`/`active`/`limit`
on visibility grants, `depth`/`valid_at` on both subchart reads.

Both revoke routes also accept an optional body carrying the reason, which
the document did not mention at all. `RevokeReasonRequest` gains the schema
derives the builder needs to describe it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
The subcommand is documented as needing no config, and builds the document
from the route table alone — but the config was loaded before the command
was chosen, so `--config <missing> openapi` failed on a file it never
reads. Behaviour and documentation disagreed.

Load the config per command instead. The commands that do need one keep
validating the path exactly as before; only the offline emit stops
depending on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
@mozhaev-dev
mozhaev-dev requested a review from a team as a code owner August 9, 2026 08:00
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@mozhaev-dev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 08c499bb-3600-4d4d-b7a8-1522cf45f89c

📥 Commits

Reviewing files that changed from the base of the PR and between 9e59e3f and f90dee3.

📒 Files selected for processing (2)
  • docs/components/backend/identity-resolution/openapi.json
  • src/backend/services/identity-resolution/src/api/mod.rs
📝 Walkthrough

Walkthrough

The identity-resolution service now generates an offline OpenAPI document. CI detects specification drift. Stand schemas are generated from the contract, and identity tests use the generated models and shared view helpers.

Changes

Identity OpenAPI contract

Layer / File(s) Summary
OpenAPI builder and route declarations
src/backend/services/identity-resolution/src/api/mod.rs, src/backend/services/identity-resolution/src/api/person_roles.rs, src/backend/services/identity-resolution/src/api/visibility.rs
The service builds an offline OpenAPI document, documents route parameters and request bodies, and tests route coverage.
Offline command and drift gate
src/backend/services/identity-resolution/src/main.rs, .github/workflows/openapi-specs.yml
The Openapi command emits the document without configuration. CI compares it with the committed specification and reports drift.
Generated identity schema pipeline
tests/generate_schemas.py, tests/stand/api/schemas/identity.py, tests/stand/api/schemas/identity_internal.py, tests/stand/api/schemas/__init__.py, tests/lib/insight_stand/coverage.py, tests/pyproject.toml
Identity response models are generated from the Rust contract. Internal undocumented models remain hand-written, and schema exports are updated.
Stand schema integration and test helpers
tests/stand/api/identity/*
Identity tests use generated response types and shared helpers for traversal, email collection, and active temporal rows.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CI as openapi-specs workflow
  participant CLI as identity-resolution Openapi CLI
  participant API as openapi_document
  participant Schemas as schema generator
  participant Tests as stand identity tests
  CI->>CLI: Generate OpenAPI JSON
  CLI->>API: Build offline document
  API-->>CLI: Return documented routes
  CLI-->>CI: Write specification output
  CI->>CI: Compare committed specification
  CI->>Schemas: Generate identity.py
  Schemas-->>Tests: Provide generated response models
  Tests->>Tests: Validate identity API responses
Loading

Possibly related PRs

Suggested reviewers: cyberantonz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes publishing the identity-resolution OpenAPI contract and adding drift protection.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/identity-openapi-spec

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

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/backend/services/identity-resolution/src/api/mod.rs (1)

543-554: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider asserting the whole path set, not one path.

the_document_builds_without_state_or_backends passes if every path except /v1/resolution/bind disappears. The CI drift gate catches such a loss, but the unit test states a weaker rule than its name suggests. Asserting the expected path count, or the full sorted key list, turns an accidental route removal into a failing unit test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/services/identity-resolution/src/api/mod.rs` around lines 543 -
554, The test the_document_builds_without_state_or_backends should validate the
complete expected OpenAPI path set rather than only checking
/v1/resolution/bind. Assert the expected path count or, preferably, compare the
full sorted path-key list so any accidental route removal fails the unit test.
tests/stand/api/schemas/__init__.py (1)

73-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Export the sync single-operation model

PersonsSeedOperationResponse and PersonsSeedListResponse are exported as Operation and SeedOperationList; PersonsSyncOperationResponse and PersonsSyncListResponse are only exported under the sync-list name. Add an explicit export for PersonsSyncOperationResponse so tests do not mix seed and sync single-operation models. Consider renaming Operation to SeedOperation when you add the sync export.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/stand/api/schemas/__init__.py` around lines 73 - 112, Update the schema
exports in __init__.py to explicitly expose PersonsSyncOperationResponse as a
sync single-operation model, alongside PersonsSyncListResponse. Rename the
existing PersonsSeedOperationResponse alias from Operation to SeedOperation so
seed and sync operation models remain distinct, and preserve the existing list
aliases.
tests/generate_schemas.py (1)

258-263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Confirm identity datetime expectations, then remove the orphaned Untrusted support.

  1. identity-resolution does not set unzoned_datetime, while analytics does. Keep CreatePersonRoleRequest.valid_from and CreateVisibilityRequest.valid_from as AwareDatetime only if the suite does not construct those identities request models with naive or date-only values; otherwise identity and analytics should use the same timezone handling.

  2. TARGETS no longer has an Untrusted entry, but check_untrusted() and the isinstance(target, Untrusted) branch still run from TARGETS. Remove Untrusted, Untrusted, check_untrusted(), the branch, and the stale instruction to retire stand/api/schemas/identity.py, or add a real service that still needs that path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/generate_schemas.py` around lines 258 - 263, Confirm how identity
request models construct valid_from values, especially CreatePersonRoleRequest
and CreateVisibilityRequest; preserve AwareDatetime only when inputs are
timezone-aware, otherwise align identity’s unzoned_datetime handling with
analytics. Remove the obsolete Untrusted type, its TARGETS entry,
check_untrusted(), related isinstance branch, and stale instruction to retire
identity.py unless an actual service still requires them.
src/backend/services/identity-resolution/src/main.rs (1)

76-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove documentation comments from this service binary.

src/backend/services/identity-resolution/src/main.rs is under the backend rule that forbids Rust /// docs in service binaries. Remove the docs on print_openapi() at lines 158-160, and keep user-visible command help through the clap #[command(about = "...")] metadata rather than enum comments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/services/identity-resolution/src/main.rs` around lines 76 - 79,
Remove the Rust documentation comments associated with the Openapi command and
the print_openapi() function in the identity-resolution service binary. Preserve
user-visible command help through the existing clap #[command(about = "...")]
metadata, without replacing the removed comments with additional ///
documentation.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/backend/services/identity-resolution/src/api/mod.rs`:
- Around line 380-393: Update the limit query-parameter descriptions in
src/backend/services/identity-resolution/src/api/mod.rs#L380-L393 and `#L437-L445`
to document both bounds and the default, using the established form “(1..=500,
default 100)” for assignments and grants. Also review the persons-seed and
persons-sync limit descriptions at `#L267-L273` and `#L306-L312` and update them to
state their verified lower bound, upper cap, and default; do not assume values
without confirming their clamp behavior.

In `@src/backend/services/identity-resolution/src/api/person_roles.rs`:
- Around line 91-96: The two RevokeReasonRequest structs collide on the
generated OpenAPI schema name. In
src/backend/services/identity-resolution/src/api/person_roles.rs:91-96, assign
the type a distinct schema name such as PersonRoleRevokeReasonRequest or replace
it with a shared type; apply the corresponding distinct-name or shared-type
change in src/backend/services/identity-resolution/src/api/visibility.rs:90-95,
preserving the existing wire fields.

In `@tests/stand/api/schemas/identity_internal.py`:
- Around line 1-11: Align the module documentation and models with both internal
routes: inspect the `by-external-id` and `by-email-override` resolver responses,
then either document both routes in `IdentityValue` when their shapes match, add
a distinct model for the override response when they differ, or explicitly state
that its body is not validated when no model is intended.

---

Nitpick comments:
In `@src/backend/services/identity-resolution/src/api/mod.rs`:
- Around line 543-554: The test the_document_builds_without_state_or_backends
should validate the complete expected OpenAPI path set rather than only checking
/v1/resolution/bind. Assert the expected path count or, preferably, compare the
full sorted path-key list so any accidental route removal fails the unit test.

In `@src/backend/services/identity-resolution/src/main.rs`:
- Around line 76-79: Remove the Rust documentation comments associated with the
Openapi command and the print_openapi() function in the identity-resolution
service binary. Preserve user-visible command help through the existing clap
#[command(about = "...")] metadata, without replacing the removed comments with
additional /// documentation.

In `@tests/generate_schemas.py`:
- Around line 258-263: Confirm how identity request models construct valid_from
values, especially CreatePersonRoleRequest and CreateVisibilityRequest; preserve
AwareDatetime only when inputs are timezone-aware, otherwise align identity’s
unzoned_datetime handling with analytics. Remove the obsolete Untrusted type,
its TARGETS entry, check_untrusted(), related isinstance branch, and stale
instruction to retire identity.py unless an actual service still requires them.

In `@tests/stand/api/schemas/__init__.py`:
- Around line 73-112: Update the schema exports in __init__.py to explicitly
expose PersonsSyncOperationResponse as a sync single-operation model, alongside
PersonsSyncListResponse. Rename the existing PersonsSeedOperationResponse alias
from Operation to SeedOperation so seed and sync operation models remain
distinct, and preserve the existing list aliases.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: eb19e72e-0c46-4795-b8a3-c7bc87f18560

📥 Commits

Reviewing files that changed from the base of the PR and between f1b308e and 9e59e3f.

📒 Files selected for processing (17)
  • .github/workflows/openapi-specs.yml
  • docs/components/backend/identity-resolution/openapi.json
  • src/backend/services/identity-resolution/src/api/mod.rs
  • src/backend/services/identity-resolution/src/api/person_roles.rs
  • src/backend/services/identity-resolution/src/api/visibility.rs
  • src/backend/services/identity-resolution/src/main.rs
  • tests/generate_schemas.py
  • tests/lib/insight_stand/coverage.py
  • tests/pyproject.toml
  • tests/stand/api/identity/test_admin.py
  • tests/stand/api/identity/test_conflicts.py
  • tests/stand/api/identity/test_query_contracts.py
  • tests/stand/api/identity/test_subchart.py
  • tests/stand/api/identity/views.py
  • tests/stand/api/schemas/__init__.py
  • tests/stand/api/schemas/identity.py
  • tests/stand/api/schemas/identity_internal.py

Comment thread src/backend/services/identity-resolution/src/api/mod.rs
Comment on lines +91 to +96
#[derive(Debug, Deserialize, ToSchema)]
pub struct RevokeReasonRequest {
#[serde(default)]
pub reason: Option<String>,
}
impl toolkit::api::api_dto::RequestApiDto for RevokeReasonRequest {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Two RevokeReasonRequest types collide on one generated schema component. Both files define a distinct type named RevokeReasonRequest and both now derive ToSchema. utoipa derives the component name from the bare type name, so both claim components.schemas.RevokeReasonRequest. The generated tests/stand/api/schemas/identity.py holds exactly one such class at Line 362, which confirms the collapse. The structs are field-identical today, so the wire contract is still correct; the contract becomes wrong the moment one side changes.

  • src/backend/services/identity-resolution/src/api/person_roles.rs#L91-L96: give the type a distinct component name with #[schema(as = PersonRoleRevokeReasonRequest)], or import a single shared type.
  • src/backend/services/identity-resolution/src/api/visibility.rs#L90-L95: apply the matching change, either a distinct component name or a reference to the shared type.
📍 Affects 2 files
  • src/backend/services/identity-resolution/src/api/person_roles.rs#L91-L96 (this comment)
  • src/backend/services/identity-resolution/src/api/visibility.rs#L90-L95
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/services/identity-resolution/src/api/person_roles.rs` around
lines 91 - 96, The two RevokeReasonRequest structs collide on the generated
OpenAPI schema name. In
src/backend/services/identity-resolution/src/api/person_roles.rs:91-96, assign
the type a distinct schema name such as PersonRoleRevokeReasonRequest or replace
it with a shared type; apply the corresponding distinct-name or shared-type
change in src/backend/services/identity-resolution/src/api/visibility.rs:90-95,
preserving the existing wire fields.

Comment on lines +1 to +11
"""Identity shapes that no OpenAPI document describes — hand-written.

The service registers its two `/internal/persons/*` S2S resolvers as raw routes,
deliberately kept out of the generated document (the .NET contract excluded them
the same way). They therefore cannot be generated into `identity.py`, and a model
for them has to be written from the Rust DTO by hand.

`extra` stays at its default here, unlike the generated models: nothing
regenerates this file when the DTO gains a field, so forbidding the unknown would
turn a benign addition into a failing suite.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The docstring names two internal routes, and the module supplies one model.

Lines 3-6 state that both /internal/persons/* resolvers stay out of the generated document and need hand-written models. Only by-external-id gets one. by-email-override has no model here.

If the override route returns the same shape, say so in the IdentityValue docstring and reference both routes. If it returns a different shape, add the second model, or state that the suite does not validate that body.

Also applies to: 20-34

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/stand/api/schemas/identity_internal.py` around lines 1 - 11, Align the
module documentation and models with both internal routes: inspect the
`by-external-id` and `by-email-override` resolver responses, then either
document both routes in `IdentityValue` when their shapes match, add a distinct
model for the override response when they differ, or explicitly state that its
body is not validated when no model is intended.

The `limit` descriptions stated the lower clamp and stopped there, so a
generated client could not learn the page size it would actually get. All
four list routes clamp to 1..=500 and default to 50; say so, in the form
the review queue's own description already uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
@mozhaev-dev

Copy link
Copy Markdown
Contributor Author

Addressed in f90dee3f. Verified the clamp constants rather than assuming them: all four list routes share LIST_DEFAULT_LIMIT = 50 and LIST_MAX_LIMIT = 500 (clamp_limit in person_roles.rs / visibility.rs, and the same pair in seed.rs / sync.rs), so the descriptions now read (1..=500, default 50) — not the default 100 the suggestion proposed. The review queue keeps its own (1..=1000, default 100), which matches DEFAULT_QUEUE_LIMIT / MAX_QUEUE_LIMIT.

@mozhaev-dev
mozhaev-dev added this pull request to the merge queue Aug 10, 2026
Merged via the queue into main with commit bccd4ce Aug 10, 2026
59 checks passed
@mozhaev-dev
mozhaev-dev deleted the feat/identity-openapi-spec branch August 10, 2026 02:54
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.

2 participants