From 9cebfbdfc2be07b387709c0af62c2bf9ef6dbfe2 Mon Sep 17 00:00:00 2001 From: witbrock Date: Thu, 17 Sep 2026 13:49:00 -0400 Subject: [PATCH] Fix naming bootstrap misclassification in workflow purity checks --- .../workflows/workflow_purity_report.py | 2 ++ .../test_conversation_naming_workflow.py | 30 ++++++++++++++++++- .../test_repo_seed_authority_guardrails.py | 20 +++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/backend/workflows/workflow_purity_report.py b/src/backend/workflows/workflow_purity_report.py index fcff3ff47..483549068 100644 --- a/src/backend/workflows/workflow_purity_report.py +++ b/src/backend/workflows/workflow_purity_report.py @@ -501,6 +501,8 @@ { "src/backend/services/academic_roster_workflow_vontology_service.py", "src/backend/services/concept_search_instance_retrieval_workflow_vontology_service.py", + # Startup seed support; runtime naming resolves the published Vontology graph. + "src/backend/services/conversation_naming_workflow_vontology_service.py", "src/backend/services/conversation_turn_workflow_vontology_service.py", "src/backend/services/email_source_representation_convergence_workflow_vontology_service.py", "src/backend/services/entity_information_retrieval_workflow_vontology_service.py", diff --git a/tests/backend/test_conversation_naming_workflow.py b/tests/backend/test_conversation_naming_workflow.py index 145eb6e82..5920701c0 100644 --- a/tests/backend/test_conversation_naming_workflow.py +++ b/tests/backend/test_conversation_naming_workflow.py @@ -272,11 +272,39 @@ def test_concurrent_title_is_preserved_and_later_page_still_progresses(world): assert world["names"]["named"] == "User chosen title" -def test_bootstrap_preserves_published_authority(world): +def test_bootstrap_preserves_published_authority(world, monkeypatch): + from src.backend.services import ( + conversation_naming_workflow_vontology_service as naming, + ) + from src.backend.services.text_value_service import ( + get_texts_for_concept, + upsert_singleton_text_relation, + ) + + custom_prompt = "Independently revised naming instructions in Vontology." + upsert_singleton_text_relation( + subject_concept_id=naming.CONVERSATION_NAMING_PROMPT_CONCEPT_ID, + predicate="hasContent", + text=custom_prompt, + lang="en-NZ", + ) + + def forbidden_prompt_seed(): + pytest.fail("An existing live prompt must not be replaced with its repo seed") + + monkeypatch.setattr( + naming, "_load_conversation_naming_prompt_seed_text", forbidden_prompt_seed + ) report = bootstrap_canonical_conversation_naming_workflow() assert report["success"], report assert report["prompt_support"]["seeded_prompt_count"] == 0 assert report["publication"]["counts"]["workflows_published"] == 0 + texts = get_texts_for_concept(naming.CONVERSATION_NAMING_PROMPT_CONCEPT_ID) + assert any(item.get("text") == custom_prompt for item in texts) + assert ( + load_workflow_definition_from_vontology(CONVERSATION_NAMING_WORKFLOW_ID) + is not None + ) def test_hourly_schedule_verifies_and_due_occurrence_executes(monkeypatch, world): diff --git a/tests/backend/test_repo_seed_authority_guardrails.py b/tests/backend/test_repo_seed_authority_guardrails.py index 70430280b..fd4cb4ef5 100644 --- a/tests/backend/test_repo_seed_authority_guardrails.py +++ b/tests/backend/test_repo_seed_authority_guardrails.py @@ -29,3 +29,23 @@ def test_vontology_first_seed_fallback_contracts_have_no_violations() -> None: "before authoritative Vontology resolution in at least one guarded path.\n" f"{violations}" ) + + +def test_naming_bootstrap_does_not_admit_runtime_seed_imports(tmp_path) -> None: + bootstrap = "src/backend/services/conversation_naming_workflow_vontology_service.py" + runtime = "src/backend/services/conversation_naming_service.py" + source = "from .workflow_repo_seed_bootstrap import bootstrap_repo_seed_workflow_bundle\n" + for relative_path in (bootstrap, runtime): + path = tmp_path / relative_path + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(source, encoding="utf-8") + + report = build_workflow_purity_report( + registry=None, project_root=tmp_path, baseline_path=tmp_path / "baseline.json" + ) + drift = report["details"]["repo_seed_authority_drift"] + assert drift["offending_paths"] == [runtime] + assert report["counters"]["repo_seed_authority_drift_path_count"] == 1 + assert any( + item["path"] == bootstrap and item["allowed"] for item in drift["matches"] + )