Skip to content

fix(api): align ConflictOut OpenAPI response with runtime schema fields - #1536

Merged
erni-a merged 3 commits into
caura-ai:mainfrom
wylovelyi:fix/issue-1440-conflict-openapi-fields
Sep 26, 2026
Merged

erni-a merged 3 commits into
caura-ai:mainfrom
wylovelyi:fix/issue-1440-conflict-openapi-fields

Conversation

@wylovelyi

Copy link
Copy Markdown
Contributor

Summary

Closes #1440.

The conflict-review routes (GET /api/v1/conflicts, GET /api/v1/conflicts/{conflict_id}, PATCH /api/v1/conflicts/{conflict_id}/resolve) serialize the runtime core_api.schemas.ConflictOut, but their responses={200: {"model": ...}} decorators point at the smaller spec-only core_api.openapi_responses.ConflictOut, which omitted 7 fields the handler actually emits.

This is a spec-only change — the OpenAPI response model is documentational (it is not used as response_model=, so no wire/runtime serialization changes). It now declares the same fields the runtime model does:

  • fleet_id
  • relationship_confidence
  • diagnosis_confidence
  • evidence_strength
  • audit_reason
  • created_by
  • created_at

Types follow the existing spec-only model's conventions (str for ids/timestamps, float | None for the confidence scores), matching what _MEMORY_CONFLICT_FIELDS / core_api.schemas.ConflictOut carry.

Regression test

Added tests/test_d11_conflict_openapi_parity.py, which asserts the spec-only ConflictOut documents every field the runtime ConflictOut serializes. This ratchets against future drift between the two models — the exact cause of #1440.

Notes

  • No runtime serialization change (spec-only model, per C33 design).
  • Generated OpenAPI artifacts are produced from the route decorators at build time, so the Python model change is sufficient; nothing else needed editing.

