Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b00e12d741 |
@@ -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()
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user