Skip to content

feat: expose custom_metadata_checksum and ConcurrencyConflictError for optimistic concurrency control [PYSDK-130]#632

Open
akunft wants to merge 6 commits intomainfrom
feat/expose-custom-metadata-checksum-param
Open

feat: expose custom_metadata_checksum and ConcurrencyConflictError for optimistic concurrency control [PYSDK-130]#632
akunft wants to merge 6 commits intomainfrom
feat/expose-custom-metadata-checksum-param

Conversation

@akunft
Copy link
Copy Markdown
Collaborator

@akunft akunft commented May 7, 2026

🛡️ Implements PYSDK-130 following CC-SOP-01 Change Control, part of our ISO 13485-certified QMS | Ketryx Project

Summary

  • Adds optional custom_metadata_checksum keyword-only parameter to Run.update_custom_metadata(), Run.update_item_custom_metadata() (platform layer), all four service-layer methods, and both CLI commands (run update-metadata --checksum, run update-item-metadata --checksum). Defaults to None — fully backward compatible.
  • Introduces ConcurrencyConflictError(ValueError) in system/_exceptions.py; HTTP 412 from the server now raises this instead of a generic ValueError, letting callers distinguish concurrency conflicts from invalid-ID errors.
  • Adds unit tests for checksum forwarding, 412→ConcurrencyConflictError mapping, and fixes previously broken assert_called_once_with assertions. Removes dead PublicApi import from runs_test.py.

Test plan

  • make lint passes (mypy, pyright, ruff)
  • uv run pytest tests/aignostics/application/service_test.py -m unit -v — all 21 tests green, including the 4 new checksum/412 tests
  • uv run aignostics application run update-metadata --help shows --checksum option
  • uv run aignostics application run update-item-metadata --help shows --checksum option
  • CI matrix green

Posted by Claude claude-sonnet-4-6 via Claude Code, applying skills cc-sop-01 on behalf of Andreas Kunft (andreas@aignostics.com)

…pdate_item_custom_metadata

Adds optional optimistic concurrency control to run and item custom
metadata updates. When `custom_metadata_checksum` is provided the
server rejects the write with HTTP 412 if the metadata was modified
since the checksum was read; pass `None` (default) to skip the check.

Changes:
- platform/resources/runs.py: add keyword-only `custom_metadata_checksum`
  param to `Run.update_custom_metadata` and `Run.update_item_custom_metadata`,
  forwarded to `CustomMetadataUpdateRequest`
- application/_service.py: propagate `custom_metadata_checksum` through
  `ApplicationService.application_run_update_custom_metadata`,
  `application_run_update_item_custom_metadata`, and both static wrappers;
  handle HTTP 412 explicitly as `ValueError` with a clear concurrency-conflict
  message instead of the generic `RuntimeError` fallback
- tests: add unit tests for checksum forwarding (None and non-None) at the
  `Run` resource layer; add service-layer tests for checksum propagation and
  412→ValueError mapping; fix stale assertions to include `custom_metadata_checksum=None`
@akunft akunft added skip:test:long_running Skip long-running tests (≥5min) sop:cc-sop-01 CC-SOP-01 Change Control (feature / planned change) type:feature New functionality (conventional feat) labels May 7, 2026
Copilot AI review requested due to automatic review settings May 7, 2026 09:27
@akunft akunft self-assigned this May 7, 2026
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 adds optimistic concurrency control support for run/item custom metadata updates by introducing an optional checksum parameter and surfacing a dedicated conflict error for HTTP 412 responses.

Changes:

  • Added keyword-only custom_metadata_checksum plumbing from CLI → application service → platform Run.update_*_custom_metadata() calls.
  • Added ConcurrencyConflictError(ValueError) and mapped HTTP 412 (precondition failed) to this error in the application service layer.
  • Updated and extended unit tests to validate checksum forwarding and 412→ConcurrencyConflictError behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/aignostics/application/service_test.py Updates assertions for the new checksum argument and adds tests for checksum forwarding + HTTP 412 mapping.
src/aignostics/system/_exceptions.py Introduces ConcurrencyConflictError for optimistic concurrency conflicts.
src/aignostics/platform/resources/runs.py Adds custom_metadata_checksum kw-only parameter and forwards it into the OpenAPI request model.
src/aignostics/application/_service.py Accepts checksum in service APIs and converts HTTP 412 ApiException into ConcurrencyConflictError.
src/aignostics/application/_cli.py Adds --checksum option to metadata update commands and handles ConcurrencyConflictError.

Comment thread src/aignostics/system/_exceptions.py Outdated
Comment thread src/aignostics/application/_service.py Outdated
Comment thread src/aignostics/application/_cli.py
Comment thread src/aignostics/application/_cli.py
Comment thread src/aignostics/application/_cli.py Outdated
Comment thread src/aignostics/application/_cli.py
@codecov
Copy link
Copy Markdown

