diff --git a/features/resource_registry_service_coverage.feature b/features/resource_registry_service_coverage.feature index de3e68179..9e53d8dd3 100644 --- a/features/resource_registry_service_coverage.feature +++ b/features/resource_registry_service_coverage.feature @@ -57,3 +57,15 @@ Feature: Resource registry service coverage gaps And I show the registered type with auto_discovery and equivalence Then the shown type should have the correct auto_discovery And the shown type should have the correct equivalence + + # ── _db_to_spec built_in field via namespace column ─────────────── + + Scenario: _db_to_spec sets built_in True when namespace column is "builtin" + Given a resource type DB row with namespace "builtin" and name "git-checkout" + When I convert that row to a spec via _db_to_spec + Then the resulting spec built_in should be True + + Scenario: _db_to_spec sets built_in False when namespace column is not "builtin" + Given a resource type DB row with namespace "local" and name "local/mytype" + When I convert that row to a spec via _db_to_spec + Then the resulting spec built_in should be False diff --git a/features/steps/resource_registry_service_coverage_steps.py b/features/steps/resource_registry_service_coverage_steps.py index 0b53e57cf..da3eb757f 100644 --- a/features/steps/resource_registry_service_coverage_steps.py +++ b/features/steps/resource_registry_service_coverage_steps.py @@ -456,3 +456,63 @@ def step_verify_rt_eq(context: Any) -> None: assert eq is not None, "equivalence should be preserved" assert eq["criteria"] == ["content_hash"] assert eq["description"] == "Hash-based equivalence" + + +# --------------------------------------------------------------------------- +# Scenarios: _db_to_spec derives built_in from namespace column +# --------------------------------------------------------------------------- + + +@given('a resource type DB row with namespace "{namespace}" and name "{name}"') +def step_db_row_with_namespace_and_name(context: Any, namespace: str, name: str) -> None: + """Create an in-memory DB row with the given namespace and name.""" + engine, factory = _in_memory_session_factory() + context.ns_builtin_engine = engine + context.ns_builtin_factory = factory + + session = factory() + from datetime import UTC, datetime + + now = datetime.now(tz=UTC).isoformat() + row = ResourceTypeModel( + name=name, + namespace=namespace, + description="Test row for namespace-based built_in detection", + resource_kind="physical", + sandbox_strategy="none", + user_addable=False, + handler_ref=None, + args_schema_json=None, + allowed_parent_types_json=None, + allowed_child_types_json=None, + auto_discover_json=None, + capabilities_json=None, + equivalence_json=None, + source="test", + created_at=now, + updated_at=now, + ) + session.add(row) + session.commit() + context.ns_builtin_row = ( + session.query(ResourceTypeModel).filter_by(name=name).first() + ) + + +@when("I convert that row to a spec via _db_to_spec") +def step_convert_ns_row_to_spec(context: Any) -> None: + context.ns_builtin_spec = _db_to_spec(context.ns_builtin_row) + + +@then("the resulting spec built_in should be True") +def step_verify_builtin_true(context: Any) -> None: + assert context.ns_builtin_spec.built_in is True, ( + f"Expected built_in=True but got {context.ns_builtin_spec.built_in!r}" + ) + + +@then("the resulting spec built_in should be False") +def step_verify_builtin_false(context: Any) -> None: + assert context.ns_builtin_spec.built_in is False, ( + f"Expected built_in=False but got {context.ns_builtin_spec.built_in!r}" + ) diff --git a/src/cleveragents/application/services/_resource_registry_data.py b/src/cleveragents/application/services/_resource_registry_data.py index e5ca010ec..8f63c2a43 100644 --- a/src/cleveragents/application/services/_resource_registry_data.py +++ b/src/cleveragents/application/services/_resource_registry_data.py @@ -401,7 +401,11 @@ def db_to_spec(row: ResourceTypeModel) -> ResourceTypeSpec: equivalence = json.loads(str(raw_equiv)) name_str = str(row.name) - is_builtin = "/" not in name_str + # Derive built_in from the namespace column: built-in types have + # namespace="builtin". Fall back to the name heuristic only when the + # column is unavailable (e.g. legacy rows without a namespace column). + raw_ns = getattr(row, "namespace", None) + is_builtin = str(raw_ns) == "builtin" if raw_ns is not None else "/" not in name_str raw_desc = getattr(row, "description", None) desc_str = str(raw_desc) if raw_desc is not None else ""