Add a Details entry for HAL 9000 describing the plan lifecycle
concurrency race-condition fix (#7989) — wiring LockService into
execute_plan/apply_plan with unique per-invocation owner identities.
ISSUES CLOSED: #7989
The original implementation used plan_id as the owner_id when acquiring
the advisory lock. Because LockService treats owner_id as the caller
identity and allows re-entrant acquisition for the same owner, concurrent
sessions attempting to lock the same plan would all present the same
owner_id and thus silently renew the lock instead of raising
LockConflictError.
This fix generates a unique UUID for each invocation as the owner_id,
ensuring that concurrent sessions present different owners and thus
trigger LockConflictError when attempting to acquire the same plan lock.
The lock is still acquired before the phase transition and released in
a finally block to ensure cleanup even on error.
ISSUES CLOSED: #8067
LockService was implemented but never integrated into the plan execution
path, leaving execute_plan() and apply_plan() unprotected against
concurrent calls on the same plan_id (race condition, issue #7989).
Changes:
- container.py: add _build_lock_service() factory and register
LockService as a Singleton provider; inject it into
PlanLifecycleService via the DI container.
- plan_lifecycle_service.py: accept optional lock_service parameter in
__init__; in execute_plan() and apply_plan() acquire a plan-level
advisory lock before the critical section and release it in a finally
block so the lock is always freed even when exceptions occur.
When lock_service is None (existing tests without DI wiring) the
behaviour is unchanged — locking is silently skipped for backward
compatibility.
Closes#7989