diff --git a/docs/1c-adapter-api-contract.md b/docs/1c-adapter-api-contract.md index 8d355e8..eeedbd9 100644 --- a/docs/1c-adapter-api-contract.md +++ b/docs/1c-adapter-api-contract.md @@ -38,7 +38,8 @@ by default) and may be explicitly cancelled with `repository.lock.request.cancel`. Manual confirmations expire after `ONEC_REPOSITORY_CONFIRMATION_TTL_SECONDS` (2 hours by default). After the user releases the objects in Configurator, `repository.lock.close` closes the -confirmation and immediately blocks further SQL writes through that session. +confirmation, marks its originating request as `closed`, and immediately blocks +further SQL writes through that session. The adapter keeps a bounded audit trail of request creation, confirmation, cancellation, and closure and exposes it to the administrative requests view. diff --git a/plugins/1c/connector/repository_control.py b/plugins/1c/connector/repository_control.py index f8b4087..93448ce 100644 --- a/plugins/1c/connector/repository_control.py +++ b/plugins/1c/connector/repository_control.py @@ -538,6 +538,7 @@ def confirm_manual_lock(payload: dict[str, Any]) -> dict[str, Any]: "base_id": base_id, "layer": str(config.get("layer") or "base"), "backend": config.get("backend"), "objects": plan["lock_objects"], "created_at": time.time(), "status": "manual_confirmed", "verification": "user_confirmation_only", "automatically_verified": False, + **({"request_id": request_id} if request_id else {}), } if isinstance(request, dict): request["status"] = "confirmed_by_user" @@ -571,13 +572,34 @@ def close_manual_lock(payload: dict[str, Any]) -> dict[str, Any]: session = (state.get("sessions") or {}).get(session_id) if not isinstance(session, dict): return {"schema": "onec_repository_lock_close.v1", "method": METHOD_CLOSE, "status": "not_found", "lock_session_id": session_id} - if session.get("status") != "manual_confirmed": + if session.get("status") not in {"manual_confirmed", "closed"}: return {"schema": "onec_repository_lock_close.v1", "method": METHOD_CLOSE, "status": "blocked", "error": "manual_confirmation_not_active", "lock_session_id": session_id} + already_closed = session.get("status") == "closed" session["status"] = "closed" - session["closed_at"] = time.time() - _audit(state, "manual_lock_closed", lock_session_id=session_id, base_id=session.get("base_id"), objects=session.get("objects")) + closed_at = float(session.get("closed_at") or time.time()) + session["closed_at"] = closed_at + request_id = str(session.get("request_id") or "").strip() + request = (state.get("requests") or {}).get(request_id) if request_id else None + if not isinstance(request, dict): + request_id = next( + ( + str(candidate_id) + for candidate_id, candidate in (state.get("requests") or {}).items() + if isinstance(candidate, dict) and candidate.get("lock_session_id") == session_id + ), + "", + ) + request = (state.get("requests") or {}).get(request_id) if request_id else None + if isinstance(request, dict): + request["status"] = "closed" + request["closed_at"] = closed_at + if not already_closed: + _audit(state, "manual_lock_closed", request_id=request_id or None, lock_session_id=session_id, base_id=session.get("base_id"), objects=session.get("objects")) _write_state(state) - return {"schema": "onec_repository_lock_close.v1", "method": METHOD_CLOSE, "status": "closed", "lock_session_id": session_id} + return { + "schema": "onec_repository_lock_close.v1", "method": METHOD_CLOSE, "status": "closed", + "lock_session_id": session_id, "request_id": request_id or None, + } def write_gate(payload: dict[str, Any]) -> dict[str, Any]: diff --git a/tests/1c/test_repository_control.py b/tests/1c/test_repository_control.py index 9bd69d4..fb71d29 100644 --- a/tests/1c/test_repository_control.py +++ b/tests/1c/test_repository_control.py @@ -104,6 +104,13 @@ def test_manual_lock_request_stays_pending_until_user_confirms_exact_saved_scope closed = repository_control.close_manual_lock({"lock_session_id": confirmed["lock_session_id"], "user_confirmed_released": True}) assert closed["status"] == "closed" + assert closed["request_id"] == requested["request_id"] + completed = repository_control.lock_request_status({"request_id": requested["request_id"]}) + assert completed["status"] == "closed" + assert completed["request"]["closed_at"] > completed["request"]["confirmed_at"] + closed_again = repository_control.close_manual_lock({"lock_session_id": confirmed["lock_session_id"], "user_confirmed_released": True}) + assert closed_again["status"] == "closed" + assert closed_again["request_id"] == requested["request_id"] assert repository_control.write_gate({"base_id": "base", "lock_session_id": confirmed["lock_session_id"], "repository_object": "РегистрСведений.Настройки"})["allowed"] is False