Skip to content

Commit b702fa4

Browse files
mchadesCopilot
andcommitted
feat(discover): inject backend type tag into resource tags
Add 'backend:<TYPE>' tag to each resource returned by adp.discover, allowing agents to distinguish backend types (e.g. RDBMS, BLOB_STORAGE) without inspecting field-level convention fields. - _to_resource() now accepts a BackendDefinition | None parameter - handle() passes the resolved backend to _to_resource() - Tags are created if absent; existing tags are preserved - If backend is None (e.g. unresolvable), no tag is injected Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a7c423e commit b702fa4

File tree

2 files changed

+71
-4
lines changed

2 files changed

+71
-4
lines changed

src/adp_hypervisor/handlers/discover.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
if TYPE_CHECKING:
4444
from adp_hypervisor.manifest.index import ManifestIndex
45+
from adp_hypervisor.manifest.physical import BackendDefinition
4546
from adp_hypervisor.manifest.semantic import CuratedResource
4647

4748
logger = logging.getLogger(__name__)
@@ -85,15 +86,22 @@ def _expand_intent_classes(intent_classes: list[IntentClass] | None) -> list[Int
8586
return intent_classes
8687

8788

88-
def _to_resource(curated: CuratedResource) -> Resource:
89-
"""Convert a CuratedResource to a protocol Resource (strip curation-specific fields)."""
89+
def _to_resource(curated: CuratedResource, backend: BackendDefinition | None = None) -> Resource:
90+
"""Convert a CuratedResource to a protocol Resource (strip curation-specific fields).
91+
92+
If a backend definition is provided, a ``"backend:<TYPE>"`` tag is appended
93+
so that clients can identify the backend type without a separate describe call.
94+
"""
95+
tags = list(curated.tags or [])
96+
if backend is not None:
97+
tags.append(f"backend:{backend.type}")
9098
return Resource(
9199
resource_id=curated.resource_id,
92100
version=curated.version,
93101
intent_classes=_expand_intent_classes(curated.intent_classes),
94102
description=curated.description,
95103
semantic_description=curated.semantic_description,
96-
tags=curated.tags,
104+
tags=tags if tags else None,
97105
)
98106

99107

@@ -223,6 +231,8 @@ async def handle(self, params: dict[str, Any]) -> BaseModel:
223231
)
224232

225233
return DiscoverResult(
226-
resources=[_to_resource(r) for r in page],
234+
resources=[
235+
_to_resource(r, self._manifest_index.get_backend(r.backend_id)) for r in page
236+
],
227237
next_cursor=next_cursor,
228238
)

tests/unit/handlers/test_discover.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def _make_resource(
9595
def _mock_manifest(resources: list[CuratedResource] | None = None) -> ManifestIndex:
9696
index = MagicMock(spec=ManifestIndex)
9797
index.list_resources.return_value = resources if resources is not None else _SAMPLE_RESOURCES
98+
index.get_backend.return_value = None
9899
return index
99100

100101

@@ -619,3 +620,59 @@ async def test_no_accessible_resources(self) -> None:
619620
data = result.model_dump(by_alias=True, exclude_none=True)
620621

621622
self.assertEqual(data["resources"], [])
623+
624+
625+
# =============================================================================
626+
class TestDiscoverBackendTagInjection(unittest.IsolatedAsyncioTestCase):
627+
"""Backend type tag is injected into resource tags during discover."""
628+
629+
async def test_backend_tag_appended(self) -> None:
630+
"""Resources get a 'backend:<TYPE>' tag from the backend definition."""
631+
from unittest.mock import MagicMock
632+
633+
from adp_hypervisor.manifest.physical import BackendDefinition, BackendType
634+
635+
backend_def = MagicMock(spec=BackendDefinition)
636+
backend_def.type = BackendType.RDBMS
637+
638+
index = _mock_manifest(
639+
[_make_resource("demo:orders", intent_classes=["QUERY"], tags=["FINANCE"])]
640+
)
641+
index.get_backend.return_value = backend_def
642+
643+
handler = DiscoverHandler(manifest_index=index, policy_enforcer=_mock_policy_enforcer())
644+
result = await handler.handle(_make_params())
645+
data = result.model_dump(by_alias=True, exclude_none=True)
646+
647+
tags = data["resources"][0]["tags"]
648+
self.assertIn("FINANCE", tags)
649+
self.assertIn("backend:RDBMS", tags)
650+
651+
async def test_backend_tag_no_existing_tags(self) -> None:
652+
"""Backend tag is injected even when the resource has no existing tags."""
653+
from unittest.mock import MagicMock
654+
655+
from adp_hypervisor.manifest.physical import BackendDefinition, BackendType
656+
657+
backend_def = MagicMock(spec=BackendDefinition)
658+
backend_def.type = BackendType.BLOB_STORAGE
659+
660+
index = _mock_manifest([_make_resource("demo:docs", intent_classes=["QUERY"])])
661+
index.get_backend.return_value = backend_def
662+
663+
handler = DiscoverHandler(manifest_index=index, policy_enforcer=_mock_policy_enforcer())
664+
result = await handler.handle(_make_params())
665+
data = result.model_dump(by_alias=True, exclude_none=True)
666+
667+
self.assertEqual(data["resources"][0]["tags"], ["backend:BLOB_STORAGE"])
668+
669+
async def test_no_backend_tag_when_backend_missing(self) -> None:
670+
"""When get_backend returns None, no backend tag is injected."""
671+
index = _mock_manifest([_make_resource("demo:orphan", intent_classes=["QUERY"])])
672+
index.get_backend.return_value = None
673+
674+
handler = DiscoverHandler(manifest_index=index, policy_enforcer=_mock_policy_enforcer())
675+
result = await handler.handle(_make_params())
676+
data = result.model_dump(by_alias=True, exclude_none=True)
677+
678+
self.assertNotIn("tags", data["resources"][0])

0 commit comments

Comments
 (0)