@wylovelyi
wylovelyi requested a review from a team as a code owner September 16, 2026 06:47
…ds (Closes caura-ai#1440)

The D11 conflict OpenAPI response model (ConflictOut) was missing 7 runtime
fields present in the actual conflict payload: fleet_id, relationship_confidence,
diagnosis_confidence, evidence_strength, audit_reason, created_by, created_at.
Clients generated from the spec could not read these fields back.

- Add the 7 fields to core-api/src/core_api/openapi_responses.py:ConflictOut
  matching the runtime model in core_api/schemas.py.
- Add tests/test_d11_conflict_openapi_parity.py to catch future field drift.

Signed-off-by: wylovelyi <wylovelyi@users.noreply.github.com>
@wylovelyi
wylovelyi force-pushed the fix/issue-1440-conflict-openapi-fields branch 4 times, most recently from 93c9882 to 41842e5 Compare September 20, 2026 14:05
@wylovelyi

Copy link
Copy Markdown
Contributor Author

Hi maintainers 👋

Brought this onto the latest main (merge commit, branch head 583d8e4b). Posting a short summary since the PR has been open a bit.

What this PR does
Aligns the ConflictOut OpenAPI response schema (core-api/src/core_api/openapi_responses.py) with the fields the runtime actually returns for the D11 conflict feature. The spec was missing 7 fields that the service emits:

  • fleet_id
  • relationship_confidence
  • diagnosis_confidence
  • evidence_strength
  • audit_reason
  • created_by
  • created_at

Added tests/test_d11_conflict_openapi_parity.py as a regression guard so these fields can't silently drift from the model again (it asserts the required field set on ConflictOut).

Closes #1440. 2 files, +50 / -0.

CI status — all green:

  • CI, CodeQL, Security Scan (bandit + pip-audit)
  • Legacy-name ratchet (×2), Do-not-touch sentinel, pre-checks
  • dco, core-api image bakes a real version, Analyze (python) + Analyze (javascript-typescript)

(The claude-* checks are skipped — not configured on the fork.)

The branch is a merge of main rather than a rebase; happy to rebase to a linear history if you'd prefer that before merging. Ready for review whenever you have a moment — thanks!

@erni-a erni-a left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @wylovelyi, and sorry this sat ten days. Verified against main: the seven fields match the runtime ConflictOut in schemas.py (float for the two confidences, str for the rest, created_at as an ISO string like resolved_at already is in this spec-only model), and the parity test lives in the root tests/ suite that CI runs. Merging; it will be behind main by the time its turn comes, so I will update the branch myself.

@erni-a
erni-a merged commit 1f929dc into caura-ai:main Sep 26, 2026
14 checks passed
@caura-deploy-bot caura-deploy-bot Bot mentioned this pull request Sep 26, 2026
ghost pushed a commit that referenced this pull request Sep 26, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>backend: 3.20.0</summary>

##
[3.20.0](backend-v3.19.2...backend-v3.20.0)
(2026-09-26)


### Features

* **llm:** add Atlas Cloud provider
([#1510](#1510))
([0ee34f4](0ee34f4))


### Bug Fixes

* **api:** align ConflictOut OpenAPI response with runtime schema fields
([#1536](#1536))
([1f929dc](1f929dc))
* **client-python:** ship the Apache-2.0 LICENSE in the published
package ([#1031](#1031))
([820d60b](820d60b))
* **client-ts:** ship the Apache-2.0 LICENSE in the npm package
([#1032](#1032))
([26231d4](26231d4))
* **contradiction:** give the forward chain-edge writes the CAS their
comments claimed
([#1727](#1727))
([4599871](4599871))
* **plugin:** stop re-requesting agent keys after the provision route
404s ([#1718](#1718))
([b5fe391](b5fe391))
* **worker:** stop warning that the async path does not fan out — it has
since A70 (oss-0924-m-03)
([#1724](#1724))
([cb84511](cb84511))
</details>

<details><summary>plugin: 2.23.3</summary>

##
[2.23.3](plugin-v2.23.2...plugin-v2.23.3)
(2026-09-26)


### Bug Fixes

* **plugin:** stop re-requesting agent keys after the provision route
404s ([#1718](#1718))
([b5fe391](b5fe391))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: release-please[bot] <release-please[bot]@users.noreply.github.com>
Signed-off-by: Eldad Caura <eldad@caura.ai>
Co-authored-by: caura-deploy-bot[bot] <265395343+caura-deploy-bot[bot]@users.noreply.github.com>
Co-authored-by: Eldad Caura <eldad@caura.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
eldad-caura-ai added a commit that referenced this pull request Oct 4, 2026
…1813)

## Audit finding

**M-101**, from batch B20 (contradiction verdict correctness). The
OpenAPI description of `MemoryContradictionsResponse.superseded_by`
points the wrong way.

- **The description:** "The newer memory that superseded this one; null
when none is live."
- **What the route returns:** `GET /memories/{id}/contradictions` builds
the field from this memory's `supersedes_id`, the older row this memory
replaced. The route's comment says so: "Despite the field name,
memory.supersedes_id points at the OLDER memory". MCP `caura_manage
op=lineage` returns the key the same way.
- **Where the newer rows are:** in `superseded_memories`, and in
`contradictions[direction=superseded_by]`.
- **Why it matters:** `docs/public-api-stability.md` makes OpenAPI
response shapes part of the contract. A client built from the published
schema walks the chain backwards: when `superseded_by` is set it marks
this memory, the live winner, as stale, and treats the retired
predecessor as its correction. This is the same class of bug as caura PR
#1536, on a different field.

## Change

- **`superseded_by`:** "The older memory this one superseded (via
supersedes_id); null when there is none or it was deleted. The field
name is kept for back-compat; newer memories that superseded this one
are in superseded_memories and in contradictions with direction
superseded_by."
- **`superseded_memories`:** gains its own description: "Newer memories
that superseded this one, each with supersedes_id pointing here."
- No behaviour change.

The served MCP `caura_manage` description is unchanged. Rewording it
would mean regenerating `tests/fixtures/tools_list_baseline_v1.json` and
`plugin/tools.json`, and spending part of the token budget, which has 22
tokens of headroom. The MCP handler docstring already states the
meaning.

## Verification

- `tests/test_contradictions_superseded_by_contract.py` reads both
descriptions from `app.openapi()`. `tests/test_api_contradictions.py`
already pins that the route returns the older row under `superseded_by`.
- Tests first: the tests-only push `8755ffbe` ([run
37179852102](https://github.com/caura-ai/caura/actions/runs/37179852102))
failed both new tests against `main`, with 8678 passing. The schema
served the old "newer memory" text, and no description for
`superseded_memories`. The fix is now amended into the same single
commit.
- No repository scripts, dependency installs, tests or builds were run
on Eldad's Mac under its safety hold. CI runs the suites.

One signed-off commit. Do not merge or enable auto-merge; Eldad merges
after review.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: eldad-caura <eldad@caura.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

fix(api): align conflict OpenAPI response model with runtime fields

2 participants