Fail closed on invalid Glas profiles
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 22s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a028de-e2c8-7732-8521-46a7fc5db82f
This commit is contained in:
tegwick 2026-08-22 23:02:41 +02:00
parent 561538c95e
commit b933cf52c8
12 changed files with 455 additions and 9 deletions

View file

@ -175,3 +175,134 @@ async def test_emit_tasks_raises_when_sink_fails(monkeypatch) -> None:
}
],
})
@pytest.mark.asyncio
async def test_emit_tasks_refuses_malformed_profile_before_any_side_effect(
monkeypatch,
) -> None:
from temporalio.exceptions import ApplicationError
monkeypatch.setattr(
activities,
"get_issue_sink",
lambda: pytest.fail("IssueSink must not be opened after profile refusal"),
)
monkeypatch.setattr(
activities,
"_get_session_factory",
lambda: pytest.fail("DB must not be opened after profile refusal"),
)
with pytest.raises(ApplicationError, match="must pin a version") as exc_info:
await activities.emit_tasks(
{
"activity_id": "00000000-0000-0000-0000-000000000001",
"triggering_event_id": "scheduled",
"task_specs": [
{
"title": "Must not emit",
"source_type": "rule",
"source_id": "malformed-profile",
"harness_profile_ref": "harness.agent-dev",
"approach_hint": "legacy-must-not-rescue",
}
],
}
)
assert exc_info.value.non_retryable is True
@pytest.mark.asyncio
async def test_emit_tasks_refuses_absent_profile_in_strict_mode(monkeypatch) -> None:
from temporalio.exceptions import ApplicationError
monkeypatch.setenv("ACTIVITY_CORE_REQUIRE_HARNESS_PROFILE", "true")
monkeypatch.setattr(
activities,
"get_issue_sink",
lambda: pytest.fail("IssueSink must not be opened after profile refusal"),
)
with pytest.raises(ApplicationError, match="cannot substitute"):
await activities.emit_tasks(
{
"activity_id": "00000000-0000-0000-0000-000000000001",
"triggering_event_id": "scheduled",
"task_specs": [
{
"title": "Must not emit",
"source_type": "rule",
"source_id": "missing-profile",
"approach_hint": "legacy-must-not-rescue",
}
],
}
)
@pytest.mark.asyncio
async def test_emit_tasks_passes_unknown_versioned_profile_to_queue(monkeypatch) -> None:
captured: dict[str, Any] = {}
class SuccessfulSink:
def emit(self, task_spec: TaskSpec) -> TaskRef:
return TaskRef(external_id="state-hub:progress-1", backend="state-hub")
class FakeTransaction:
async def __aenter__(self) -> None:
return None
async def __aexit__(self, *exc_info: object) -> bool:
return False
class FakeSession:
def begin(self) -> FakeTransaction:
return FakeTransaction()
async def __aenter__(self) -> "FakeSession":
return self
async def __aexit__(self, *exc_info: object) -> bool:
return False
def add(self, row: object) -> None:
return None
class FakeSessionFactory:
def __call__(self) -> FakeSession:
return FakeSession()
async def fake_create_ops_run(session, spec, **kwargs):
captured.update(kwargs)
return None
monkeypatch.setattr(activities, "get_issue_sink", lambda: SuccessfulSink())
monkeypatch.setattr(activities, "_get_session_factory", lambda: FakeSessionFactory())
monkeypatch.setattr(activities, "create_ops_run_from_spec", fake_create_ops_run)
refs = await activities.emit_tasks(
{
"activity_id": "00000000-0000-0000-0000-000000000001",
"triggering_event_id": "event-1",
"task_specs": [
{
"title": "Let Glas resolve it",
"source_type": "rule",
"source_id": "profiled",
"harness_profile_ref": "harness.unknown-locally@9.9.9",
"approach_hint": "legacy-only",
"execution_refs": {
"correlation_id": "corr-9",
"api_key": "must-be-dropped",
},
}
],
}
)
assert refs == ["state-hub:progress-1"]
assert captured["harness_profile_ref"] == "harness.unknown-locally@9.9.9"
assert captured["approach_hint"] == "legacy-only"
assert captured["execution_refs"] == {"correlation_id": "corr-9"}