codecov Bot commented May 7, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
885 1 884 15
View the top 1 failed test(s) by shortest run time
tests.aignostics.application.cli_test::test_cli_application_dump_schemata
Stack Traces | 0.192s run time
runner = <typer.testing.CliRunner object at 0x7f838c4ae250>
tmp_path = PosixPath('.../pytest-18/popen-gw0/test_cli_application_dump_sche0')
record_property = <function record_property.<locals>.append_property at 0x7f838c4be090>

    @pytest.mark.e2e
    @pytest.mark.timeout(timeout=60)
    def test_cli_application_dump_schemata(runner: CliRunner, tmp_path: Path, record_property) -> None:
        """Check application dump schemata works as expected."""
        record_property("tested-item-id", "SPEC-APPLICATION-SERVICE")
        result = runner.invoke(
            cli, ["application", "dump-schemata", HETA_APPLICATION_ID, "--destination", str(tmp_path), "--zip"]
        )
        application_version = ApplicationService().application_version(HETA_APPLICATION_ID)
        application_version = ApplicationService().application_version(HETA_APPLICATION_ID)
        assert result.exit_code == 0
>       assert "Zipped 11 files" in normalize_output(result.output)
E       AssertionError: assert 'Zipped 11 files' in 'Zipped 16 files to .../pytest-18/popen-gw0/test_cli_application_dump_sche0/he-tme_1.2.0_schemata.zip'
E        +  where 'Zipped 16 files to .../pytest-18/popen-gw0/test_cli_application_dump_sche0/he-tme_1.2.0_schemata.zip' = normalize_output('Zipped 16 files to .../pytest-18/popen-gw0/test_cli_application_dump_sche0/he-tme_1.2.0_schemata.zip\n')
E        +    where 'Zipped 16 files to .../pytest-18/popen-gw0/test_cli_application_dump_sche0/he-tme_1.2.0_schemata.zip\n' = <Result okay>.output

.../aignostics/application/cli_test.py:221: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@akunft akunft removed the skip:test:long_running Skip long-running tests (≥5min) label May 7, 2026
akunft added 2 commits May 7, 2026 12:50
- Re-export ConcurrencyConflictError from aignostics.system public API
- Update imports in _service.py and _cli.py to use public path
- Add unit tests for CLI --checksum success path and ConcurrencyConflictError handler on both update-metadata and update-item-metadata commands
Extract repeated string literals into named constants:
- APPLICATION_CLI_SERVICE_PATCH_TARGET for the service patch path (5 uses)
- _TEST_METADATA_JSON for the metadata stub (4 uses)
- cast("dict[str, Any]") → cast(dict[str, Any]) in runs.py (4 uses)
Copilot AI review requested due to automatic review settings May 7, 2026 12:19
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 7 out of 7 changed files in this pull request and generated 1 comment.

Comment on lines +11 to +13
from aignostics.platform import NotFoundException, RunData, RunOutput
from aignostics.platform.resources.runs import ApiException
from aignostics.system._exceptions import ConcurrencyConflictError
akunft added 2 commits May 8, 2026 14:30
Replace internal module imports with public API paths:
- aignostics.platform.resources.runs.ApiException → aignostics.platform
- aignostics.system._exceptions.ConcurrencyConflictError → aignostics.system
Copilot AI review requested due to automatic review settings May 8, 2026 14:54
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 7 out of 7 changed files in this pull request and generated no new comments.

@akunft akunft marked this pull request as ready for review May 8, 2026 19:45
@claude
Copy link
Copy Markdown

claude Bot commented May 8, 2026

PR #632 Code Review Complete

Comprehensive review of optimistic concurrency control implementation.


Executive Summary

This PR successfully implements optimistic concurrency control via an optional custom_metadata_checksum parameter and a new ConcurrencyConflictError exception. The implementation is backward compatible, well-tested, and follows the repository's architectural patterns.

Overall Assessment: High-quality implementation with one minor fix required


Critical Findings (BLOCKING)

⚠️ Line Length Violations (Ruff E501)

Location: src/aignostics/application/_cli.py

  • Line 1222: 131 characters (exceeds 120 limit)
  • Line 1279: 131 characters (exceeds 120 limit)

Both lines contain:

"The server rejects the update with HTTP 412 if the metadata was modified since this checksum was read."

Fix: Split across more lines:

help=(
    "Optional checksum for optimistic concurrency control. "
    "The server rejects the update with HTTP 412 if the metadata "
    "was modified since this checksum was read."
),

Verify: ruff check src/aignostics/application/_cli.py --select E501


Code Quality Review (Non-Blocking)

