Summary
Skill lifecycle mutations are not serialized by the final (namespace_id, slug) coordinate.
In the publish flow, the service performs a check-then-create sequence:
- query skills by namespace/slug;
- query the current owner's skill;
- insert the skill if absent;
- query/replace/create a version.
Two concurrent publishes for the same owner, namespace, slug, and version can both observe “not found” and race on the database uniqueness constraints. One request then returns an unhandled DataIntegrityViolationException as HTTP 500. Concurrent publish/review mutations can also interleave pending-review withdrawal and latest_version_id updates.
Current code:
Steps To Reproduce
- Prepare a valid publish package for a new skill/version.
- Use the same user and namespace in two concurrent publish requests.
- Add a barrier after both transactions finish the “find existing skill/version” reads and before either insert, to make the race deterministic.
- Release both requests.
- Observe one request succeed and the other fail with a unique-constraint exception / HTTP 500.
A second regression test should run publish and review approval concurrently on the same skill coordinate and assert deterministic lifecycle state.
Expected Behavior
Mutations for the same final skill coordinate should be serialized or otherwise use an atomic upsert/conflict protocol.
Concurrent identical requests should result in either:
- one success and one deterministic business conflict/idempotent response; or
- two serialized, valid outcomes.
They should not produce an internal server error or leave ambiguous lifecycle pointers.
Environment
- Branch:
main
- Commit:
9f602f8184f23347124b3606ffce1a6ac5f19c0a
- Database: PostgreSQL
API Contract Impact
No response-shape change is required. Contention should stop surfacing as HTTP 500.
Logs Or Screenshots
Representative database symptoms:
duplicate key value violates unique constraint "skill_namespace_id_slug_owner_id_key"
duplicate key value violates unique constraint on skill_version(skill_id, version)
Summary
Skill lifecycle mutations are not serialized by the final
(namespace_id, slug)coordinate.In the publish flow, the service performs a check-then-create sequence:
Two concurrent publishes for the same owner, namespace, slug, and version can both observe “not found” and race on the database uniqueness constraints. One request then returns an unhandled
DataIntegrityViolationExceptionas HTTP 500. Concurrent publish/review mutations can also interleave pending-review withdrawal andlatest_version_idupdates.Current code:
skill(namespace_id, slug, owner_id)unique constraintskill_version(skill_id, version)unique constraintSteps To Reproduce
A second regression test should run publish and review approval concurrently on the same skill coordinate and assert deterministic lifecycle state.
Expected Behavior
Mutations for the same final skill coordinate should be serialized or otherwise use an atomic upsert/conflict protocol.
Concurrent identical requests should result in either:
They should not produce an internal server error or leave ambiguous lifecycle pointers.
Environment
main9f602f8184f23347124b3606ffce1a6ac5f19c0aAPI Contract Impact
No response-shape change is required. Contention should stop surfacing as HTTP 500.
Logs Or Screenshots
Representative database symptoms: