)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":12393,"name":"chandan kumar","display_name":"Chandan Kumar","email":"chkumar@redhat.com","username":"chkumar246"},"change_message_id":"4c7a3f0d19ac937e89cf9b5f4a39a0d41ffe1b7f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":14,"id":"01fdaed7_ed885185","updated":"2026-08-14 01:25:36.000000000","message":"recheck","commit_id":"20c065008011362e300a5fcc6b67f0b50da62800"},{"author":{"_account_id":12393,"name":"chandan kumar","display_name":"Chandan Kumar","email":"chkumar@redhat.com","username":"chkumar246"},"change_message_id":"47697ad15166b16ce11a5cb31d81b30ac08aef90","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":14,"id":"7d591394_9986b898","updated":"2026-08-14 03:56:29.000000000","message":"recheck","commit_id":"20c065008011362e300a5fcc6b67f0b50da62800"}],"cyborg/api/controllers/v2/devices.py":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"0617d7d1b1d3431129fb563874491d2a137fdbb6","unresolved":false,"context_lines":[{"line_number":224,"context_line":"                status_code\u003dHTTPStatus.BAD_REQUEST,"},{"line_number":225,"context_line":"            )"},{"line_number":226,"context_line":""},{"line_number":227,"context_line":"        if device.device_state \u003d\u003d constants.DEVICE_STATE_AVAILABLE:"},{"line_number":228,"context_line":"            raise wsme.exc.ClientSideError("},{"line_number":229,"context_line":"                \u0027Device already clean\u0027,"},{"line_number":230,"context_line":"                status_code\u003dHTTPStatus.CONFLICT,"}],"source_content_type":"text/x-python","patch_set":5,"id":"734368e7_148daabf","line":227,"updated":"2026-08-06 17:24:14.000000000","message":"The clean() method blocks cleanup for four known states (available, allocated, cleaning, pending_cleaning) but uses a denylist approach. Any state not in the denylist—including None—falls through to dispatch_cleanup. The device_state field is nullable\u003dTrue, so devices upgraded from older versions...\n\n**Severity**: WARNING | **Confidence**: 0.8\n\n**Impact**: During upgrades or for newly discovered devices with device_state\u003dNULL, an admin calling the clean endpoint would receive 202 and trigger cleanup on a device whose lifecycle state is uninitialized, which is not the documented behavior. This could cause unexpected device operations.\n\n**Suggestion**:\nUse an allowlist approach: explicitly check that device_state \u003d\u003d constants.DEVICE_STATE_ERROR before dispatching cleanup, and return 409 for any other state including None. Alternatively, add an explicit None/unset check that returns 409 with an appropriate message.","commit_id":"52af26449156097a0518a4ba2d18fbff7ed858bf"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"34c67e6301f0fffa07d4d57209a470873eb41dd6","unresolved":false,"context_lines":[{"line_number":213,"context_line":"        )"},{"line_number":214,"context_line":""},{"line_number":215,"context_line":"    @authorize_wsgi.authorize_wsgi(\"cyborg:device\", \"clean\")"},{"line_number":216,"context_line":"    @expose.expose(None, wtypes.text, status_code\u003dHTTPStatus.ACCEPTED)"},{"line_number":217,"context_line":"    def clean(self, uuid):"},{"line_number":218,"context_line":"        context \u003d pecan.request.context"},{"line_number":219,"context_line":"        device \u003d objects.Device.get(context, uuid)"}],"source_content_type":"text/x-python","patch_set":6,"id":"f83d08b5_bd02e285","line":216,"updated":"2026-08-07 07:49:28.000000000","message":"The POST /devices/{uuid}/clean endpoint\u0027s @expose.expose decorator omits types.uuid for the uuid path parameter. The sibling disable and enable endpoints both use @expose.expose(None, wtypes.text, types.uuid, ...) where types.uuid validates the path parameter. The clean endpoint only has @expose....\n\n**Severity**: HIGH | **Confidence**: 0.9\n\n**Risk**: Without types.uuid validation, malformed UUID strings bypass WSME input validation and reach objects.Device.get(), which will raise an unhandled exception and return HTTP 500 instead of a proper 400 Bad Request. This breaks the input-validation contract that disable and enforce enforce for the sa...\n\n**Priority**: Before merge\n**Why This Matters**: Without types.uuid validation, malformed UUID strings bypass WSME input validation and reach objects.Device.get(), which will raise an unhandled exception and return HTTP 500 instead of a proper 400 Bad Request. This breaks the input-validation contract that disable and enforce enforce for the sa...\n\n**Recommendation**:\nAdd types.uuid as the third type argument to the expose decorator: @expose.expose(None, wtypes.text, types.uuid, status_code\u003dHTTPStatus.ACCEPTED). This matches the pattern used by disable and enable.","commit_id":"6a858303cb4b534bae01cfad7ba1fc381dcdc0f8"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"5b51f3ef47e2abcb2ed6405687c8e47d7beae5e9","unresolved":false,"context_lines":[{"line_number":253,"context_line":"                status_code\u003dHTTPStatus.CONFLICT,"},{"line_number":254,"context_line":"            )"},{"line_number":255,"context_line":""},{"line_number":256,"context_line":"        agent_api \u003d agent_rpcapi.AgentAPI()"},{"line_number":257,"context_line":"        if not agent_api.client.can_send_version(\u00271.1\u0027):"},{"line_number":258,"context_line":"            raise wsme.exc.ClientSideError("},{"line_number":259,"context_line":"                \u0027Cleanup not available: the cyborg-agent does not support \u0027"}],"source_content_type":"text/x-python","patch_set":9,"id":"43296aae_a0f5468a","line":256,"updated":"2026-08-11 10:18:14.000000000","message":"The clean method checks `agent_api.client.can_send_version(\u00271.1\u0027)` to detect clusters with old agents. However, AgentAPI hardcodes version_cap\u003dRPC_API_VERSION (\u00271.1\u0027) in its constructor. Since version_cap equals the checked version, can_send_version(\u00271.1\u0027) always returns True. The 400 error path...\n\n**Severity**: HIGH | **Confidence**: 0.8\n\n**Risk**: During rolling upgrades where agents have not been upgraded to RPC 1.1, the API returns 202 instead of 400, dispatching cleanup to an agent that cannot handle it. The device may get stuck in pending_cleaning state. The test gives false confidence that this scenario is handled.\n\n**Priority**: Before merge\n**Why This Matters**: During rolling upgrades where agents have not been upgraded to RPC 1.1, the API returns 202 instead of 400, dispatching cleanup to an agent that cannot handle it. The device may get stuck in pending_cleaning state. The test gives false confidence that this scenario is handled.\n\n**Recommendation**:\nEither remove the unreachable guard from the API controller (the conductor already handles this), or add a configurable version_cap (e.g., CONF.upgrade_levels.agent) so can_send_version can actually return False during rolling upgrades.","commit_id":"61e007ac6ea6de88b19460efb41d660a825d3e6e"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"30b4f917c907ac82ed78911787351080a2325947","unresolved":false,"context_lines":[{"line_number":253,"context_line":"                status_code\u003dHTTPStatus.CONFLICT,"},{"line_number":254,"context_line":"            )"},{"line_number":255,"context_line":""},{"line_number":256,"context_line":"        agent_api \u003d agent_rpcapi.AgentAPI()"},{"line_number":257,"context_line":"        if not agent_api.client.can_send_version(\u00271.1\u0027):"},{"line_number":258,"context_line":"            raise wsme.exc.ClientSideError("},{"line_number":259,"context_line":"                \u0027Cleanup not available: the cyborg-agent does not support \u0027"}],"source_content_type":"text/x-python","patch_set":10,"id":"6c06f6d0_d7d6a072","line":256,"updated":"2026-08-11 16:34:50.000000000","message":"The clean() handler instantiates AgentAPI solely to call can_send_version(\u00271.1\u0027), then dispatches work through ConductorAPI.dispatch_cleanup. The conductor manager\u0027s dispatch_cleanup method performs its own independent agent capability check, making the API-layer check redundant and creating a ma...\n\n**Severity**: HIGH | **Confidence**: 0.8\n\n**Risk**: The API-layer agent capability check is architecturally misplaced—the API process cannot know the version of the specific agent that will handle the device on the target compute host. The conductor manager already performs this check correctly and handles the failure case. More importantly, there...\n\n**Priority**: Before merge\n**Why This Matters**: The API-layer agent capability check is architecturally misplaced—the API process cannot know the version of the specific agent that will handle the device on the target compute host. The conductor manager already performs this check correctly and handles the failure case. More importantly, there...\n\n**Recommendation**:\nRemove the AgentAPI capability check from the API layer and rely on the conductor\u0027s dispatch_cleanup to handle agent version incompatibility (it already sets device_state\u003dERROR and logs a warning). Alternatively, if an early-fail UX is desired, check the conductor RPC version (via conductor_rpcapi client can_send_version(\u00271.1\u0027)) instead of the agent RPC version, since the API layer talks to conductor, not to agents directly.","commit_id":"10ceea44025481ebdbd9efadc8805bed306dc55b"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"ea2726138d0283f2a95ee0a5fc7bbe875748d30c","unresolved":false,"context_lines":[{"line_number":253,"context_line":"                status_code\u003dHTTPStatus.CONFLICT,"},{"line_number":254,"context_line":"            )"},{"line_number":255,"context_line":""},{"line_number":256,"context_line":"        agent_api \u003d agent_rpcapi.AgentAPI()"},{"line_number":257,"context_line":"        if not agent_api.client.can_send_version(\u00271.1\u0027):"},{"line_number":258,"context_line":"            raise wsme.exc.ClientSideError("},{"line_number":259,"context_line":"                \u0027Cleanup not available: the cyborg-agent does not support \u0027"}],"source_content_type":"text/x-python","patch_set":11,"id":"55a13967_0a065563","line":256,"updated":"2026-08-12 10:20:36.000000000","message":"The clean() method creates a fresh AgentAPI() and calls client.can_send_version(\u00271.1\u0027). However, AgentAPI.__init__ hardcodes version_cap\u003dself.RPC_API_VERSION which is \u00271.1\u0027. oslo_messaging\u0027s can_send_version() returns True whenever the requested version is \u003c\u003d the version_cap. Since both are \u00271.1\u0027...\n\n**Severity**: HIGH | **Confidence**: 0.9\n\n**Risk**: The API-layer guard for old agents that don\u0027t support cleanup RPC is non-functional. Operators and reviewers may believe the API rejects requests when agents are too old, but it does not. The test_clean_old_agent_400 test provides false confidence. The conductor has the same dead check (pre-exist...\n\n**Priority**: Before merge\n**Why This Matters**: The API-layer guard for old agents that don\u0027t support cleanup RPC is non-functional. Operators and reviewers may believe the API rejects requests when agents are too old, but it does not. The test_clean_old_agent_400 test provides false confidence. The conductor has the same dead check (pre-exist...\n\n**Recommendation**:\nEither make AgentAPI\u0027s version_cap configurable (e.g., via an upgrade_levels config option similar to Nova/Ironic) so can_send_version can actually return False during rolling upgrades, or remove the dead check from the API layer and rely solely on conductor-side enforcement (which also needs the same fix).","commit_id":"f026485f08465d270df80e32d8a7dbf99cab6f61"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"b92056c110d9c58ce940b55c191d575e6db9022d","unresolved":false,"context_lines":[{"line_number":223,"context_line":"        device \u003d objects.Device.get(context, uuid)"},{"line_number":224,"context_line":""},{"line_number":225,"context_line":"        if not device.supports_cleaning:"},{"line_number":226,"context_line":"            raise wsme.exc.ClientSideError("},{"line_number":227,"context_line":"                \u0027Device does not support cleaning\u0027,"},{"line_number":228,"context_line":"                status_code\u003dHTTPStatus.BAD_REQUEST,"},{"line_number":229,"context_line":"            )"}],"source_content_type":"text/x-python","patch_set":12,"id":"84453525_68244ef7","line":226,"updated":"2026-08-12 15:47:02.000000000","message":"The clean() method raises wsme.exc.ClientSideError with raw string literals for all error messages. The project\u0027s other controllers (arqs.py, device_profiles.py, types.py, utils.py) consistently wrap user-visible messages with the _() translation function from cyborg.common.i18n. devices.py does...\n\n**Severity**: SUGGESTION | **Confidence**: 0.9\n\n**Benefit**: Error messages returned by the clean endpoint are not translatable for users in non-English locales, inconsistent with the rest of the cyborg API surface.\n\n**Recommendation**:\nImport _ from cyborg.common.i18n in devices.py and wrap all ClientSideError message strings with _(), matching the pattern used in arqs.py and device_profiles.py.","commit_id":"71820d527d82c31c95ae3a78fcd7aec09a4b4eff"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"b92056c110d9c58ce940b55c191d575e6db9022d","unresolved":false,"context_lines":[{"line_number":247,"context_line":"                \u0027Device is already being cleaned\u0027,"},{"line_number":248,"context_line":"                status_code\u003dHTTPStatus.CONFLICT,"},{"line_number":249,"context_line":"            )"},{"line_number":250,"context_line":"        if state !\u003d constants.DEVICE_STATE_ERROR:"},{"line_number":251,"context_line":"            raise wsme.exc.ClientSideError("},{"line_number":252,"context_line":"                \u0027Device is not in a cleanable state\u0027,"},{"line_number":253,"context_line":"                status_code\u003dHTTPStatus.CONFLICT,"}],"source_content_type":"text/x-python","patch_set":12,"id":"43c6ff9b_7ad9ab1b","line":250,"updated":"2026-08-12 15:47:02.000000000","message":"The clean endpoint validates device_state \u003d\u003d \u0027error\u0027 in the API process, then dispatches cleanup via conductor RPC cast (asynchronous). The device state is not transitioned to \u0027pending_cleaning\u0027 until the conductor processes the cast. Two concurrent POST /clean requests can both observe \u0027error\u0027 s...\n\n**Severity**: HIGH | **Confidence**: 0.8\n\n**Risk**: Duplicate cleanup operations (e.g., NVMe sanitize) could be dispatched to the same device concurrently, potentially causing unexpected device behavior or wasted work. The first conductor call sets state to PENDING_CLEANING, but the second call (from the duplicate dispatch) will process a device t...\n\n**Priority**: Before merge\n**Why This Matters**: Duplicate cleanup operations (e.g., NVMe sanitize) could be dispatched to the same device concurrently, potentially causing unexpected device behavior or wasted work. The first conductor call sets state to PENDING_CLEANING, but the second call (from the duplicate dispatch) will process a device t...\n\n**Recommendation**:\nTransition the device state to PENDING_CLEANING (or an intermediate locking state) in the API layer before dispatching the conductor cast, using an atomic DB update with a WHERE clause that checks current state. Alternatively, use an optimistic lock or SELECT ... FOR UPDATE pattern to ensure only one request can transition from \u0027error\u0027.","commit_id":"71820d527d82c31c95ae3a78fcd7aec09a4b4eff"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"4980122d7c6d45b924712b701428a9b63550d9f0","unresolved":false,"context_lines":[{"line_number":253,"context_line":"                status_code\u003dHTTPStatus.CONFLICT,"},{"line_number":254,"context_line":"            )"},{"line_number":255,"context_line":""},{"line_number":256,"context_line":"        agent_api \u003d agent_rpcapi.AgentAPI()"},{"line_number":257,"context_line":"        if not agent_api.client.can_send_version(\u00271.1\u0027):"},{"line_number":258,"context_line":"            raise wsme.exc.ClientSideError("},{"line_number":259,"context_line":"                \u0027Cleanup not available: the cyborg-agent does not support \u0027"}],"source_content_type":"text/x-python","patch_set":14,"id":"c90ebefa_edfe4901","line":256,"updated":"2026-08-14 01:43:53.000000000","message":"The clean() method instantiates AgentAPI and calls can_send_version(\u00271.1\u0027) to guard against old agents. However, AgentAPI.__init__ sets version_cap\u003dself.RPC_API_VERSION which is \u00271.1\u0027. Since can_send_version(\u00271.1\u0027) checks whether 1.1 \u003c\u003d the cap (also 1.1), it always returns True. This makes the A...\n\n**Severity**: WARNING | **Confidence**: 0.8\n\n**Impact**: In a rolling upgrade with mixed agent versions (some at 1.0), the API will not reject the clean request with 400 as documented. Instead, the conductor\u0027s dispatch_cleanup will set the device to PENDING_CLEANING, then discover the agent doesn\u0027t support 1.1, log a warning, and set the device back to...\n\n**Suggestion**:\nEither remove the API-level can_send_version check entirely (relying on conductor\u0027s handling), or make the version_cap configurable via oslo_config so can_send_version reflects actual deployment pinning. If keeping the check as a defensive guard, add a comment explaining it will only trigger in pinned deployments and update the API doc to note that the 400 may not be returned in all cases.","commit_id":"20c065008011362e300a5fcc6b67f0b50da62800"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"b09f9abccc55e3b1ca0c5fb0b0ad7274721d378c","unresolved":false,"context_lines":[{"line_number":214,"context_line":"        )"},{"line_number":215,"context_line":""},{"line_number":216,"context_line":"    @authorize_wsgi.authorize_wsgi(\"cyborg:device\", \"clean\")"},{"line_number":217,"context_line":"    @expose.expose(None, wtypes.text, status_code\u003dHTTPStatus.ACCEPTED)"},{"line_number":218,"context_line":"    def clean(self, uuid):"},{"line_number":219,"context_line":"        if not api.request.version.minor \u003e\u003d versions.MINOR_4_DEVICE_STATE:"},{"line_number":220,"context_line":"            pecan.abort(HTTPStatus.NOT_FOUND)"}],"source_content_type":"text/x-python","patch_set":15,"id":"18a73de4_03c3b0cb","line":217,"updated":"2026-08-14 16:33:52.000000000","message":"The new endpoint uses `@expose.expose(None, wtypes.text, status_code\u003dHTTPStatus.ACCEPTED)`, while the neighboring disable() and enable() handlers in the same controller use wtypes.uuid for their uuid argument.\n\n**Severity**: SUGGESTION | **Confidence**: 0.8\n\n**Benefit**: Malformed uuid strings are passed through to objects.Device.get instead of being rejected at the WSME layer, producing inconsistent error responses versus the sibling endpoints and weaker input validation.\n\n**Recommendation**:\nChange the expose signature to `@expose.expose(None, wtypes.uuid, status_code\u003dHTTPStatus.ACCEPTED)` to match disable/enable.","commit_id":"ef82e977816acea0c35397743a8c94373e10d7bf"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"b09f9abccc55e3b1ca0c5fb0b0ad7274721d378c","unresolved":false,"context_lines":[{"line_number":253,"context_line":"                status_code\u003dHTTPStatus.CONFLICT,"},{"line_number":254,"context_line":"            )"},{"line_number":255,"context_line":""},{"line_number":256,"context_line":"        agent_api \u003d agent_rpcapi.AgentAPI()"},{"line_number":257,"context_line":"        if not agent_api.client.can_send_version(\u00271.1\u0027):"},{"line_number":258,"context_line":"            raise wsme.exc.ClientSideError("},{"line_number":259,"context_line":"                \u0027Cleanup not available: the cyborg-agent does not support \u0027"}],"source_content_type":"text/x-python","patch_set":15,"id":"812d5684_13a19cf8","line":256,"updated":"2026-08-14 16:33:52.000000000","message":"The old-agent guard `agent_api.client.can_send_version(\u00271.1\u0027)` can never return False because AgentAPI constructs its RPC client with version_cap\u003dself.RPC_API_VERSION and RPC_API_VERSION \u003d\u003d \u00271.1\u0027, so the client always believes 1.1 is sendable.\n\n**Severity**: WARNING | **Confidence**: 0.8\n\n**Impact**: Operators get no protection against dispatching cleanup to pre-1.1 agents from this endpoint; the conductor-side fallback (which sets the device to error) is the only real guard. The dead branch and its test give false confidence in the downgrade handling.\n\n**Suggestion**:\nRemove the client-side can_send_version check and rely on the conductor-side version handling in dispatch_cleanup (which logs and sets DEVICE_STATE_ERROR), or make the check meaningful by configuring a pin value that can actually be below 1.1.","commit_id":"ef82e977816acea0c35397743a8c94373e10d7bf"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"699f864cd16b0ff55015982e0a479fe6e1bf4b9b","unresolved":false,"context_lines":[{"line_number":219,"context_line":"        if not api.request.version.minor \u003e\u003d versions.MINOR_4_DEVICE_STATE:"},{"line_number":220,"context_line":"            pecan.abort(HTTPStatus.NOT_FOUND)"},{"line_number":221,"context_line":""},{"line_number":222,"context_line":"        context \u003d pecan.request.context"},{"line_number":223,"context_line":"        device \u003d objects.Device.get(context, uuid)"},{"line_number":224,"context_line":""},{"line_number":225,"context_line":"        if not device.supports_cleaning:"}],"source_content_type":"text/x-python","patch_set":16,"id":"4148b5f4_d7726653","line":222,"updated":"2026-08-15 17:22:10.000000000","message":"The clean() handler reads device.device_state, validates it, and then asynchronously dispatches conductor.dispatch_cleanup without any atomic state transition or conditional update. Two concurrent POST /clean calls (or a concurrent bind) can both pass validation for a device in \u0027error\u0027 state, and...\n\n**Severity**: HIGH | **Confidence**: 0.8\n\n**Risk**: Concurrent admin retries can dispatch two NVMe cleanup jobs (data-destructive sanitize) against the same device, and a concurrent bind between validation and the conductor-side transition can start cleanup on a device that just became allocated. Both requests receive 202 for a state the contract...\n\n**Priority**: Before merge\n**Why This Matters**: Concurrent admin retries can dispatch two NVMe cleanup jobs (data-destructive sanitize) against the same device, and a concurrent bind between validation and the conductor-side transition can start cleanup on a device that just became allocated. Both requests receive 202 for a state the contract...\n\n**Recommendation**:\nUse an atomic guard: perform a conditional DB update of device_state from \u0027error\u0027 to \u0027pending_cleaning\u0027 in the handler (or a compare-and-swap in conductor whose failure is surfaced to the caller) before replying 202, returning 409 when the transition fails; alternatively serialize with a per-device lock and re-check state inside it.","commit_id":"80fc5b6a7e1acdc75c5a241dde45e3a7cd0131e2"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"2f3235a40b82ade4c7f405f6e477ea0371f4f331","unresolved":false,"context_lines":[{"line_number":253,"context_line":"                status_code\u003dHTTPStatus.CONFLICT,"},{"line_number":254,"context_line":"            )"},{"line_number":255,"context_line":""},{"line_number":256,"context_line":"        agent_api \u003d agent_rpcapi.AgentAPI()"},{"line_number":257,"context_line":"        if not agent_api.client.can_send_version(\u00271.1\u0027):"},{"line_number":258,"context_line":"            raise wsme.exc.ClientSideError("},{"line_number":259,"context_line":"                \u0027Cleanup not available: the cyborg-agent does not support \u0027"}],"source_content_type":"text/x-python","patch_set":17,"id":"0ce787fc_1b5c21d7","line":256,"updated":"2026-08-17 08:47:33.000000000","message":"clean() builds an AgentAPI and rejects when client.can_send_version(\u00271.1\u0027) is False, claiming the cluster is pinned below 1.1. In cyborg the RPC client version_cap is the static class constant AgentAPI.RPC_API_VERSION \u003d \u00271.1\u0027 (cyborg/common/rpc.py get_client), with no DB-derived cluster pin, so c...\n\n**Severity**: HIGH | **Confidence**: 0.8\n\n**Risk**: A rolling-upgrade cluster with old agents or an old conductor still receives 202 Accepted, and the failure is silent (async cast), so the documented 400 protection never fires. The code and its test give false confidence about upgrade safety.\n\n**Priority**: Before merge\n**Why This Matters**: A rolling-upgrade cluster with old agents or an old conductor still receives 202 Accepted, and the failure is silent (async cast), so the documented 400 protection never fires. The code and its test give false confidence about upgrade safety.\n\n**Recommendation**:\nRemove the API-layer guard (conductor.dispatch_cleanup already owns agent capability handling) or back it with a real service-version pin from the Service object. If the guard stays, update test_clean_old_agent_400 to exercise the real pinning mechanism rather than mocking can_send_version.","commit_id":"f04cd6a850a5ec8d1addffc5ebef2eef63450c78"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"dfe18cb7e8510a1438384b786d15f34540c219f0","unresolved":false,"context_lines":[{"line_number":253,"context_line":"                status_code\u003dHTTPStatus.CONFLICT,"},{"line_number":254,"context_line":"            )"},{"line_number":255,"context_line":""},{"line_number":256,"context_line":"        agent_api \u003d agent_rpcapi.AgentAPI()"},{"line_number":257,"context_line":"        if not agent_api.client.can_send_version(\u00271.1\u0027):"},{"line_number":258,"context_line":"            raise wsme.exc.ClientSideError("},{"line_number":259,"context_line":"                \u0027Cleanup not available: the cyborg-agent does not support \u0027"}],"source_content_type":"text/x-python","patch_set":18,"id":"5fd37b70_e2314689","line":256,"updated":"2026-08-18 07:03:18.000000000","message":"The clean() handler constructs cyborg.agent.rpcapi.AgentAPI() and gates the request on agent_api.client.can_send_version(\u00271.1\u0027), promising HTTP 400 with \u0027the cyborg-agent does not support the cleanup RPC (cluster pinned below 1.1)\u0027. In oslo.messaging, can_send_version() compares the requested version against the client\u0027s version_cap, which AgentAPI sets to its own hardcoded class constant RPC_API_VERSION \u003d \u00271.1\u0027 (cyborg/agent/rpcapi.py:32,50). Cyborg has no live agent version pinning mechanism (no equivalent of nova\u0027s compute version tracker), so a client built from this API service\u0027s code always has cap \u00271.1\u0027, and the check returns True in every deployment running this code, regardless of the versions of deployed cyborg-agents. The 400 path is unreachable; the documented old-agent behavior cannot occur.\n\n**Severity**: WARNING | **Confidence**: 0.85\n\n**Impact**: During a rolling upgrade (new API/conductor, old 1.0 agents), an operator retrying cleanup on an error device gets 202 Accepted, but the conductor-side check then silently resets the device to error with only a log line. The caller-visible contract in the commit message and the tested 400 behavior can never happen. The check also duplicates the agent-capability decision already owned by conductor.dispatch_cleanup, so the two implementations can drift (maintenance risk of parallel rule ownership).\n\n**Suggestion**:\nRemove the API-layer AgentAPI check (and test_clean_old_agent_400) and let conductor.dispatch_cleanup own agent capability handling, surfacing its outcome through device_state; or, if an up-front 400 is required, implement a real capability signal (e.g. track deployed agent RPC versions like nova\u0027s service version registry) instead of the local version_cap. At minimum, correct the commit message/docs so they do not promise an unreachable 400.","commit_id":"fc72713e43eefa6c017262beabc42e42e4d73e5b"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"75505a88234a0c5fa8ff839062724a1c6e5d279e","unresolved":false,"context_lines":[{"line_number":215,"context_line":""},{"line_number":216,"context_line":"    @authorize_wsgi.authorize_wsgi(\"cyborg:device\", \"clean\")"},{"line_number":217,"context_line":"    @expose.expose(None, wtypes.text, status_code\u003dHTTPStatus.ACCEPTED)"},{"line_number":218,"context_line":"    def clean(self, uuid):"},{"line_number":219,"context_line":"        if not api.request.version.minor \u003e\u003d versions.MINOR_4_DEVICE_STATE:"},{"line_number":220,"context_line":"            pecan.abort(HTTPStatus.NOT_FOUND)"},{"line_number":221,"context_line":""}],"source_content_type":"text/x-python","patch_set":19,"id":"31581305_1ae2826a","line":218,"updated":"2026-08-21 12:32:56.000000000","message":"The change adds a public REST endpoint (POST /v2/devices/{uuid}/clean) and a new response field (device_state), but does not update the project\u0027s API reference at api-ref/source/devices.inc or api-ref/source/parameters.yaml. Cyborg maintains a rendered api-ref (tox env \u0027api-ref\u0027) that currently documents list/get devices including the 2.3 \u0027status\u0027 field, and the existing POST disable/enable custom actions. The new endpoint, its 202/400/409 response codes, and the device_state response parameter are absent, and no device_state parameter exists alongside device_status_resp. Only rest_api_version_history.rst and the release note were updated.\n\n**Severity**: WARNING | **Confidence**: 0.9\n\n**Impact**: API consumers and operators reading the published OpenStack api-ref will not discover the cleanup endpoint or the device_state field added in 2.4; the documentation drifts from the implemented API and future api-ref reviewers cannot see the new contract.\n\n**Suggestion**:\nAdd a \u0027Clean Device\u0027 section to api-ref/source/devices.inc (rest_method POST /v2/devices/{device_uuid}/clean, response codes 202/400/401/403/404/409, microversion 2.4 note) and add a device_state response parameter to parameters.yaml, listing device_state in the list/get responses.","commit_id":"17fd7a3890967c2ed7feb8cb2ffd3b2b222c7874"}],"cyborg/api/rest_api_version_history.rst":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"3bd751115d54d38cc884a9f1ee17655865b00486","unresolved":false,"context_lines":[{"line_number":46,"context_line":" - GET: /devices"},{"line_number":47,"context_line":" - GET: /devices/{uuid}"},{"line_number":48,"context_line":""},{"line_number":49,"context_line":"2.4"},{"line_number":50,"context_line":"---"},{"line_number":51,"context_line":""},{"line_number":52,"context_line":"Add ``device_state`` field to device API responses. The field indicates"}],"source_content_type":"text/x-rst","patch_set":2,"id":"2d700aac_8950e658","line":49,"updated":"2026-08-06 11:33:45.000000000","message":"The rest_api_version_history.rst 2.4 section only documents the device_state field on GET responses but does not mention the new POST /devices/{uuid}/clean endpoint that is also introduced in this microversion.\n\n**Severity**: WARNING | **Confidence**: 0.9\n\n**Impact**: API consumers and SDK authors relying on the version history to discover available endpoints will not know the clean endpoint exists or that it was introduced in microversion 2.4.\n\n**Suggestion**:\nAdd a bullet point under the 2.4 section documenting the POST /devices/{uuid}/clean endpoint, including its purpose (trigger device cleanup) and that it requires admin privileges.","commit_id":"f2035bd1d024d8e5dc0ba03962426e9ed4e14bd1"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"3bd751115d54d38cc884a9f1ee17655865b00486","unresolved":false,"context_lines":[{"line_number":50,"context_line":"---"},{"line_number":51,"context_line":""},{"line_number":52,"context_line":"Add ``device_state`` field to device API responses. The field indicates"},{"line_number":53,"context_line":"the device lifecycle state (available, allocated, cleaning, error)."},{"line_number":54,"context_line":""},{"line_number":55,"context_line":" - GET: /devices"},{"line_number":56,"context_line":" - GET: /devices/{uuid}"}],"source_content_type":"text/x-rst","patch_set":2,"id":"e03532ef_2ff48cdd","line":53,"updated":"2026-08-06 11:33:45.000000000","message":"The rest_api_version_history.rst 2.4 entry documents device_state as \u0027(available, allocated, cleaning, error)\u0027 but the actual DEVICE_STATES tuple in constants.py includes five states, the missing one being \u0027pending_cleaning\u0027.\n\n**Severity**: SUGGESTION | **Confidence**: 0.9\n\n**Benefit**: Clients parsing the documented state list will not handle the \u0027pending_cleaning\u0027 state, potentially causing unexpected behavior or display errors when a device is in that transitional state.\n\n**Recommendation**:\nUpdate the parenthetical in the 2.4 version history entry to include \u0027pending_cleaning\u0027, e.g. \u0027(available, allocated, pending_cleaning, cleaning, error)\u0027.","commit_id":"f2035bd1d024d8e5dc0ba03962426e9ed4e14bd1"}],"cyborg/policies/devices.py":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"cfd08857810958ad0b26d1def610702ef671f5b2","unresolved":false,"context_lines":[{"line_number":78,"context_line":"        scope_types\u003d[\u0027project\u0027],"},{"line_number":79,"context_line":"    ),"},{"line_number":80,"context_line":"    policy.DocumentedRuleDefault("},{"line_number":81,"context_line":"        name\u003d\u0027cyborg:device:clean\u0027,"},{"line_number":82,"context_line":"        check_str\u003dbase.ADMIN,"},{"line_number":83,"context_line":"        description\u003d\u0027Trigger device cleanup\u0027,"},{"line_number":84,"context_line":"        operations\u003d["}],"source_content_type":"text/x-python","patch_set":14,"id":"1a6d8dab_1afb47fb","line":81,"updated":"2026-08-13 15:06:30.000000000","message":"The patch adds the admin-only policy rule cyborg:device:clean but adds no tests in cyborg/tests/unit/policies/test_devices.py, which exhaustively covers every other device rule (get_all, get_one, disable, enable) with authorized/unauthorized persona contexts.\n\n**Severity**: WARNING | **Confidence**: 0.9\n\n**Impact**: A regression in the clean policy (e.g. wrong check_str or scope handling) would not be caught by CI; reviewers and operators cannot verify the admin-only claim in the commit message.\n\n**Suggestion**:\nAdd cyborg:device:clean to the write_authorized/write_unauthorized context lists in cyborg/tests/unit/policies/test_devices.py and add success/forbidden/system-scope tests for POST /v2/devices/{uuid}/clean, mirroring the existing test_disable_device_* pattern.","commit_id":"20c065008011362e300a5fcc6b67f0b50da62800"}],"cyborg/tests/unit/api/controllers/v2/test_devices.py":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"3bd751115d54d38cc884a9f1ee17655865b00486","unresolved":false,"context_lines":[{"line_number":173,"context_line":"        self.assertEqual(202, response.status_int)"},{"line_number":174,"context_line":"        mock_cond_cls.return_value.dispatch_cleanup.assert_called_once()"},{"line_number":175,"context_line":""},{"line_number":176,"context_line":"    @mock.patch(\u0027cyborg.objects.Device.get\u0027)"},{"line_number":177,"context_line":"    def test_clean_available_409(self, mock_get):"},{"line_number":178,"context_line":"        device \u003d self.fake_devices[0]"},{"line_number":179,"context_line":"        device.type \u003d \u0027NVME\u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"b784c743_f7416370","line":176,"updated":"2026-08-06 11:33:45.000000000","message":"The clean endpoint has three distinct 409 CONFLICT response paths (available, allocated, cleaning/pending_cleaning), but tests only cover the \u0027available\u0027 409 path. The \u0027allocated\u0027 and \u0027cleaning/pending_cleaning\u0027 409 paths are untested.\n\n**Severity**: SUGGESTION | **Confidence**: 0.8\n\n**Benefit**: Regression in the allocated or cleaning/pending_cleaning guard logic could go undetected, allowing cleanup to be dispatched for a device that is in use or already being cleaned.\n\n**Recommendation**:\nAdd test methods for: (1) a device with device_state\u003d\u0027allocated\u0027 expecting 409, and (2) a device with device_state\u003d\u0027cleaning\u0027 or \u0027pending_cleaning\u0027 expecting 409, following the pattern of test_clean_available_409.","commit_id":"f2035bd1d024d8e5dc0ba03962426e9ed4e14bd1"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"7d646c8a4c65c133ba0b7748ad1f5de322d3b435","unresolved":false,"context_lines":[{"line_number":179,"context_line":"            expect_errors\u003dFalse,"},{"line_number":180,"context_line":"        )"},{"line_number":181,"context_line":"        self.assertEqual(202, response.status_int)"},{"line_number":182,"context_line":"        mock_cond_cls.return_value.dispatch_cleanup.assert_called_once()"},{"line_number":183,"context_line":""},{"line_number":184,"context_line":"    @mock.patch(\u0027cyborg.objects.Device.get\u0027)"},{"line_number":185,"context_line":"    def test_clean_available_409(self, mock_get):"}],"source_content_type":"text/x-python","patch_set":7,"id":"0063070a_5a69d7b4","line":182,"updated":"2026-08-07 17:05:52.000000000","message":"The test_clean_error_device_202 test asserts dispatch_cleanup was called once but does not verify the correct device object was passed as an argument, weakening the test\u0027s ability to catch regressions in the dispatch logic.\n\n**Severity**: SUGGESTION | **Confidence**: 0.8\n\n**Benefit**: A regression where dispatch_cleanup receives the wrong device or None could pass this test silently, since only the call count is verified.\n\n**Recommendation**:\nStrengthen the assertion to check arguments, e.g. mock_cond_cls.return_value.dispatch_cleanup.assert_called_once_with(mock.ANY, device), to verify the correct device is dispatched to the conductor.","commit_id":"db6dced176de1fa1ebf37f7a37dc5b8802358172"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"699f864cd16b0ff55015982e0a479fe6e1bf4b9b","unresolved":false,"context_lines":[{"line_number":127,"context_line":"        for in_device, out_device in zip(self.fake_devices, out_devices):"},{"line_number":128,"context_line":"            self._validate_device(in_device, out_device)"},{"line_number":129,"context_line":""},{"line_number":130,"context_line":"    @mock.patch(\u0027cyborg.objects.Device.get\u0027)"},{"line_number":131,"context_line":"    def test_get_device_new_version_has_device_state(self, mock_device):"},{"line_number":132,"context_line":"        in_device \u003d self.fake_devices[0]"},{"line_number":133,"context_line":"        in_device.device_state \u003d \u0027available\u0027"}],"source_content_type":"text/x-python","patch_set":16,"id":"5cee8469_3c32a747","line":130,"updated":"2026-08-15 17:22:10.000000000","message":"The new 409 tests never assert the per-state messages the commit message documents, and no test verifies that a non-admin receives 403 for POST /clean, which is the only enforcement of the new cyborg:device:clean policy.\n\n**Severity**: WARNING | **Confidence**: 0.8\n\n**Impact**: Removing the authorize_wsgi decorator from clean() or loosening the policy would not fail any test, and the documented per-state messages are unverified, so the API contract can silently drift.\n\n**Suggestion**:\nAdd a test posting to /clean with non-admin headers asserting 403, and assert the expected faultstring (\u0027Device is already clean\u0027, \u0027Device is bound to an instance\u0027, \u0027Device is already being cleaned\u0027) for each 409 state.","commit_id":"80fc5b6a7e1acdc75c5a241dde45e3a7cd0131e2"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"699f864cd16b0ff55015982e0a479fe6e1bf4b9b","unresolved":false,"context_lines":[{"line_number":159,"context_line":"        self.assertNotIn(\u0027device_state\u0027, out_device)"},{"line_number":160,"context_line":""},{"line_number":161,"context_line":"    @mock.patch(\u0027cyborg.api.controllers.v2.devices.agent_rpcapi.AgentAPI\u0027)"},{"line_number":162,"context_line":"    @mock.patch("},{"line_number":163,"context_line":"        \u0027cyborg.api.controllers.v2.devices.conductor_rpcapi.ConductorAPI\u0027"},{"line_number":164,"context_line":"    )"},{"line_number":165,"context_line":"    @mock.patch(\u0027cyborg.objects.Device.get\u0027)"}],"source_content_type":"text/x-python","patch_set":16,"id":"75f97543_7e76f59d","line":162,"updated":"2026-08-15 17:22:10.000000000","message":"The new tests patch cyborg.api.controllers.v2.devices.agent_rpcapi.AgentAPI and conductor_rpcapi.ConductorAPI without autospec\u003dTrue, so the mocks accept any attribute or signature.\n\n**Severity**: SUGGESTION | **Confidence**: 0.8\n\n**Benefit**: A rename of dispatch_cleanup or can_send_version on the RPC API classes would not fail these tests, weakening the regression net around the new endpoint.\n\n**Recommendation**:\nAdd autospec\u003dTrue to the class-level patches (or patch the specific methods with autospec) so signature and attribute drift fails fast.","commit_id":"80fc5b6a7e1acdc75c5a241dde45e3a7cd0131e2"}],"doc/api_samples/devices/devices-getone-resp-v24.json":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"cfd08857810958ad0b26d1def610702ef671f5b2","unresolved":false,"context_lines":[{"line_number":6,"context_line":"    \"std_board_info\": \"{\u0027device_id\u0027: \u00270xabcd\u0027, \u0027class\u0027: \u0027Fake class\u0027}\","},{"line_number":7,"context_line":"    \"vendor_board_info\": \"fake_vendor_info\","},{"line_number":8,"context_line":"    \"hostname\": \"computenode\","},{"line_number":9,"context_line":"    \"status\": \"available\","},{"line_number":10,"context_line":"    \"device_state\": \"available\","},{"line_number":11,"context_line":"    \"created_at\": \"2020-03-13T02:26:31+00:00\","},{"line_number":12,"context_line":"    \"updated_at\": null,"}],"source_content_type":"application/json","patch_set":14,"id":"d3f584a1_f4cb3fb6","line":9,"updated":"2026-08-13 15:06:30.000000000","message":"The new functional test test_devices_get_one_v24 compares response structure to devices-getone-resp-v24.json, but seed_devices() never sets device_state, so the real response contains device_state: null and the _structure() helper treats null as a wildcard. The sample\u0027s \"available\" value is never...\n\n**Severity**: WARNING | **Confidence**: 0.8\n\n**Impact**: The published v2.4 sample shows device_state \u0027available\u0027 for an FPGA device while the real seeded response emits null; a future change that drops the field or emits a wrong value would still pass the structural check, weakening the microversion guard test.\n\n**Suggestion**:\nSet device_state explicitly in seed_devices() (e.g. device_state\u003d\u0027available\u0027) so the v2.4 sample is actually validated, or assert the concrete value in the test; keep the sample consistent with what the seeded fixture really returns.","commit_id":"20c065008011362e300a5fcc6b67f0b50da62800"}],"releasenotes/notes/add-device-state-api-microversion-24-d4e5f6a7b8c9.yaml":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"5b51f3ef47e2abcb2ed6405687c8e47d7beae5e9","unresolved":false,"context_lines":[{"line_number":6,"context_line":"    state (``available``, ``allocated``, ``pending_cleaning``,"},{"line_number":7,"context_line":"    ``cleaning``, ``error``) and is omitted for older microversions."},{"line_number":8,"context_line":"  - |"},{"line_number":9,"context_line":"    A new ``POST /v2/devices/{uuid}/clean`` endpoint is available under"},{"line_number":10,"context_line":"    microversion 2.4.  It allows administrators to manually trigger"},{"line_number":11,"context_line":"    cleanup on a device in ``error`` state.  The endpoint requires"},{"line_number":12,"context_line":"    admin privileges and returns ``202 Accepted``; cleanup proceeds"}],"source_content_type":"text/x-yaml","patch_set":9,"id":"375cc86d_4f36ed2c","line":9,"updated":"2026-08-11 10:18:14.000000000","message":"The release note for the clean endpoint documents 202 Accepted and 409 Conflict but omits the 400 Bad Request returned for non-cleanable device types (e.g., FPGA). The API version history rst file mentions it, but operators reading the release note will not know about the 400 case.\n\n**Severity**: WARNING | **Confidence**: 0.8\n\n**Impact**: Operators may be surprised by a 400 response when attempting to clean non-NVMe devices, leading to confusion or support tickets.\n\n**Suggestion**:\nAdd a sentence to the release note mentioning that calling the endpoint on a device type that does not support cleaning (currently only NVMe) returns 400 Bad Request.","commit_id":"61e007ac6ea6de88b19460efb41d660a825d3e6e"}]}
