fix(client): fail closed on polling configuration drift
This commit is contained in:
@@ -353,14 +353,24 @@ class LocalStateStore:
|
||||
row = connection.execute(
|
||||
"""SELECT service_url,device_id,adb_path,adb_serial,transport,
|
||||
poll_interval_seconds,failure_threshold,http_timeout_seconds,step_timeout_seconds,
|
||||
CASE WHEN length(device_token_cipher) > 0 THEN 1 ELSE 0 END
|
||||
typeof(device_token_cipher),length(device_token_cipher)
|
||||
FROM profiles WHERE profile_id=?""",
|
||||
(profile_id,),
|
||||
).fetchone()
|
||||
if row is None:
|
||||
raise StateError("profile_not_found")
|
||||
settings = ProfileSettings(profile_id, *row[:9])
|
||||
return ProfileSummary(settings, bool(row[9]))
|
||||
# summary 不解密,但也不能把损坏的密文降级成“尚未保存”。否则 UI 会
|
||||
# 允许覆盖本应进入人工恢复的本地状态。DPAPI 密文长度不固定,只要求
|
||||
# SQLite storage class 确为 BLOB 且非空。
|
||||
if row[9] != "blob" or type(row[10]) is not int or row[10] <= 0:
|
||||
raise StateError("invalid_device_token_cipher")
|
||||
try:
|
||||
settings = ProfileSettings(profile_id, *row[:9])
|
||||
except (TypeError, ValueError, ValidationError):
|
||||
# 存储字段损坏不得把裸模型异常或实际值带到 UI;也不得以默认配置
|
||||
# 继续启动。配置修复必须显式进行。
|
||||
raise StateError("stored_profile_invalid") from None
|
||||
return ProfileSummary(settings, True)
|
||||
|
||||
def start_or_resume_polling(self, profile_id: str) -> PollingSession:
|
||||
now = self._utc_now()
|
||||
|
||||
@@ -51,7 +51,7 @@ class ClaimGateway(Protocol):
|
||||
|
||||
|
||||
class ExecutionConsumer(Protocol):
|
||||
def accept_claim(self, claimed: ClaimedTask) -> None: ...
|
||||
def accept_claim(self, claimed: ClaimedTask, profile: ProfileSettings) -> None: ...
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -469,9 +469,12 @@ class PollingCoordinator(QObject):
|
||||
return
|
||||
try:
|
||||
consumer = self._consumer
|
||||
if consumer is None:
|
||||
frozen_profile = self._frozen_profile
|
||||
if consumer is None or frozen_profile is None:
|
||||
raise RuntimeError("execution_consumer_missing")
|
||||
consumer.accept_claim(claimed)
|
||||
# consumer 只能使用本次显式 Start 冻结的不可变配置;不得在
|
||||
# 已领取后回读可变 UI/store,否则 ADB 身份和超时会发生趟内漂移。
|
||||
consumer.accept_claim(claimed, frozen_profile)
|
||||
except Exception:
|
||||
self._request_stop(
|
||||
PollingState.RECOVERY_REQUIRED,
|
||||
|
||||
@@ -5,8 +5,8 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
from typing import Protocol
|
||||
|
||||
from PySide6.QtCore import QRegularExpression, Qt, Signal, Slot
|
||||
from PySide6.QtGui import QAction, QKeySequence, QRegularExpressionValidator
|
||||
from PySide6.QtCore import Qt, Signal, Slot
|
||||
from PySide6.QtGui import QAction, QKeySequence
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
QFormLayout,
|
||||
@@ -77,9 +77,11 @@ class SettingsPage(QScrollArea):
|
||||
self.device_token = QLineEdit()
|
||||
self.device_token.setObjectName("deviceToken")
|
||||
self.device_token.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
self.device_token.setMaxLength(64)
|
||||
# 控件只负责给输入设置合理上限;长度与字符集必须在 save() 中显式
|
||||
# 验证。若这里限制为 64,粘贴 65 位 token 会被 Qt 静默截成合法
|
||||
# 64 位并覆盖原凭据。
|
||||
self.device_token.setMaxLength(256)
|
||||
self.device_token.setPlaceholderText("首次必填;已有凭据时留空表示保留")
|
||||
self.device_token.setValidator(QRegularExpressionValidator(QRegularExpression("[0-9a-f]{0,64}"), self))
|
||||
form.addRow("设备 token", self.device_token)
|
||||
|
||||
self.token_status = QLabel()
|
||||
|
||||
Reference in New Issue
Block a user