fix: unwrap single-key kaizen resolver payloads in resolve_context

When discover_kaizen_projects returns {"projects": [...]} bound to
context.projects, for_each can iterate the list directly. Multi-key
summaries (e.g. repo SBOM bulk) remain unchanged.
This commit is contained in:
2026-06-18 08:11:09 +02:00
parent 517bf9c133
commit 9a72c9f210
2 changed files with 60 additions and 1 deletions

View File

@@ -13,6 +13,7 @@ from __future__ import annotations
import uuid import uuid
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.dialects.postgresql import insert as pg_insert from sqlalchemy.dialects.postgresql import insert as pg_insert
@@ -52,6 +53,18 @@ def _get_session_factory() -> async_sessionmaker[AsyncSession]:
return _session_factory return _session_factory
def _bind_resolver_result(bind_key: str, result: Any) -> Any:
"""Unwrap single-key resolver payloads when the key matches bind_key.
Resolvers such as ``discover_kaizen_projects`` return ``{"projects": [...]}``
while definitions bind to ``context.projects`` and iterate ``for_each:
context.projects``. Multi-key summaries (e.g. repo SBOM bulk) stay intact.
"""
if isinstance(result, dict) and len(result) == 1 and bind_key in result:
return result[bind_key]
return result
# ── Activities ───────────────────────────────────────────────────────────────── # ── Activities ─────────────────────────────────────────────────────────────────
@activity.defn @activity.defn
@@ -139,7 +152,8 @@ async def resolve_context(
continue continue
try: try:
snapshot[bind_key] = resolver_cls().resolve(query, None, params) resolved = resolver_cls().resolve(query, None, params)
snapshot[bind_key] = _bind_resolver_result(bind_key, resolved)
except Exception as exc: except Exception as exc:
if required: if required:
raise ApplicationError( raise ApplicationError(

View File

@@ -0,0 +1,45 @@
from __future__ import annotations
import pytest
from activity_core.activities import _bind_resolver_result, resolve_context
def test_bind_resolver_result_unwraps_single_key_wrapper() -> None:
projects = [{"repo": "kaizen-agentic", "has_metrics": True}]
assert _bind_resolver_result("projects", {"projects": projects}) == projects
def test_bind_resolver_result_keeps_multi_key_summary() -> None:
summary = {
"repos": [{"repo_slug": "a"}],
"stale_count": 1,
"total_count": 2,
}
assert _bind_resolver_result("repos", summary) == summary
@pytest.mark.asyncio
async def test_resolve_context_unwraps_kaizen_projects(monkeypatch) -> None:
class _FakeResolver:
def resolve(self, query: str, event: object, params: dict) -> dict:
assert query == "discover_kaizen_projects"
return {"projects": [{"repo": "pilot", "has_metrics": True}]}
import activity_core.context_resolvers # noqa: F401
from activity_core.context_resolvers.base import CONTEXT_RESOLVER_REGISTRY
monkeypatch.setitem(CONTEXT_RESOLVER_REGISTRY, "kaizen", lambda: _FakeResolver())
snapshot = await resolve_context(
[
{
"type": "kaizen",
"query": "discover_kaizen_projects",
"params": {},
"bind_to": "context.projects",
}
]
)
assert snapshot == {"projects": [{"repo": "pilot", "has_metrics": True}]}