From f373e3a527aabb42f01773d471df7c72c921dc26 Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Freeman Date: Sun, 5 Apr 2026 07:29:35 +0000 Subject: [PATCH] fix(resources): register fs-mount built-in resource type at startup Add fs-mount ResourceTypeDefinition to _GIT_FS_CONTAINER_TYPES in _resource_registry_data.py so that bootstrap_builtin_types() seeds it into the registry at startup. The fs-mount type was listed in BUILTIN_TYPE_NAMES (validation frozenset) but had no corresponding type definition in BUILTIN_TYPES, causing: - 'agents resource type list' to omit fs-mount - 'agents resource add fs-mount ...' to fail with 'Resource type not found' - fs-directory to be unable to reference fs-mount as a parent type Changes: - Add fs-mount definition to _GIT_FS_CONTAINER_TYPES with correct fields: name, description, resource_kind=physical, sandbox_strategy=copy_on_write, user_addable=True, cli_args=[path], parent_types=[], child_types=[fs-directory], auto_discovery with fs-directory rule, handler=fs_mount:FsMountHandler, capabilities (read/write/sandbox=True, checkpoint=False) - Add fs-mount to fs-directory's parent_types so fs-directory can be nested under an fs-mount resource - Add BDD scenarios in resource_type_bootstrap_fs_mount.feature covering registration, user_addable, child_types, parent_types, resource add, and fs-directory parent resolution - Add Robot Framework integration tests in resource_type_bootstrap_fs_mount.robot covering all four acceptance criteria end-to-end ISSUES CLOSED: #2911 --- .../resource_type_bootstrap_fs_mount.feature | 50 +++++++++ .../steps/resource_type_bootstrap_fs_steps.py | 39 +++++++ robot/resource_type_bootstrap_fs_mount.robot | 101 ++++++++++++++++++ .../services/_resource_registry_data.py | 34 +++++- 4 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 features/resource_type_bootstrap_fs_mount.feature create mode 100644 robot/resource_type_bootstrap_fs_mount.robot diff --git a/features/resource_type_bootstrap_fs_mount.feature b/features/resource_type_bootstrap_fs_mount.feature new file mode 100644 index 000000000..a39c32894 --- /dev/null +++ b/features/resource_type_bootstrap_fs_mount.feature @@ -0,0 +1,50 @@ +Feature: Built-in fs-mount Resource Type Bootstrap + As a CleverAgents user + I want the built-in fs-mount resource type to be available after initialization + So that I can run "agents resource add fs-mount" without "Resource type not found" + + # ── Bug reproduction: fs-mount must be in BUILTIN_TYPES ─────────── + + @tdd_issue @tdd_issue_2911 + Scenario: fs-mount type exists after bootstrap + Given a fresh in-memory resource registry with bootstrap + When I query the fs bootstrap resource type registry for "fs-mount" + Then the bootstrap fs resource type "fs-mount" should exist + And the bootstrap fs resource type kind should be "physical" + And the bootstrap fs resource type sandbox_strategy should be "copy_on_write" + + @tdd_issue @tdd_issue_2911 + Scenario: fs-mount is user-addable after bootstrap + Given a fresh in-memory resource registry with bootstrap + When I query the fs bootstrap resource type registry for "fs-mount" + Then the bootstrap fs resource type "fs-mount" should exist + And the bootstrap fs resource type user_addable should be true + + @tdd_issue @tdd_issue_2911 + Scenario: fs-mount has fs-directory as child type after bootstrap + Given a fresh in-memory resource registry with bootstrap + When I query the fs bootstrap resource type registry for "fs-mount" + Then the bootstrap fs resource type "fs-mount" should exist + And the bootstrap fs resource type child_types should contain "fs-directory" + + @tdd_issue @tdd_issue_2911 + Scenario: fs-mount has no parent types after bootstrap + Given a fresh in-memory resource registry with bootstrap + When I query the fs bootstrap resource type registry for "fs-mount" + Then the bootstrap fs resource type "fs-mount" should exist + And the bootstrap fs resource type parent_types should be empty + + @tdd_issue @tdd_issue_2911 + Scenario: resource add fs-mount succeeds after bootstrap + Given a fresh in-memory resource registry with bootstrap + When I run resource add for type "fs-mount" named "local/test-mount" with path "/tmp" + Then the resource add command should succeed + And the resource add output should contain "Added resource" + And the resource add output should not contain "Resource type not found" + + @tdd_issue @tdd_issue_2911 + Scenario: fs-directory can reference fs-mount as parent type after bootstrap + Given a fresh in-memory resource registry with bootstrap + When I query the fs bootstrap resource type registry for "fs-directory" + Then the bootstrap fs resource type "fs-directory" should exist + And the bootstrap fs resource type parent_types should contain "fs-mount" diff --git a/features/steps/resource_type_bootstrap_fs_steps.py b/features/steps/resource_type_bootstrap_fs_steps.py index 504505214..12deb3c2c 100644 --- a/features/steps/resource_type_bootstrap_fs_steps.py +++ b/features/steps/resource_type_bootstrap_fs_steps.py @@ -207,3 +207,42 @@ def step_output_should_not_contain(context: Context, text: str) -> None: f"Output unexpectedly contained '{text}'. " f"Full output: {context.bootstrap_fs_output!r}" ) + + +@then("the bootstrap fs resource type user_addable should be true") +def step_type_user_addable_true(context: Context) -> None: + """Assert the resource type is user-addable.""" + spec = context.bootstrap_fs_type_spec + assert spec.user_addable is True, ( + f"Expected user_addable to be True, got {spec.user_addable!r}" + ) + + +@then('the bootstrap fs resource type child_types should contain "{child}"') +def step_type_child_types_contain(context: Context, child: str) -> None: + """Assert the resource type has the given child type.""" + spec = context.bootstrap_fs_type_spec + child_types = spec.child_types or [] + assert child in child_types, ( + f"Expected child_types to contain '{child}', got {child_types!r}" + ) + + +@then("the bootstrap fs resource type parent_types should be empty") +def step_type_parent_types_empty(context: Context) -> None: + """Assert the resource type has no parent types.""" + spec = context.bootstrap_fs_type_spec + parent_types = spec.parent_types or [] + assert len(parent_types) == 0, ( + f"Expected parent_types to be empty, got {parent_types!r}" + ) + + +@then('the bootstrap fs resource type parent_types should contain "{parent}"') +def step_type_parent_types_contain(context: Context, parent: str) -> None: + """Assert the resource type has the given parent type.""" + spec = context.bootstrap_fs_type_spec + parent_types = spec.parent_types or [] + assert parent in parent_types, ( + f"Expected parent_types to contain '{parent}', got {parent_types!r}" + ) diff --git a/robot/resource_type_bootstrap_fs_mount.robot b/robot/resource_type_bootstrap_fs_mount.robot new file mode 100644 index 000000000..d77fb0d31 --- /dev/null +++ b/robot/resource_type_bootstrap_fs_mount.robot @@ -0,0 +1,101 @@ +*** Settings *** +Documentation Integration tests for built-in fs-mount type bootstrap (bug #2911). +... These tests verify that bootstrap_builtin_types() correctly seeds +... fs-mount into the registry and that resource registration succeeds +... after bootstrap. +Library Process +Library OperatingSystem +Resource ${CURDIR}/common.resource + +Suite Setup Setup Test Environment +Suite Teardown Cleanup Test Environment + +*** Test Cases *** +Bootstrap Seeds Fs Mount Type Into Registry + [Documentation] Verify that bootstrap_builtin_types() seeds fs-mount into the + ... database so that show_type("fs-mount") succeeds. + ${script}= Catenate SEPARATOR=\n + ... from sqlalchemy import create_engine + ... from sqlalchemy.orm import sessionmaker + ... from cleveragents.infrastructure.database.models import Base + ... from cleveragents.application.services.resource_registry_service import ResourceRegistryService + ... engine = create_engine("sqlite:///:memory:", echo=False) + ... Base.metadata.create_all(engine) + ... factory = sessionmaker(bind=engine, expire_on_commit=False) + ... service = ResourceRegistryService(session_factory=factory) + ... registered = [t.name for t in service.list_types()] + ... assert "fs-mount" in registered, f"fs-mount not in registered: {registered}" + ... spec = service.show_type("fs-mount") + ... assert spec.name == "fs-mount", f"name mismatch: {spec.name}" + ... kind = spec.resource_kind.value if hasattr(spec.resource_kind, "value") else str(spec.resource_kind) + ... assert kind == "physical", f"kind: {kind}" + ... strategy = spec.sandbox_strategy.value if hasattr(spec.sandbox_strategy, "value") else str(spec.sandbox_strategy) + ... assert strategy == "copy_on_write", f"strategy: {strategy}" + ... assert spec.user_addable is True, f"user_addable: {spec.user_addable}" + ... print("fs-mount bootstrap validated successfully") + ${result}= Run Process ${PYTHON} -c ${script} timeout=120s on_timeout=kill + Should Be Equal As Integers ${result.rc} 0 fs-mount bootstrap failed: ${result.stderr} + Should Contain ${result.stdout} fs-mount bootstrap validated successfully + +Fs Mount Has Correct Child Types After Bootstrap + [Documentation] Verify that fs-mount has fs-directory as a child type. + ${script}= Catenate SEPARATOR=\n + ... from sqlalchemy import create_engine + ... from sqlalchemy.orm import sessionmaker + ... from cleveragents.infrastructure.database.models import Base + ... from cleveragents.application.services.resource_registry_service import ResourceRegistryService + ... engine = create_engine("sqlite:///:memory:", echo=False) + ... Base.metadata.create_all(engine) + ... factory = sessionmaker(bind=engine, expire_on_commit=False) + ... service = ResourceRegistryService(session_factory=factory) + ... spec = service.show_type("fs-mount") + ... assert "fs-directory" in spec.child_types, f"child_types: {spec.child_types}" + ... assert spec.parent_types == [], f"parent_types should be empty: {spec.parent_types}" + ... print("fs-mount child/parent types validated successfully") + ${result}= Run Process ${PYTHON} -c ${script} timeout=120s on_timeout=kill + Should Be Equal As Integers ${result.rc} 0 fs-mount child types check failed: ${result.stderr} + Should Contain ${result.stdout} fs-mount child/parent types validated successfully + +Resource Add Fs Mount Succeeds After Bootstrap + [Documentation] Verify that after bootstrap, registering an fs-mount resource + ... instance succeeds (no "Resource type not found" error). + ${script}= Catenate SEPARATOR=\n + ... from sqlalchemy import create_engine + ... from sqlalchemy.orm import sessionmaker + ... from cleveragents.infrastructure.database.models import Base + ... from cleveragents.application.services.resource_registry_service import ResourceRegistryService + ... engine = create_engine("sqlite:///:memory:", echo=False) + ... Base.metadata.create_all(engine) + ... factory = sessionmaker(bind=engine, expire_on_commit=False) + ... service = ResourceRegistryService(session_factory=factory) + ... service.bootstrap_builtin_types() + ... resource = service.register_resource( + ... ${SPACE}${SPACE}${SPACE}${SPACE}type_name="fs-mount", + ... ${SPACE}${SPACE}${SPACE}${SPACE}name="local/test-mount", + ... ${SPACE}${SPACE}${SPACE}${SPACE}location="/tmp", + ... ${SPACE}${SPACE}${SPACE}${SPACE}description="Test fs-mount resource", + ... ) + ... assert resource.resource_type_name == "fs-mount", f"type: {resource.resource_type_name}" + ... assert resource.name == "local/test-mount", f"name: {resource.name}" + ... print("resource add fs-mount succeeded after bootstrap") + ${result}= Run Process ${PYTHON} -c ${script} timeout=120s on_timeout=kill + Should Be Equal As Integers ${result.rc} 0 resource add fs-mount failed: ${result.stderr} + Should Contain ${result.stdout} resource add fs-mount succeeded after bootstrap + +Fs Directory Has Fs Mount As Parent Type After Bootstrap + [Documentation] Verify that fs-directory lists fs-mount as an allowed parent type. + ${script}= Catenate SEPARATOR=\n + ... from sqlalchemy import create_engine + ... from sqlalchemy.orm import sessionmaker + ... from cleveragents.infrastructure.database.models import Base + ... from cleveragents.application.services.resource_registry_service import ResourceRegistryService + ... engine = create_engine("sqlite:///:memory:", echo=False) + ... Base.metadata.create_all(engine) + ... factory = sessionmaker(bind=engine, expire_on_commit=False) + ... service = ResourceRegistryService(session_factory=factory) + ... spec = service.show_type("fs-directory") + ... assert "fs-mount" in spec.parent_types, f"fs-mount not in fs-directory parent_types: {spec.parent_types}" + ... print("fs-directory parent_types includes fs-mount") + ${result}= Run Process ${PYTHON} -c ${script} timeout=120s on_timeout=kill + Should Be Equal As Integers ${result.rc} 0 fs-directory parent types check failed: ${result.stderr} + Should Contain ${result.stdout} fs-directory parent_types includes fs-mount diff --git a/src/cleveragents/application/services/_resource_registry_data.py b/src/cleveragents/application/services/_resource_registry_data.py index e5ca010ec..959c418e5 100644 --- a/src/cleveragents/application/services/_resource_registry_data.py +++ b/src/cleveragents/application/services/_resource_registry_data.py @@ -72,6 +72,38 @@ __all__ = [ # ===== Git / Filesystem / Container types (unchanged) ===== _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [ + { + "name": "fs-mount", + "description": "A physical mount point on the local system.", + "resource_kind": "physical", + "sandbox_strategy": "copy_on_write", + "user_addable": True, + "built_in": True, + "cli_args": [ + { + "name": "path", + "type": "path", + "required": True, + "description": "Path to the mount point.", + }, + ], + "parent_types": [], + "child_types": ["fs-directory"], + "auto_discovery": { + "enabled": True, + "scan_depth": 1, + "rules": [ + {"type": "fs-directory", "pattern": "*/"}, + ], + }, + "handler": "cleveragents.resource.handlers.fs_mount:FsMountHandler", + "capabilities": { + "read": True, + "write": True, + "sandbox": True, + "checkpoint": False, + }, + }, { "name": "git-checkout", "description": "A local git checkout (cloned repository or worktree).", @@ -125,7 +157,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [ "description": "Path to the directory.", }, ], - "parent_types": ["git-checkout", "fs-directory"], + "parent_types": ["git-checkout", "fs-mount", "fs-directory"], "child_types": [ "fs-directory", "fs-file", -- 2.52.0