Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions loopx/extensions/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions tests/extensions/test_extension_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion tests/extensions/test_lark_goal_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down
17 changes: 15 additions & 2 deletions tests/test_local_state_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


Expand Down
Loading