From 24ccd21f516ecdb3772357b7db3bd9f7f0637105 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 04:16:10 +0800 Subject: [PATCH 1/2] fix(extensions): keep unverified legacy status readable Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/extensions/runtime.py | 9 ++-- tests/extensions/test_extension_runtime.py | 57 ++++++++++++++++++++++ tests/test_local_state_migration.py | 17 ++++++- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/loopx/extensions/runtime.py b/loopx/extensions/runtime.py index 9c52db6413..c87e9da777 100644 --- a/loopx/extensions/runtime.py +++ b/loopx/extensions/runtime.py @@ -478,11 +478,12 @@ def extension_status( "enabled": bool(entry.get("enabled")), "active_revision": entry.get("active_revision"), "rollback_available": bool(entry.get("rollback_revision")), - # Readiness is the same predicate the catalog and dispatch use: - # a stale proof under an incompatible API is not "verified". + # Missing legacy metadata is readable but unverified. Resolve + # its proof before parsing the active manifest; a valid proof + # must still match this runtime's extension API. "doctor_verified": bool(entry.get("enabled")) - and _active_manifest_incompatibility(entry) is None - and _verified_entrypoint(entry) is not None, + and _verified_entrypoint(entry) is not None + and _active_manifest_incompatibility(entry) is None, "revision_count": len(entry.get("revisions") or []), } for entry in visible diff --git a/tests/extensions/test_extension_runtime.py b/tests/extensions/test_extension_runtime.py index 107138affd..a0614f96d2 100644 --- a/tests/extensions/test_extension_runtime.py +++ b/tests/extensions/test_extension_runtime.py @@ -1487,6 +1487,63 @@ def test_install_disable_upgrade_and_rollback_preserve_verified_binding( ] +@pytest.mark.parametrize("enabled", [True, False]) +@pytest.mark.parametrize( + "damage", + ["legacy", "invalid_history", "missing_revision", "invalid_manifest", "no_proof", "forged_proof"], +) +def test_status_reads_unverified_records_without_affecting_healthy_extensions( + tmp_path: Path, damage: str, enabled: bool, +) -> None: + provider = _provider(tmp_path / "provider") + state_file = tmp_path / "extensions.json" + for extension_id in ("test-semantic-extension", "healthy-extension"): + manifest = _manifest( + tmp_path / f"{extension_id}.toml", entrypoint=provider, + version="1.0.0", extension_id=extension_id, + ) + install_extension(manifest, state_file=state_file, execute=True) + state = json.loads(state_file.read_text(encoding="utf-8")) + entry = state["extensions"]["test-semantic-extension"] + if damage == "legacy": + entry.clear() + entry["id"] = "test-semantic-extension" + elif damage == "invalid_history": + entry["revisions"] = {} + elif damage == "missing_revision": + entry["revisions"] = [] + elif damage == "invalid_manifest": + entry["revisions"][0]["manifest"] = None + elif damage == "no_proof": + entry.pop("doctor_verified_revision") + else: + entry["doctor_verified_entrypoint_identities"] = ["unprobed-entrypoint"] + entry["enabled"] = enabled + state_file.write_text(json.dumps(state), encoding="utf-8") + original = state_file.read_bytes() + + statuses = extension_status(state_file=state_file)["extensions"] + by_id = {status["id"]: status for status in statuses} + assert by_id["healthy-extension"]["doctor_verified"] is True + assert by_id["test-semantic-extension"]["enabled"] is enabled + assert by_id["test-semantic-extension"]["doctor_verified"] is False + assert extension_status( + state_file=state_file, extension_id="test-semantic-extension", + )["extensions"] == [by_id["test-semantic-extension"]] + with pytest.raises(ValueError): + resolve_extension_activation("test-semantic-extension", state_file=state_file) + with pytest.raises(ValueError): + resolve_extension_binding( + "test-semantic-extension", state_file=state_file, + capability_id="semantic-preference", protocol="semantic_preference_provider_v0", + permission="semantic_preference.read", + ) + if damage in {"legacy", "invalid_history", "missing_revision", "invalid_manifest"}: + with pytest.raises(ValueError): + enable_extension("test-semantic-extension", state_file=state_file, execute=True) + assert state_file.read_bytes() == original + + def test_installed_snapshot_fails_closed_when_the_runtime_api_moves( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: diff --git a/tests/test_local_state_migration.py b/tests/test_local_state_migration.py index fb5a7e2ce1..7dd1136f20 100644 --- a/tests/test_local_state_migration.py +++ b/tests/test_local_state_migration.py @@ -614,9 +614,22 @@ def test_extension_cli_keeps_legacy_machine_state_without_a_global_registry(tmp_ "schema_version": "loopx_extension_state_v0", "extensions": {"example": {"id": "example", "enabled": True}}, }) - assert _list_extensions(source.parents[1])[0]["id"] == "example" + state_file = source / "extensions" / "state.json" + original = state_file.read_bytes() + [status] = _list_extensions(source.parents[1]) + assert status["id"] == "example" + assert status["enabled"] is True + assert status["doctor_verified"] is False + assert state_file.read_bytes() == original + refused = _extension_cli_result(source.parents[1], "enable", "example", "--execute") + assert refused.returncode == 2 + assert "extension revision history is invalid" in refused.stdout + assert state_file.read_bytes() == original assert _extension_cli(source.parents[1], "disable", "example", "--execute")["changed"] - assert _read(source / "extensions" / "state.json")["extensions"]["example"]["enabled"] is False + [disabled] = _list_extensions(source.parents[1]) + assert disabled["enabled"] is False + assert disabled["doctor_verified"] is False + assert _read(state_file)["extensions"]["example"] == {"id": "example", "enabled": False} assert not target.exists() From 00fcd1a99ea9dfe25166866d49271ca4cda4407b Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 15 Oct 2026 04:31:31 +0800 Subject: [PATCH 2/2] test(lark): inject atomic failures at the shared file owner Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- tests/extensions/test_lark_goal_channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/extensions/test_lark_goal_channel.py b/tests/extensions/test_lark_goal_channel.py index 9fabb1efd4..18b926402a 100644 --- a/tests/extensions/test_lark_goal_channel.py +++ b/tests/extensions/test_lark_goal_channel.py @@ -439,7 +439,7 @@ def fail_replace(source: object, destination: object) -> None: raise OSError("fixture replace failure") monkeypatch.setattr( - "loopx.extensions.lark.private_json.os.replace", + "loopx.file_lock.os.replace", fail_replace, )