feat(lora): the LoRA save takes the settings revision

POST /prompt_manager/lora/enable accepts the optional expected_revision
that GET /settings returns, so the System Save row refuses to write over
a config.json changed elsewhere (settings-changed, nothing written). The
legacy page sends none and still merges into what is on disk.
This commit is contained in:
Vito Sansevero
2026-10-25 06:57:01 -07:00
parent 6a2dae0f78
commit 818f2e22e1
2 changed files with 51 additions and 1 deletions
+9 -1
View File
@@ -168,6 +168,9 @@ class LoraIntegrationMixin:
async def lora_enable(self, request):
"""Enable or disable LoraManager integration and save it to config.json.
``expected_revision`` (optional, GET /settings' ``revision``) refuses
a save over a config.json changed meanwhile (``settings-changed``).
The values are committed through the config store first (#220) and
published at runtime only once config.json holds them; a save that
cannot be written reports ``persisted: false`` and changes nothing.
@@ -180,6 +183,11 @@ class LoraIntegrationMixin:
for flag in _LORA_FLAGS:
if flag in data and not isinstance(data[flag], bool):
return self._bad_lora_request(f"{flag} must be true or false")
# The System Save row sends the revision it read (#220); the
# legacy page sends none and merges into what is on disk.
expected_revision = data.get("expected_revision")
if expected_revision is not None and not isinstance(expected_revision, str):
return self._bad_lora_request("expected_revision must be a string")
enabled = data.get("enabled", False)
path = data.get("path", "")
trigger_words = data.get("trigger_words_enabled", False)
@@ -226,7 +234,7 @@ class LoraIntegrationMixin:
_saved, revision = await self._run_in_executor(
get_config_store().commit,
_lora_settings_mutation(values),
expected_revision=None,
expected_revision=expected_revision,
publish=publish,
)
except (ConfigUnreadable, RevisionConflict, OSError) as exc:
+42
View File
@@ -528,6 +528,48 @@ class TestLoraEnableRoute(LoraAPITestCase):
self.assertEqual(self.config_file.read_bytes(), b"{not json")
self.assertFalse(IntegrationConfig.LORA_TRIGGER_WORDS_ENABLED)
async def test_enable_with_a_stale_revision_changes_nothing(self):
# System S10: the Save row sends the revision it read; a file changed
# since is a settings-changed conflict and nothing is written.
self.config_file.write_text(json.dumps({"web_ui": {"result_timeout": 9}}))
resp = await self.client.request("GET", "/prompt_manager/settings")
revision = (await resp.json())["revision"]
self.config_file.write_text(json.dumps({"web_ui": {"result_timeout": 10}}))
before = self.config_file.read_bytes()
resp = await self._post_json(
"/prompt_manager/lora/enable",
{
"enabled": False,
"trigger_words_enabled": True,
"expected_revision": revision,
},
)
body = await resp.json()
self.assertEqual((resp.status, body["code"]), (409, "settings-changed"))
self.assertEqual(self.config_file.read_bytes(), before)
self.assertFalse(IntegrationConfig.LORA_TRIGGER_WORDS_ENABLED)
resp = await self._post_json(
"/prompt_manager/lora/enable",
{
"enabled": False,
"trigger_words_enabled": True,
"expected_revision": body["revision"],
},
)
saved = await resp.json()
self.assertEqual(resp.status, 200)
self.assertTrue(saved["revision"].startswith("sha256:"))
self.assertTrue(IntegrationConfig.LORA_TRIGGER_WORDS_ENABLED)
resp = await self._post_json(
"/prompt_manager/lora/enable",
{"enabled": False, "expected_revision": 7},
)
self.assertEqual(resp.status, 400)
async def test_enable_requires_boolean_flags_and_body_cap(self):
for body in (
{"enabled": "yes"},