Implement ACTIVITY-WP-0022/0023: safe sink default and gap closures
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 3s
Build and Publish Container Image / build-and-push (push) Successful in 28s

Default ISSUE_SINK_TYPE to state-hub (no silent Forgejo issues), hard-fail
prune apply without live-images protection, refresh-live-images script,
disable TaskExecutor stub by default, and document consumer/sink contracts.
This commit is contained in:
tegwick 2026-07-21 21:40:08 +02:00
parent 5c7a90ce7c
commit 4f5399df84
19 changed files with 525 additions and 155 deletions

View file

@ -36,4 +36,43 @@ def test_shell_resolver_runs_forgejo_package_prune(tmp_path, monkeypatch) -> Non
)
assert result["kind"] == "forgejo_package_prune"
assert result["candidate_count"] == 2
assert result["candidate_count"] == 2
def test_apply_without_live_images_file_is_rejected(tmp_path) -> None:
"""ACTIVITY-WP-0023-T03: apply=true must not run unprotected."""
script = tmp_path / "forgejo-package-prune"
script.write_text("#!/usr/bin/env bash\necho '{}'\n", encoding="utf-8")
script.chmod(0o755)
try:
CONTEXT_RESOLVER_REGISTRY["shell"]().resolve(
"forgejo_package_prune",
None,
{"prune_script": str(script), "max_versions": 3, "apply": True},
)
raise AssertionError("expected RuntimeError")
except RuntimeError as exc:
assert "live_images_file" in str(exc)
def test_apply_with_empty_live_images_file_is_rejected(tmp_path) -> None:
script = tmp_path / "forgejo-package-prune"
script.write_text("#!/usr/bin/env bash\necho '{}'\n", encoding="utf-8")
script.chmod(0o755)
empty = tmp_path / "live.txt"
empty.write_text("", encoding="utf-8")
try:
CONTEXT_RESOLVER_REGISTRY["shell"]().resolve(
"forgejo_package_prune",
None,
{
"prune_script": str(script),
"apply": True,
"live_images_file": str(empty),
},
)
raise AssertionError("expected RuntimeError")
except RuntimeError as exc:
assert "non-empty" in str(exc) or "empty" in str(exc)

View file

@ -81,6 +81,31 @@ def test_issue_core_rest_sink_posts_task_contract(monkeypatch) -> None:
assert "review_required" not in posts[0]["json"]
def test_default_sink_is_state_hub_not_rest(monkeypatch) -> None:
"""ACTIVITY-WP-0022: unset ISSUE_SINK_TYPE must not open Forgejo/rest."""
from activity_core import issue_sink as mod
monkeypatch.delenv("ISSUE_SINK_TYPE", raising=False)
sink = mod.get_issue_sink()
assert type(sink).__name__ == "StateHubProgressSink"
def test_unknown_sink_type_falls_back_to_state_hub(monkeypatch) -> None:
from activity_core import issue_sink as mod
monkeypatch.setenv("ISSUE_SINK_TYPE", "forgejo-please")
sink = mod.get_issue_sink()
assert type(sink).__name__ == "StateHubProgressSink"
def test_rest_sink_still_available_when_explicit(monkeypatch) -> None:
from activity_core import issue_sink as mod
monkeypatch.setenv("ISSUE_SINK_TYPE", "rest")
sink = mod.get_issue_sink()
assert type(sink).__name__ == "IssueCoreRestSink"
def test_issue_core_rest_sink_requires_api_key() -> None:
sink = IssueCoreRestSink("http://issue-core.test/", api_key="")
with pytest.raises(RuntimeError, match="ISSUE_CORE_API_KEY"):