fix(register): require explicit topic when ambiguous

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06d83-1cbc-71f2-b0dc-e0f48cedae43
This commit is contained in:
tegwick 2026-09-04 21:16:07 +02:00
parent 803bb95e1d
commit e663f209f7
4 changed files with 209 additions and 4 deletions

View file

@ -16,6 +16,7 @@ from statehub_register import (
project_registration_plan,
refuse_project_flavor_scaffold,
run_register,
_find_or_create_topic,
_invoke_llm,
_normalise_inference,
_parse_json_object,
@ -265,6 +266,7 @@ def _register_args(path: Path, **overrides):
"repo_slug": None,
"wp_prefix": None,
"domain": "infotech",
"topic": None,
"description": "Test project.",
"intent": None,
"api_base": "http://unused",
@ -333,6 +335,120 @@ def test_run_register_existing_project_skips_scaffold_and_templating(
assert not (repo / "INTENT.md").exists()
def test_run_register_honors_explicit_topic_slug(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
repo = tmp_path / "demo-service"
repo.mkdir()
written, registered = _stub_register_io(monkeypatch)
monkeypatch.setattr(
"statehub_register._api_get",
lambda path, *_a, **_k: (
[{"slug": "infotech", "id": "dom-1"}]
if str(path).startswith("/domains")
else [
{"id": "topic-1", "slug": "custodian", "domain_slug": "infotech"},
{"id": "topic-2", "slug": "activity-core", "domain_slug": "infotech"},
]
),
)
run_register(
_register_args(
repo,
topic="activity-core",
intent="Register the demo service with the requested topic.",
)
)
assert written[0]["topic_id"] == "topic-2"
repo_registration = next(item for kind, item in registered if kind == "repo")
assert repo_registration["topic_id"] == "topic-2"
def test_topic_selection_refuses_ambiguous_domain(
monkeypatch: pytest.MonkeyPatch,
):
monkeypatch.setattr(
"statehub_register._api_get",
lambda *_a, **_k: [
{"id": "topic-1", "slug": "custodian", "domain_slug": "infotech"},
{"id": "topic-2", "slug": "activity-core", "domain_slug": "infotech"},
],
)
with pytest.raises(SystemExit, match=r"Pass --topic <slug>"):
_find_or_create_topic(
"infotech",
"demo",
"demo",
RegisterInference(),
"http://unused",
)
def test_topic_selection_uses_exact_inferred_topic(
monkeypatch: pytest.MonkeyPatch,
):
topics = [
{"id": "topic-1", "slug": "custodian", "domain_slug": "infotech"},
{"id": "topic-2", "slug": "activity-core", "domain_slug": "infotech"},
]
monkeypatch.setattr("statehub_register._api_get", lambda *_a, **_k: topics)
selected = _find_or_create_topic(
"infotech",
"activity-core",
"activity-core",
RegisterInference(topic_slug="activity-core"),
"http://unused",
)
assert selected == topics[1]
def test_topic_selection_refuses_cross_domain_topic(
monkeypatch: pytest.MonkeyPatch,
):
monkeypatch.setattr(
"statehub_register._api_get",
lambda *_a, **_k: [
{"id": "topic-1", "slug": "activity-core", "domain_slug": "agents"},
],
)
with pytest.raises(SystemExit, match=r"belongs to domain\(s\) agents"):
_find_or_create_topic(
"infotech",
"demo",
"demo",
RegisterInference(),
"http://unused",
requested_topic_slug="activity-core",
)
def test_topic_selection_refuses_missing_explicit_topic(
monkeypatch: pytest.MonkeyPatch,
):
monkeypatch.setattr(
"statehub_register._api_get",
lambda *_a, **_k: [
{"id": "topic-1", "slug": "custodian", "domain_slug": "infotech"},
],
)
with pytest.raises(SystemExit, match=r"Available: custodian"):
_find_or_create_topic(
"infotech",
"demo",
"demo",
RegisterInference(),
"http://unused",
requested_topic_slug="activity-core",
)
def test_write_registration_files_is_idempotent_without_force(tmp_path: Path):
inference = RegisterInference()
kwargs = {