# Changelog — 监测槽 TTL 到期竞态修复

**日期**：2026-06-12

## 摘要

修复 TTL 到期与后台 `expire_due_pins`（5s 探针循环）之间的竞态窗口：槽位短暂显示为空、无法添加/移除、可能触发唯一约束冲突。

## 动机

`list_active_pins_for_admin` 在 `monitoring_enabled=true` 且 `expires_at<=now` 时排除 pin，但 DB 行仍在且 `monitoring_enabled` 仍为 true，直到探针循环执行 `expire_due_pins`。此窗口内 UI 显示空槽，但 `create_pin` 会因 `uq_watch_pins_admin_slot` 失败。

## 变更

- `WatchService._finalize_due_pins()`：在读/写路径同步到期 pin（暂停 + end session + 按需清 Redis）
- 在 `list_slots`、`pin_server`、`replace_slot`、`remove_slot`、`set_slot_monitoring`、`set_slot_ttl` 入口调用
- 新增单测：`test_watch_pin_ttl_expiry_list_syncs_before_display`、`test_watch_pin_ttl_expiry_pin_server_no_duplicate`
- **P2**：`WatchSlotRow.onRemove` 失败时 snackbar 提示

## 涉及文件

- `server/application/services/watch_service.py`
- `frontend/src/components/watch/WatchSlotRow.vue`
- `tests/test_watch_pins.py`

## 迁移 / 重启

- 无需迁移
- 需重启 API

## 验证

```bash
.venv/bin/pytest tests/test_watch_pins.py -q
```