✅ Test Coverage: 85.18% (Above 85% Minimum)

  • ✅ Happy path tested (checksum forwarding)
  • ✅ HTTP 412 → ConcurrencyConflictError tested
  • ⚠️ 4 lines missing: error handlers for HTTP 422 and generic exceptions (acceptable)

✅ Test Markers: All Present

All 6 new tests have @pytest.mark.unit:

  • test_application_run_update_custom_metadata_with_checksum
  • test_application_run_update_custom_metadata_412_raises_concurrency_error
  • test_application_run_update_item_custom_metadata_with_checksum
  • test_application_run_update_item_custom_metadata_412_raises_concurrency_error
  • test_cli_run_update_metadata_success_with_checksum
  • test_cli_run_update_item_metadata_success_with_checksum

Verification: uv run pytest tests/aignostics/application/ -m "not unit and not integration and not e2e" --collect-only0 tests selected

✅ Conventional Commits: All Valid

All 5 commits follow type(scope): description format.


Architecture & Design Review

✅ Exception Export (Copilot Was INCORRECT)

Copilot claimed: ConcurrencyConflictError not re-exported from public module

Reality: Exception IS properly exported in src/aignostics/system/__init__.py:

from ._exceptions import ConcurrencyConflictError
__all__ = ["ConcurrencyConflictError", "Service", "Settings", "cli"]

Usage: from aignostics.system import ConcurrencyConflictErrorPublic import

Verdict: Copilot's review was outdated (fixed in commit b58c302)

✅ Backward Compatible Design

  • Parameter is keyword-only with default None
  • ConcurrencyConflictError subclasses ValueError (existing except ValueError still catches it)
  • No API signature changes

✅ HTTP 412 Error Handling

Correct mapping in application/_service.py:

if e.status == HTTPStatus.PRECONDITION_FAILED:  # HTTP 412
    message = f"Custom metadata for run '{run_id}' was modified..."
    raise ConcurrencyConflictError(message) from e

✅ Modulith Compliance

Clean dependency flow: CLI → Service → Platform (no circular dependencies)


Security & Breaking Changes

✅ No Security Issues

  • No secrets/tokens in code
  • No sensitive data exposure
  • Error messages safe

✅ No Breaking Changes

  • New parameter is optional
  • Exception hierarchy preserves compatibility
  • No environment variable changes

Comparison with Bot Reviews

Codecov: ✅ Acceptable

85.18% patch coverage (above 85% minimum), missing lines are error handlers (non-critical)

SonarQube: ✅ Quality Gate Passed

  • 0 new issues
  • 0 security hotspots

Copilot: Partially Outdated

  1. ❌ "ConcurrencyConflictError not re-exported" - INCORRECT (already fixed)
  2. ✅ "Line length violations" - CORRECT (blocking issue)
  3. ❌ "Tests import from private modules" - INCORRECT (already fixed in 7e2318f)

Summary of Strengths

  • ✅ Backward compatible design
  • ✅ Complete parameter threading through all layers
  • ✅ Correct HTTP 412 error mapping
  • ✅ Comprehensive unit tests with proper markers
  • ✅ Clear documentation and docstrings
  • ✅ No security issues or breaking changes
  • ✅ All conventional commits follow format

Action Items

Must Fix (Blocking)

  • Fix line 1222 in _cli.py (split help text to <120 chars)
  • Fix line 1279 in _cli.py (split help text to <120 chars)
  • Run make lint locally to verify
  • Push changes

Nice to Have (Future PRs)

  • Add HTTP 422 test for UNPROCESSABLE_ENTITY error path
  • Add integration test against staging API with actual checksum validation

Verdict: ❌ claude:review:failed

Reason: Line length violations are the only blocking issue. Once fixed, this PR is ready to merge.

The implementation is excellent and raises the bar for the codebase. After the line length fix, this will be a high-quality addition to the SDK.

View job run

@claude claude Bot added the claude:review:failed Automated Claude PR review found blocking issues on the current head commit label May 8, 2026
…exit code 3, GUI handler

- Move ConcurrencyConflictError from system/_exceptions to platform/_exceptions
  and re-export from aignostics.platform — eliminates application→system
  bidirectional dependency
- Revert cast() string-form regression in runs.py (restore TC006-compliant
  cast("dict[str, Any]", ...) in all four call sites)
- CLI conflict handlers now exit 3 (distinct from exit 2 for not-found)
- Add specific ConcurrencyConflictError handler in GUI metadata editor
  with "reload and retry" guidance instead of generic error message
- Update all imports and test assertions accordingly
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 9, 2026

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

Labels

claude:review:failed Automated Claude PR review found blocking issues on the current head commit sop:cc-sop-01 CC-SOP-01 Change Control (feature / planned change) type:feature New functionality (conventional feat)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants