Compare commits

...

1 Commits

Author SHA1 Message Date
hamza.khyari b00e12d741 feat(resource): add fs-mount and fs-file resource types
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 17s
CI / build (pull_request) Successful in 29s
CI / quality (pull_request) Successful in 38s
CI / typecheck (pull_request) Successful in 42s
CI / security (pull_request) Successful in 44s
CI / unit_tests (pull_request) Successful in 3m10s
CI / integration_tests (pull_request) Successful in 3m48s
CI / docker (pull_request) Successful in 1m0s
CI / e2e_tests (pull_request) Successful in 4m54s
CI / coverage (pull_request) Successful in 7m52s
CI / benchmark-regression (pull_request) Successful in 37m29s
Register fs-mount and fs-file in BUILTIN_TYPES bootstrap. Both types
were defined in BUILTIN_NAMES and had YAML configs but were never
registered, causing them to be absent from the resource registry.

- fs-mount: top-level mount point, user-addable, copy_on_write sandbox,
  auto-discovery with scan_depth=1, children: fs-directory
- fs-file: leaf file node, auto-discovered from fs-directory, not
  user-addable, copy_on_write sandbox, content hash for equivalence
- Updated fs-directory parent_types to include fs-mount
- 12 Behave BDD scenarios, 3 Robot integration tests

ISSUES CLOSED: #833
2026-03-19 03:57:27 +00:00
7 changed files with 385 additions and 1 deletions
+6
View File
@@ -2,6 +2,12 @@
## Unreleased
- Added `fs-mount` and `fs-file` bootstrap registration. Both types were
defined in `BUILTIN_NAMES` and had YAML configs but were never registered
in `BUILTIN_TYPES`. `fs-mount` is a top-level mount point (user-addable,
`copy_on_write` sandbox). `fs-file` is a leaf file node (auto-discovered,
child of `fs-directory`). Updated `fs-directory` parent_types to include
`fs-mount`. Includes Behave BDD tests and Robot integration tests. (#833)
- Added built-in deferred virtual resource types: `remote`, `submodule`, and
`symlink` with equivalence metadata rules for cross-repo and cross-layer
identity tracking. Registry bootstrap includes deferred virtual types but
@@ -0,0 +1,78 @@
@fs-mount-file
Feature: fs-mount and fs-file resource types
Verifies that fs-mount and fs-file are correctly defined,
registered in BUILTIN_TYPES, and have proper parent/child
relationships within the filesystem resource hierarchy.
# YAML schema loading
Scenario Outline: fs types YAML validates for fs_mount_file
Given the built-in "<type_name>" YAML for fs_mount_file
When I load the YAML via from_yaml_file for fs_mount_file
Then the fs_mount_file schema should be valid
And the fs_mount_file schema name should be "<type_name>"
And the fs_mount_file schema resource_kind should be "physical"
Examples:
| type_name |
| fs-mount |
| fs-file |
# ── Domain model creation ───────────────────────────────
Scenario Outline: fs types load as domain model for fs_mount_file
Given the built-in "<type_name>" YAML for fs_mount_file
When I load the YAML as a ResourceTypeSpec for fs_mount_file
Then the fs_mount_file domain model should be valid
And the fs_mount_file domain model name should be "<type_name>"
Examples:
| type_name |
| fs-mount |
| fs-file |
# ── Parent/child relationships ──────────────────────────
Scenario: fs-mount is top-level with fs-directory child for fs_mount_file
Given the built-in "fs-mount" YAML for fs_mount_file
When I load the YAML as a ResourceTypeSpec for fs_mount_file
Then the fs_mount_file spec should have empty parent_types
And the fs_mount_file child_types should contain "fs-directory"
Scenario: fs-file parent is fs-directory for fs_mount_file
Given the built-in "fs-file" YAML for fs_mount_file
When I load the YAML as a ResourceTypeSpec for fs_mount_file
Then the fs_mount_file parent_types should contain "fs-directory"
And the fs_mount_file spec should have empty child_types
Scenario: fs-directory lists fs-mount as parent for fs_mount_file
Given the bootstrap builtin type definitions for fs_mount_file
When I find the type "fs-directory" in builtin definitions for fs_mount_file
Then the found builtin parent_types should contain "fs-mount" for fs_mount_file
# ── User-addable and sandbox ────────────────────────────
Scenario: fs-mount is user-addable for fs_mount_file
Given the built-in "fs-mount" YAML for fs_mount_file
When I load the YAML as a ResourceTypeSpec for fs_mount_file
Then the fs_mount_file user_addable should be true
Scenario: fs-file is not user-addable for fs_mount_file
Given the built-in "fs-file" YAML for fs_mount_file
When I load the YAML as a ResourceTypeSpec for fs_mount_file
Then the fs_mount_file user_addable should be false
# ── BUILTIN_NAMES and BUILTIN_TYPES ─────────────────────
Scenario Outline: fs types in BUILTIN_NAMES for fs_mount_file
Then BUILTIN_NAMES should contain "<type_name>" for fs_mount_file
Examples:
| type_name |
| fs-mount |
| fs-file |
Scenario: Both fs types in BUILTIN_TYPES for fs_mount_file
Given the bootstrap builtin type definitions for fs_mount_file
Then BUILTIN_TYPES should contain "fs-mount" for fs_mount_file
And BUILTIN_TYPES should contain "fs-file" for fs_mount_file
@@ -0,0 +1,126 @@
"""Step definitions for resource_type_fs_mount_file.feature."""
from __future__ import annotations
from pathlib import Path
from typing import Any
import yaml
from behave import given, then, when
from cleveragents.application.services._resource_registry_data import (
BUILTIN_TYPES,
)
from cleveragents.domain.models.core.resource_type import (
ResourceTypeSpec,
)
from cleveragents.resource.schema import ResourceTypeConfigSchema
_EXAMPLES_DIR = (
Path(__file__).resolve().parent.parent.parent / "examples" / "resource-types"
)
@given('the built-in "{type_name}" YAML for fs_mount_file')
def step_builtin_yaml(context: object, type_name: str) -> None:
path = _EXAMPLES_DIR / f"{type_name}.yaml"
assert path.exists(), f"YAML not found: {path}"
context.fsm_yaml_path = path
@given("the bootstrap builtin type definitions for fs_mount_file")
def step_bootstrap_defs(context: object) -> None:
context.fsm_builtin_defs = list(BUILTIN_TYPES)
@when("I load the YAML via from_yaml_file for fs_mount_file")
def step_load_yaml(context: object) -> None:
path: Path = context.fsm_yaml_path
context.fsm_schema = ResourceTypeConfigSchema.from_yaml_file(path)
@when("I load the YAML as a ResourceTypeSpec for fs_mount_file")
def step_load_domain(context: object) -> None:
path: Path = context.fsm_yaml_path
raw: dict[str, Any] = yaml.safe_load(path.read_text(encoding="utf-8"))
context.fsm_spec = ResourceTypeSpec.from_config(raw)
@when('I find the type "{name}" in builtin definitions for fs_mount_file')
def step_find_in_builtins(context: object, name: str) -> None:
defs: list[dict[str, Any]] = context.fsm_builtin_defs
found = [d for d in defs if d["name"] == name]
assert len(found) == 1, f"Expected 1 entry for '{name}', found {len(found)}"
context.fsm_found_builtin = found[0]
@then("the fs_mount_file schema should be valid")
def step_schema_valid(context: object) -> None:
schema = context.fsm_schema
assert isinstance(schema, ResourceTypeConfigSchema)
@then('the fs_mount_file schema name should be "{name}"')
def step_schema_name(context: object, name: str) -> None:
assert context.fsm_schema.name == name
@then('the fs_mount_file schema resource_kind should be "{kind}"')
def step_schema_kind(context: object, kind: str) -> None:
assert context.fsm_schema.resource_kind == kind
@then("the fs_mount_file domain model should be valid")
def step_domain_valid(context: object) -> None:
assert isinstance(context.fsm_spec, ResourceTypeSpec)
@then('the fs_mount_file domain model name should be "{name}"')
def step_domain_name(context: object, name: str) -> None:
assert context.fsm_spec.name == name
@then("the fs_mount_file spec should have empty parent_types")
def step_empty_parents(context: object) -> None:
assert context.fsm_spec.parent_types == []
@then("the fs_mount_file spec should have empty child_types")
def step_empty_children(context: object) -> None:
assert context.fsm_spec.child_types == []
@then('the fs_mount_file parent_types should contain "{name}"')
def step_parent_contains(context: object, name: str) -> None:
spec = context.fsm_spec
assert name in spec.parent_types, f"'{name}' not in {spec.parent_types}"
@then('the fs_mount_file child_types should contain "{name}"')
def step_child_contains(context: object, name: str) -> None:
spec = context.fsm_spec
assert name in spec.child_types, f"'{name}' not in {spec.child_types}"
@then('the found builtin parent_types should contain "{name}" for fs_mount_file')
def step_found_parent(context: object, name: str) -> None:
found: dict[str, Any] = context.fsm_found_builtin
assert name in found["parent_types"], f"'{name}' not in {found['parent_types']}"
@then("the fs_mount_file user_addable should be {addable}")
def step_user_addable(context: object, addable: str) -> None:
expected = addable.lower() == "true"
assert context.fsm_spec.user_addable is expected
@then('BUILTIN_NAMES should contain "{name}" for fs_mount_file')
def step_builtin_names(context: object, name: str) -> None:
assert name in ResourceTypeSpec.BUILTIN_NAMES
@then('BUILTIN_TYPES should contain "{name}" for fs_mount_file')
def step_builtin_types(context: object, name: str) -> None:
defs: list[dict[str, Any]] = context.fsm_builtin_defs
names = [d["name"] for d in defs]
assert name in names, f"'{name}' not in BUILTIN_TYPES"
@@ -0,0 +1,83 @@
"""Helper script for fs-mount and fs-file resource type Robot tests."""
from __future__ import annotations
import sys
from pathlib import Path
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
from cleveragents.application.services._resource_registry_data import ( # noqa: E402
BUILTIN_TYPES,
)
from cleveragents.domain.models.core.resource_type import ( # noqa: E402
ResourceTypeSpec,
)
from cleveragents.resource.schema import ( # noqa: E402
ResourceTypeConfigSchema,
)
_EXAMPLES = Path(__file__).resolve().parents[1] / "examples" / "resource-types"
def cmd_check_registration() -> None:
"""Verify fs-mount and fs-file in BUILTIN_TYPES."""
names = [d["name"] for d in BUILTIN_TYPES]
for n in ("fs-mount", "fs-file"):
assert n in names, f"{n} not in BUILTIN_TYPES"
assert n in ResourceTypeSpec.BUILTIN_NAMES, f"{n} not in BUILTIN_NAMES"
# Verify basic properties
mount = next(d for d in BUILTIN_TYPES if d["name"] == "fs-mount")
assert mount["user_addable"] is True
assert mount["sandbox_strategy"] == "copy_on_write"
ffile = next(d for d in BUILTIN_TYPES if d["name"] == "fs-file")
assert ffile["user_addable"] is False
assert ffile["sandbox_strategy"] == "copy_on_write"
print("check-registration-ok")
def cmd_check_hierarchy() -> None:
"""Verify fs-mount -> fs-directory -> fs-file DAG."""
by_name = {d["name"]: d for d in BUILTIN_TYPES}
mount = by_name["fs-mount"]
assert "fs-directory" in mount["child_types"]
assert mount["parent_types"] == []
fdir = by_name["fs-directory"]
assert "fs-mount" in fdir["parent_types"]
assert "fs-file" in fdir["child_types"]
ffile = by_name["fs-file"]
assert "fs-directory" in ffile["parent_types"]
assert ffile["child_types"] == []
print("check-hierarchy-ok")
def cmd_check_yaml() -> None:
"""Load both YAML configs."""
for n in ("fs-mount", "fs-file"):
path = _EXAMPLES / f"{n}.yaml"
assert path.exists(), f"Missing: {path}"
schema = ResourceTypeConfigSchema.from_yaml_file(path)
assert schema.name == n
spec = ResourceTypeSpec.from_config(schema.model_dump())
assert spec.name == n
print("check-yaml-ok")
_COMMANDS = {
"check-registration": cmd_check_registration,
"check-hierarchy": cmd_check_hierarchy,
"check-yaml": cmd_check_yaml,
}
def main() -> None:
if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr)
sys.exit(1)
_COMMANDS[sys.argv[1]]()
if __name__ == "__main__":
main()
+36
View File
@@ -0,0 +1,36 @@
*** Settings ***
Documentation Integration tests for fs-mount and fs-file resource types.
Resource ${CURDIR}/common.resource
Suite Setup Setup Test Environment
Suite Teardown Cleanup Test Environment
*** Variables ***
${HELPER} ${CURDIR}/helper_resource_type_fs_mount_file.py
*** Test Cases ***
Both fs Types In BUILTIN_TYPES
[Documentation] Verify fs-mount and fs-file are registered in BUILTIN_TYPES
[Tags] fs registration smoke
${result}= Run Process ${PYTHON} ${HELPER} check-registration
... timeout=60s on_timeout=kill
Should Be Equal As Integers ${result.rc} 0
... Registration check failed: ${result.stderr}
Should Contain ${result.stdout} check-registration-ok
Filesystem Hierarchy
[Documentation] Verify fs-mount -> fs-directory -> fs-file DAG
[Tags] fs hierarchy
${result}= Run Process ${PYTHON} ${HELPER} check-hierarchy
... timeout=60s on_timeout=kill
Should Be Equal As Integers ${result.rc} 0
... Hierarchy check failed: ${result.stderr}
Should Contain ${result.stdout} check-hierarchy-ok
YAML Schema Loading
[Documentation] Load both YAML configs and verify schema
[Tags] fs yaml
${result}= Run Process ${PYTHON} ${HELPER} check-yaml
... timeout=60s on_timeout=kill
Should Be Equal As Integers ${result.rc} 0
... YAML loading failed: ${result.stderr}
Should Contain ${result.stdout} check-yaml-ok
@@ -177,7 +177,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",
@@ -192,6 +192,59 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
"checkpoint": False,
},
},
{
"name": "fs-mount",
"description": "A filesystem mount point (bind mount, NFS, tmpfs, etc.).",
"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": "fs-file",
"description": (
"An individual file on the local filesystem with "
"content hash metadata for equivalence tracking."
),
"resource_kind": "physical",
"sandbox_strategy": "copy_on_write",
"user_addable": False,
"built_in": True,
"cli_args": [],
"parent_types": ["fs-directory"],
"child_types": [],
"handler": "cleveragents.resource.handlers.fs_file:FsFileHandler",
"capabilities": {
"read": True,
"write": True,
"sandbox": False,
"checkpoint": False,
},
},
{
"name": "container-instance",
"description": "A container execution environment instance.",
@@ -16,6 +16,8 @@ development workflows. Built-in types are **unnamespaced** (no
|-------------------|----------|------------------|--------------------------------|
| ``git-checkout`` | physical | ``git_worktree`` | Git repository checkout |
| ``fs-directory`` | physical | ``copy_on_write``| Filesystem directory |
| ``fs-mount`` | physical | ``copy_on_write``| Filesystem mount point |
| ``fs-file`` | physical | ``copy_on_write``| Individual file (with hash) |
| ``file`` | virtual | ``none`` | Cross-layer file identity |
| ``directory`` | virtual | ``none`` | Cross-layer directory identity |
| ``commit`` | virtual | ``none`` | Cross-repo commit identity |