fix(resources): fix resource_kind field to use physical boolean per spec and add cloud resource types #10647

Open
HAL9000 wants to merge 3 commits from fix/v360/resource-kind-field into master
81 changed files with 833 additions and 251 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: branch
description: "Cross-repo branch identity linking branches with the same name and HEAD"
resource_kind: virtual
physical: false
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: commit
description: "Cross-repo commit identity linking commits with the same hash"
resource_kind: virtual
physical: false
sandbox_strategy: none
user_addable: false
built_in: true
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: container-exec-env
description: "Execution environment variables and configuration inside a container"
resource_kind: physical
physical: true
sandbox_strategy: snapshot
user_addable: false
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: container-image
description: "A container image identified by tag and/or digest"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: true
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: container-mount
description: "A bind mount or volume mount inside a container instance"
resource_kind: physical
physical: true
sandbox_strategy: snapshot
user_addable: false
built_in: true
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: container-network
description: "A container network (bridge, host, overlay, macvlan, or none)"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: container-port
description: "A port mapping between host and container (e.g. 8080:80/tcp)"
resource_kind: physical
physical: true
sandbox_strategy: snapshot
user_addable: false
built_in: true
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: container-runtime
description: "A container runtime engine (Docker, Podman, containerd, etc.)"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: true
built_in: true
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: container-volume
description: "A named container volume managed by the runtime engine"
resource_kind: physical
physical: true
sandbox_strategy: snapshot
user_addable: true
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: directory
description: "Cross-layer directory identity linking equivalent physical directories"
resource_kind: virtual
physical: false
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: executable
description: "System executable (language runtime, compiler, LSP server binary). Discovered from container or host PATH."
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: true
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: file
description: "Cross-layer file identity linking equivalent physical files"
resource_kind: virtual
physical: false
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: fs-directory
description: "A filesystem directory"
resource_kind: physical
physical: true
sandbox_strategy: copy_on_write
user_addable: true
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: fs-file
description: "A regular file on the local filesystem"
resource_kind: physical
physical: true
sandbox_strategy: copy_on_write
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: fs-hardlink
description: "A hard link on the local filesystem — a file with link count > 1"
resource_kind: physical
physical: true
sandbox_strategy: copy_on_write
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: fs-mount
description: "A physical mount point on the local system"
resource_kind: physical
physical: true
sandbox_strategy: copy_on_write
user_addable: true
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: fs-symlink
description: "A symbolic link on the local filesystem"
resource_kind: physical
physical: true
sandbox_strategy: copy_on_write
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: git-branch
description: "A named branch ref (e.g., main, feature/auth)"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: git-checkout
description: "A local git repository checkout"
resource_kind: physical
physical: true
sandbox_strategy: git_worktree
user_addable: true
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: git-commit
description: "A specific commit object"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: git-remote
description: "A remote URL configured on a git repo"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: git-stash
description: "A stash entry (e.g., stash@{0})"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: git-submodule
description: "A submodule reference — a pointer to another git repository at a specific commit and path"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: git-tag
description: "A tag ref — lightweight or annotated (e.g., v1.0.0)"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: git-tree-entry
description: "A blob entry in a tree — a specific file's content at a specific path and mode"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: git-tree
description: "A tree object — a directory listing at a specific commit"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version: "1.0"
name: git
description: "A git repository — the object database, refs, and full history"
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: true
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: lsp-document
description: "Text document tracked by an LSP server. Provides semantic access: types, diagnostics, completions, references, symbols."
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: lsp-server
description: "LSP server definition and running process. Provides semantic services for one or more languages."
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: true
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: lsp-workspace
description: "Workspace root tracked by an LSP server. Corresponds to a workspaceFolder in the LSP specification."
resource_kind: physical
physical: true
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: remote
description: "Cross-repo remote identity. Links git-remote resources across different repos that point to the same URL."
resource_kind: virtual
physical: false
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: submodule
description: "Cross-repo submodule identity. Links git-submodule resources across different repos with the same submodule URL and path."
resource_kind: virtual
physical: false
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: symlink
description: "Cross-layer symlink identity. Links fs-symlink and git-tree-entry (mode 120000) resources with the same name and target."
resource_kind: virtual
physical: false
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: tag
description: "Cross-repo tag identity linking tags with the same name and target"
resource_kind: virtual
physical: false
sandbox_strategy: none
user_addable: false
built_in: true
+1 -1
View File
@@ -2,7 +2,7 @@
schema_version: "1.0"
name: tree
description: "Cross-repo/cross-commit tree identity linking trees with the same hash"
resource_kind: virtual
physical: false
sandbox_strategy: none
user_addable: false
built_in: true
+3 -3
View File
@@ -9,7 +9,7 @@ Feature: Cloud Infrastructure Resources
Given a cloud resource type definition for "aws-account"
Then the cloud type name should be "aws-account"
And the cloud type sandbox_strategy should be "none"
And the cloud type resource_kind should be "physical"
And the cloud type physical should be True
And the cloud type should be built_in
Scenario: AWS account resource type has correct CLI args
@@ -58,7 +58,7 @@ Feature: Cloud Infrastructure Resources
Given a cloud resource type definition for "gcp"
Then the cloud type name should be "gcp"
And the cloud type sandbox_strategy should be "none"
And the cloud type resource_kind should be "physical"
And the cloud type physical should be True
And the cloud type should be built_in
Scenario: GCP resource type has correct CLI args
@@ -94,7 +94,7 @@ Feature: Cloud Infrastructure Resources
Given a cloud resource type definition for "azure"
Then the cloud type name should be "azure"
And the cloud type sandbox_strategy should be "none"
And the cloud type resource_kind should be "physical"
And the cloud type physical should be True
And the cloud type should be built_in
Scenario: Azure resource type has correct CLI args
+2 -2
View File
@@ -592,13 +592,13 @@ Feature: Consolidated Resource
Scenario: Physical resource kind
Given a resource type config with resource_kind "physical"
When I create a ResourceTypeSpec from config
Then the resource type resource_kind should be "physical"
Then the resource type physical should be True
Scenario: Virtual resource kind
Given a resource type config with resource_kind "virtual" and equivalence set
When I create a ResourceTypeSpec from config
Then the resource type resource_kind should be "virtual"
Then the resource type physical should be False
Scenario: Virtual resource kind without equivalence is rejected
+2 -2
View File
@@ -146,9 +146,9 @@ Feature: Built-in Resource Type Configurations
Scenario: Both built-ins are physical resources
Given the built-in git-checkout YAML file
When I load the built-in YAML via from_yaml_file
Then the builtin schema resource_kind should be "physical"
Then the builtin schema physical should be True
Scenario: fs-directory is physical
Given the built-in fs-directory YAML file
When I load the built-in YAML via from_yaml_file
Then the builtin schema resource_kind should be "physical"
Then the builtin schema physical should be True
@@ -12,7 +12,7 @@ Feature: Container infrastructure resource types
When I load the YAML via from_yaml_file for container_infra
Then the container_infra schema should be valid
And the container_infra schema name should be "<type_name>"
And the container_infra schema resource_kind should be "physical"
And the container_infra schema physical should be True
Examples:
| type_name |
@@ -10,7 +10,7 @@ Feature: Deferred Physical Resource Types
When I load the YAML via from_config for phys_deferred
Then the spec should be valid for phys_deferred
And the spec name should be "git" for phys_deferred
And the spec resource_kind should be "physical" for phys_deferred
And the spec physical should be True for phys_deferred
And the spec sandbox_strategy should be "none" for phys_deferred
And the spec user_addable should be true for phys_deferred
And the spec built_in should be true for phys_deferred
@@ -20,7 +20,7 @@ Feature: Deferred Physical Resource Types
When I load the YAML via from_config for phys_deferred
Then the spec should be valid for phys_deferred
And the spec name should be "git-remote" for phys_deferred
And the spec resource_kind should be "physical" for phys_deferred
And the spec physical should be True for phys_deferred
And the spec user_addable should be false for phys_deferred
Scenario: git-branch YAML validates for phys_deferred
@@ -72,7 +72,7 @@ Feature: Deferred Physical Resource Types
When I load the YAML via from_config for phys_deferred
Then the spec should be valid for phys_deferred
And the spec name should be "fs-symlink" for phys_deferred
And the spec resource_kind should be "physical" for phys_deferred
And the spec physical should be True for phys_deferred
And the spec user_addable should be false for phys_deferred
Scenario: fs-hardlink YAML validates for phys_deferred
@@ -80,7 +80,7 @@ Feature: Deferred Physical Resource Types
When I load the YAML via from_config for phys_deferred
Then the spec should be valid for phys_deferred
And the spec name should be "fs-hardlink" for phys_deferred
And the spec resource_kind should be "physical" for phys_deferred
And the spec physical should be True for phys_deferred
And the spec user_addable should be false for phys_deferred
# ── Parent/child constraints for phys_deferred ─────────────
@@ -28,17 +28,17 @@ Feature: Deferred Virtual Resource Types
Scenario: remote is a virtual resource type for virt_deferred
Given the built-in remote YAML file for virt_deferred
When I load the YAML via schema for virt_deferred
Then the virt_deferred schema resource_kind should be "virtual"
Then the virt_deferred schema physical should be False
Scenario: submodule is a virtual resource type for virt_deferred
Given the built-in submodule YAML file for virt_deferred
When I load the YAML via schema for virt_deferred
Then the virt_deferred schema resource_kind should be "virtual"
Then the virt_deferred schema physical should be False
Scenario: symlink is a virtual resource type for virt_deferred
Given the built-in symlink YAML file for virt_deferred
When I load the YAML via schema for virt_deferred
Then the virt_deferred schema resource_kind should be "virtual"
Then the virt_deferred schema physical should be False
# ── Not user-addable ──────────────────────────────────────
@@ -149,21 +149,21 @@ Feature: Deferred Virtual Resource Types
When I load the YAML as a ResourceTypeSpec for virt_deferred
Then the virt_deferred domain model should be valid
And the virt_deferred domain model name should be "remote"
And the virt_deferred domain model resource_kind should be "virtual"
And the virt_deferred domain model physical should be False
Scenario: submodule YAML loads as domain model for virt_deferred
Given the built-in submodule YAML file for virt_deferred
When I load the YAML as a ResourceTypeSpec for virt_deferred
Then the virt_deferred domain model should be valid
And the virt_deferred domain model name should be "submodule"
And the virt_deferred domain model resource_kind should be "virtual"
And the virt_deferred domain model physical should be False
Scenario: symlink YAML loads as domain model for virt_deferred
Given the built-in symlink YAML file for virt_deferred
When I load the YAML as a ResourceTypeSpec for virt_deferred
Then the virt_deferred domain model should be valid
And the virt_deferred domain model name should be "symlink"
And the virt_deferred domain model resource_kind should be "virtual"
And the virt_deferred domain model physical should be False
# ── Child types ───────────────────────────────────────────
+2 -2
View File
@@ -14,7 +14,7 @@ Feature: Filesystem Resource Type Configurations
Scenario: fs-mount has correct resource kind
Given the built-in fs-mount YAML file
When I load the built-in YAML via from_yaml_file
Then the builtin schema resource_kind should be "physical"
Then the builtin schema physical should be True
Scenario: fs-mount has correct sandbox strategy
Given the built-in fs-mount YAML file
@@ -123,7 +123,7 @@ Feature: Filesystem Resource Type Configurations
Scenario: fs-file has correct resource kind
Given the built-in fs-file YAML file
When I load the built-in YAML via from_yaml_file
Then the builtin schema resource_kind should be "physical"
Then the builtin schema physical should be True
Scenario: fs-file has copy_on_write sandbox
Given the built-in fs-file YAML file
+1 -1
View File
@@ -11,7 +11,7 @@ Feature: LSP Resource Types
When I load the YAML via schema for lsp_rt
Then the lsp_rt schema should be valid
And the lsp_rt schema name should be "<type_name>"
And the lsp_rt schema resource_kind should be "physical"
And the lsp_rt schema physical should be True
And the lsp_rt schema sandbox_strategy should be "none"
Examples:
@@ -0,0 +1,102 @@
Feature: Resource Type Physical Boolean Field
As a CleverAgents developer
I want resource types to use a physical boolean field per spec
So that the schema matches the specification (UAT #5124)
# ── Physical boolean field ──────────────────────────────────────────────
Scenario: ResourceTypeSpec uses physical boolean field
Given a resource type config with physical True
When I create a ResourceTypeSpec from config
Then the spec physical field should be True
And the spec should be a physical type
Scenario: ResourceTypeSpec virtual type uses physical False
Given a resource type config with physical False
When I create a ResourceTypeSpec from config
Then the spec physical field should be False
And the spec should be a virtual type
Scenario: ResourceTypeSpec from_config accepts physical boolean
Given a resource type config dict with physical True
When I call from_config on ResourceTypeSpec
Then the resulting spec physical should be True
Scenario: ResourceTypeSpec from_config accepts legacy resource_kind physical
Given a resource type config dict with resource_kind physical
When I call from_config on ResourceTypeSpec
Then the resulting spec physical should be True
Scenario: ResourceTypeSpec from_config accepts legacy resource_kind virtual
Given a resource type config dict with resource_kind virtual
When I call from_config on ResourceTypeSpec
Then the resulting spec physical should be False
Scenario: ResourceTypeSpec as_cli_dict outputs physical field
Given a physical resource type spec
When I call as_cli_dict on the spec
Then the cli dict should contain physical True
And the cli dict should not contain resource_kind
Scenario: ResourceTypeConfigSchema uses physical boolean field
Given a resource type YAML with physical true
When I parse the YAML with ResourceTypeConfigSchema
Then the schema physical field should be True
Scenario: ResourceTypeConfigSchema rejects missing physical field
Given a resource type YAML without physical field
When I parse the YAML with ResourceTypeConfigSchema
Then a validation error should be raised
# ── AWS RDS resource type ────────────────────────────────────────────────
Scenario: aws-rds-instance type exists in registry
Given the physical boolean resource type registry
When I look up resource type "aws-rds-instance"
Then the rds type should exist
And the rds type physical should be True
And the rds type should inherit from "cloud-database"
Scenario: aws-rds-instance has required CLI args
Given the physical boolean resource type registry
When I look up resource type "aws-rds-instance"
Then the rds type should have cli arg "engine"
And the rds type should have cli arg "instance-class"
And the rds type cli arg "engine" should be required
And the rds type cli arg "instance-class" should be required
Scenario: aws-rds-instance has optional CLI args
Given the physical boolean resource type registry
When I look up resource type "aws-rds-instance"
Then the rds type should have cli arg "db-name"
And the rds type should have cli arg "port"
And the rds type cli arg "db-name" should not be required
And the rds type cli arg "port" should not be required
Scenario: cloud-database base type exists
Given the physical boolean resource type registry
When I look up resource type "cloud-database"
Then the cloud-database type should exist
And the cloud-database type physical should be True
# ── AWS S3 resource type ─────────────────────────────────────────────────
Scenario: aws-s3-bucket type has S3-specific CLI args
Given the physical boolean resource type registry
When I look up resource type "aws-s3-bucket"
Then the s3 type should have cli arg "bucket-name"
And the s3 type should have cli arg "versioning"
And the s3 type cli arg "bucket-name" should be required
# ── All built-in types use physical boolean ──────────────────────────────
Scenario: All built-in physical types have physical True
Given the physical boolean resource type registry
When I check all physical boolean built-in types
Then all physical types should have physical True
And no type should have a resource_kind field
Scenario: All built-in virtual types have physical False
Given the physical boolean resource type registry
When I check all physical boolean built-in types
Then all virtual types should have physical False
+2 -2
View File
@@ -25,7 +25,7 @@ Feature: Virtual Core Resource Types
Scenario Outline: Virtual core types have resource_kind virtual for vcore
Given the built-in <type_name> virtual YAML file for vcore
When I load the virtual YAML via from_yaml_file for vcore
Then the vcore schema resource_kind should be "virtual"
Then the vcore schema physical should be False
Examples:
| type_name |
@@ -260,7 +260,7 @@ Feature: Virtual Core Resource Types
Given the bootstrap builtin type definitions for vcore
When I find the virtual type "<type_name>" in builtin definitions for vcore
Then the found builtin type user_addable should be false for vcore
And the found builtin type resource_kind should be "virtual" for vcore
And the found builtin type physical should be False for vcore
And the found builtin type should have equivalence for vcore
Examples:
+1 -1
View File
@@ -322,7 +322,7 @@ def step_cloud_type_strategy(context: Any, strategy: str) -> None:
assert context.cloud_type_def["sandbox_strategy"] == strategy # type: ignore[attr-defined]
@then('the cloud type resource_kind should be "{kind}"')
@then("the cloud type physical should be {value}")
def step_cloud_type_kind(context: Any, kind: str) -> None:
"""Check cloud type resource kind."""
assert context.cloud_type_def["resource_kind"] == kind # type: ignore[attr-defined]
@@ -60,7 +60,7 @@ def step_yaml_nonexistent_parent(context: Context) -> None:
{
"name": "acme/child",
"description": "Child type",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"inherits": "nonexistent-parent",
"user_addable": True,
@@ -80,7 +80,7 @@ def step_yaml_chain_error(context: Context) -> None:
{
"name": "acme/chain-child",
"description": "Chain child",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"inherits": "git-checkout",
}
@@ -94,7 +94,7 @@ def step_yaml_duplicate(context: Context) -> None:
{
"name": "git-checkout",
"description": "Duplicate",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"user_addable": True,
"built_in": True,
@@ -165,7 +165,7 @@ def step_register_custom_type(context: Context, name: str) -> None:
{
"name": name,
"description": f"Custom type {name}",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"user_addable": True,
}
@@ -184,7 +184,7 @@ def step_register_child_type(context: Context, child: str, parent: str) -> None:
{
"name": child,
"description": f"Child type {child}",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"inherits": parent,
}
@@ -62,9 +62,11 @@ def step_builtin_schema_sandbox(context: object, strategy: str) -> None:
assert context.builtin_schema.sandbox_strategy == strategy # type: ignore[attr-defined]
@then('the builtin schema resource_kind should be "{kind}"')
@then("the builtin schema physical should be {value}")
def step_builtin_schema_kind(context: object, kind: str) -> None:
assert context.builtin_schema.resource_kind == kind # type: ignore[attr-defined]
# kind is "True" or "False" string from feature file
expected = kind.lower() == "true"
assert context.builtin_schema.physical is expected # type: ignore[attr-defined]
@then('the builtin schema parent_types should contain "{parent}"')
@@ -19,10 +19,7 @@ from cleveragents.application.services._resource_registry_container import (
from cleveragents.application.services._resource_registry_data import (
BUILTIN_TYPES,
)
from cleveragents.domain.models.core.resource_type import (
ResourceKind,
ResourceTypeSpec,
)
from cleveragents.domain.models.core.resource_type import ResourceTypeSpec
from cleveragents.resource.schema import ResourceTypeConfigSchema
_EXAMPLES_DIR = (
@@ -78,7 +75,7 @@ def step_schema_name(context: object, name: str) -> None:
assert context.ci_schema.name == name
@then('the container_infra schema resource_kind should be "{kind}"')
@then("the container_infra schema physical should be {value}")
def step_schema_kind(context: object, kind: str) -> None:
assert context.ci_schema.resource_kind == kind
@@ -100,7 +97,7 @@ def step_domain_name(context: object, name: str) -> None:
@then("the container_infra domain model resource_kind should be physical")
def step_domain_kind_physical(context: object) -> None:
assert context.ci_spec.resource_kind == ResourceKind.PHYSICAL
assert context.ci_spec.physical is True
# ── Then steps: parent/child ─────────────────────────────
@@ -72,7 +72,7 @@ def step_spec_name_for_phys_deferred(context: object, name: str) -> None:
assert context.phys_deferred_spec.name == name # type: ignore[attr-defined]
@then('the spec resource_kind should be "{kind}" for phys_deferred')
@then("the spec physical should be {value} for phys_deferred")
def step_spec_kind_for_phys_deferred(context: object, kind: str) -> None:
actual = context.phys_deferred_spec.resource_kind # type: ignore[attr-defined]
assert str(actual) == kind or actual.value == kind, (
@@ -186,7 +186,7 @@ def _base_physical_config(**overrides: Any) -> dict[str, Any]:
"""Create a minimal physical type config."""
config: dict[str, Any] = {
"name": "testns/phys-test",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": False,
@@ -98,7 +98,7 @@ def step_schema_name_virt_deferred(context: object, name: str) -> None:
assert actual == name, f"Expected schema name '{name}', got '{actual}'"
@then('the virt_deferred schema resource_kind should be "{kind}"')
@then("the virt_deferred schema physical should be {value}")
def step_schema_kind_virt_deferred(context: object, kind: str) -> None:
"""Assert the resource kind matches."""
actual = context.virt_deferred_schema.resource_kind # type: ignore[attr-defined]
@@ -212,7 +212,7 @@ def step_domain_name_virt_deferred(context: object, name: str) -> None:
assert actual == name, f"Expected domain name '{name}', got '{actual}'"
@then('the virt_deferred domain model resource_kind should be "{kind}"')
@then("the virt_deferred domain model physical should be {value}")
def step_domain_kind_virt_deferred(context: object, kind: str) -> None:
"""Assert the domain model resource kind matches."""
actual = context.virt_deferred_domain.resource_kind # type: ignore[attr-defined]
@@ -411,7 +411,7 @@ def step_create_virtual_no_equiv_virt_deferred(context: object) -> None:
ResourceTypeSpec.from_config(
{
"name": "test-virtual-no-equiv",
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"built_in": True,
}
@@ -427,7 +427,7 @@ def step_create_no_name_virt_deferred(context: object) -> None:
try:
ResourceTypeSpec.from_config(
{
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
}
)
@@ -513,7 +513,7 @@ def step_create_criteria_non_string_virt_deferred(context: object) -> None:
ResourceTypeSpec.from_config(
{
"name": "test-bad-criteria-int",
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"built_in": True,
"equivalence": {"criteria": [123], "description": "bad"},
@@ -533,7 +533,7 @@ def step_create_criteria_empty_string_virt_deferred(context: object) -> None:
ResourceTypeSpec.from_config(
{
"name": "test-bad-criteria-empty",
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"built_in": True,
"equivalence": {"criteria": [""], "description": "bad"},
+1 -1
View File
@@ -92,7 +92,7 @@ def step_schema_name_lsp_rt(context: object, name: str) -> None:
assert actual == name, f"Expected name '{name}', got '{actual}'"
@then('the lsp_rt schema resource_kind should be "{kind}"')
@then("the lsp_rt schema physical should be {value}")
def step_schema_kind_lsp_rt(context: object, kind: str) -> None:
"""Assert the resource kind matches."""
actual = context.lsp_rt_schema.resource_kind # type: ignore[attr-defined]
+8 -6
View File
@@ -6,7 +6,6 @@ import tempfile
from behave import given, then, when
from cleveragents.domain.models.core.resource_type import (
ResourceKind,
ResourceTypeSpec,
SandboxStrategy,
)
@@ -122,7 +121,7 @@ def step_config_with_parent_and_child(context, parent, child):
@given("a resource type config dict without name")
def step_config_without_name(context):
context.rt_config = {
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "git_worktree",
}
@@ -134,7 +133,7 @@ def step_config_without_kind(context):
@given("a resource type config dict without sandbox_strategy")
def step_config_without_strategy(context):
context.rt_config = {"name": "myorg/test", "resource_kind": "physical"}
context.rt_config = {"name": "myorg/test", "physical": True}
@given("a full resource type config dict")
@@ -480,9 +479,12 @@ def step_rt_creation_fail(context, fragment):
)
@then('the resource type resource_kind should be "{kind}"')
def step_rt_kind(context, kind):
assert context.rt_spec.resource_kind == ResourceKind(kind)
@then("the resource type physical should be {value}")
def step_rt_kind(context, value):
expected = value.lower() == "true"
assert context.rt_spec.physical is expected, (
f"Expected physical={expected}, got {context.rt_spec.physical}"
)
@then('the resource type sandbox_strategy should be "{strategy}"')
@@ -0,0 +1,375 @@
"""Step definitions for resource_type_physical_boolean.feature.
Tests for the physical boolean field replacing resource_kind enum,
and for the new AWS RDS and S3 resource types.
"""
from __future__ import annotations
from typing import Any
from behave import given, then, when
from pydantic import ValidationError
from cleveragents.application.services._resource_registry_cloud import (
AWS_TYPES,
CLOUD_BASE_TYPES,
)
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
# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------
@given("a resource type config with physical True")
def step_config_physical_true(context: Any) -> None:
"""Set up a physical resource type config."""
context.config = {
"name": "test-physical",
"description": "A physical test type",
"physical": True,
"sandbox_strategy": "copy_on_write",
"built_in": True,
}
@given("a resource type config with physical False")
def step_config_physical_false(context: Any) -> None:
"""Set up a virtual resource type config."""
context.config = {
"name": "test-virtual",
"description": "A virtual test type",
"physical": False,
"sandbox_strategy": "none",
"built_in": True,
"equivalence": {"criteria": ["content_hash"]},
}
@given("a resource type config dict with physical True")
def step_config_dict_physical_true(context: Any) -> None:
"""Set up a config dict with physical boolean."""
context.config = {
"name": "test-type",
"physical": True,
"sandbox_strategy": "none",
"built_in": True,
}
@given("a resource type config dict with resource_kind physical")
def step_config_dict_rk_physical(context: Any) -> None:
"""Set up a legacy config dict with resource_kind string."""
context.config = {
"name": "test-type",
"resource_kind": "physical",
"sandbox_strategy": "none",
"built_in": True,
}
@given("a resource type config dict with resource_kind virtual")
def step_config_dict_rk_virtual(context: Any) -> None:
"""Set up a legacy config dict with resource_kind virtual."""
context.config = {
"name": "test-virtual",
"resource_kind": "virtual",
"sandbox_strategy": "none",
"built_in": True,
"equivalence": {"criteria": ["content_hash"]},
}
@given("a physical resource type spec")
def step_physical_spec(context: Any) -> None:
"""Create a physical ResourceTypeSpec."""
context.spec = ResourceTypeSpec(
name="test-physical",
physical=True,
sandbox_strategy="copy_on_write",
built_in=True,
)
@given("a resource type YAML with physical true")
def step_yaml_physical_true(context: Any) -> None:
"""Set up YAML with physical: true."""
context.yaml_string = """
name: myorg/test-type
description: A test resource type
physical: true
sandbox_strategy: none
"""
@given("a resource type YAML without physical field")
def step_yaml_no_physical(context: Any) -> None:
"""Set up YAML without physical field."""
context.yaml_string = """
name: myorg/test-type
description: A test resource type
sandbox_strategy: none
"""
@given("the physical boolean resource type registry")
def step_builtin_registry(context: Any) -> None:
"""Load the built-in resource type registry."""
context.registry = {t["name"]: t for t in BUILTIN_TYPES}
context.cloud_base = {t["name"]: t for t in CLOUD_BASE_TYPES}
context.aws_types = {t["name"]: t for t in AWS_TYPES}
# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------
@when("I create a ResourceTypeSpec from config")
def step_create_spec_from_config(context: Any) -> None:
"""Create a ResourceTypeSpec from the config."""
context.spec = ResourceTypeSpec.from_config(context.config)
@when("I call from_config on ResourceTypeSpec")
def step_call_from_config(context: Any) -> None:
"""Call ResourceTypeSpec.from_config."""
context.spec = ResourceTypeSpec.from_config(context.config)
@when("I call as_cli_dict on the spec")
def step_call_as_cli_dict(context: Any) -> None:
"""Call as_cli_dict on the spec."""
context.cli_dict = context.spec.as_cli_dict()
@when("I parse the YAML with ResourceTypeConfigSchema")
def step_parse_yaml(context: Any) -> None:
"""Parse YAML with ResourceTypeConfigSchema."""
try:
context.schema = ResourceTypeConfigSchema.from_yaml(context.yaml_string)
context.validation_error = None
except (ValidationError, ValueError) as e:
context.schema = None
context.validation_error = e
@when('I look up resource type "{type_name}"')
def step_lookup_type(context: Any, type_name: str) -> None:
"""Look up a resource type in the registry."""
context.type_name = type_name
context.type_def = context.registry.get(type_name)
@when("I check all physical boolean built-in types")
def step_check_all_types(context: Any) -> None:
"""Check all built-in types."""
context.all_types = BUILTIN_TYPES
# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------
@then("the spec physical field should be True")
def step_spec_physical_true(context: Any) -> None:
"""Assert spec.physical is True."""
assert context.spec.physical is True, (
f"Expected spec.physical to be True, got {context.spec.physical}"
)
@then("the spec physical field should be False")
def step_spec_physical_false(context: Any) -> None:
"""Assert spec.physical is False."""
assert context.spec.physical is False, (
f"Expected spec.physical to be False, got {context.spec.physical}"
)
@then("the spec should be a physical type")
def step_spec_is_physical(context: Any) -> None:
"""Assert spec is a physical type."""
assert context.spec.physical is True, "Expected physical type"
@then("the spec should be a virtual type")
def step_spec_is_virtual(context: Any) -> None:
"""Assert spec is a virtual type."""
assert context.spec.physical is False, "Expected virtual type"
@then("the resulting spec physical should be True")
def step_result_physical_true(context: Any) -> None:
"""Assert resulting spec.physical is True."""
assert context.spec.physical is True, (
f"Expected physical=True, got {context.spec.physical}"
)
@then("the resulting spec physical should be False")
def step_result_physical_false(context: Any) -> None:
"""Assert resulting spec.physical is False."""
assert context.spec.physical is False, (
f"Expected physical=False, got {context.spec.physical}"
)
@then("the cli dict should contain physical True")
def step_cli_dict_physical_true(context: Any) -> None:
"""Assert cli dict contains physical: True."""
assert "physical" in context.cli_dict, "cli_dict missing 'physical' key"
assert context.cli_dict["physical"] is True, (
f"Expected physical=True in cli_dict, got {context.cli_dict.get('physical')}"
)
@then("the cli dict should not contain resource_kind")
def step_cli_dict_no_resource_kind(context: Any) -> None:
"""Assert cli dict does not contain resource_kind."""
assert "resource_kind" not in context.cli_dict, (
"cli_dict should not contain 'resource_kind' (deprecated field)"
)
@then("the schema physical field should be True")
def step_schema_physical_true(context: Any) -> None:
"""Assert schema.physical is True."""
assert context.schema is not None, "Schema should not be None"
assert context.schema.physical is True, (
f"Expected schema.physical=True, got {context.schema.physical}"
)
@then("a validation error should be raised")
def step_validation_error_raised(context: Any) -> None:
"""Assert a validation error was raised."""
assert context.validation_error is not None, (
"Expected a validation error but none was raised"
)
@then("the rds type should exist")
def step_rds_type_exists(context: Any) -> None:
"""Assert aws-rds-instance type exists."""
assert context.type_def is not None, (
f"Resource type '{context.type_name}' not found in registry"
)
@then("the rds type physical should be True")
def step_rds_physical_true(context: Any) -> None:
"""Assert aws-rds-instance has physical=True."""
assert context.type_def.get("physical") is True, (
f"Expected physical=True for {context.type_name}, "
f"got {context.type_def.get('physical')}"
)
@then('the rds type should inherit from "{parent}"')
def step_rds_inherits(context: Any, parent: str) -> None:
"""Assert aws-rds-instance inherits from the given parent."""
assert context.type_def.get("inherits") == parent, (
f"Expected {context.type_name} to inherit from '{parent}', "
f"got '{context.type_def.get('inherits')}'"
)
@then('the rds type should have cli arg "{arg_name}"')
def step_rds_has_cli_arg(context: Any, arg_name: str) -> None:
"""Assert aws-rds-instance has the given CLI arg."""
cli_args = context.type_def.get("cli_args", [])
arg_names = [a["name"] for a in cli_args]
assert arg_name in arg_names, (
f"Expected {context.type_name} to have cli arg '{arg_name}', got {arg_names}"
)
@then('the rds type cli arg "{arg_name}" should be required')
def step_rds_arg_required(context: Any, arg_name: str) -> None:
"""Assert the given CLI arg is required."""
cli_args = context.type_def.get("cli_args", [])
arg = next((a for a in cli_args if a["name"] == arg_name), None)
assert arg is not None, f"CLI arg '{arg_name}' not found"
assert arg.get("required") is True, f"Expected cli arg '{arg_name}' to be required"
@then('the rds type cli arg "{arg_name}" should not be required')
def step_rds_arg_not_required(context: Any, arg_name: str) -> None:
"""Assert the given CLI arg is not required."""
cli_args = context.type_def.get("cli_args", [])
arg = next((a for a in cli_args if a["name"] == arg_name), None)
assert arg is not None, f"CLI arg '{arg_name}' not found"
assert arg.get("required") is not True, (
f"Expected cli arg '{arg_name}' to not be required"
)
@then("the cloud-database type should exist")
def step_cloud_db_exists(context: Any) -> None:
"""Assert cloud-database type exists."""
assert context.type_def is not None, (
f"Resource type '{context.type_name}' not found in registry"
)
@then("the cloud-database type physical should be True")
def step_cloud_db_physical(context: Any) -> None:
"""Assert cloud-database has physical=True."""
assert context.type_def.get("physical") is True, (
f"Expected physical=True for {context.type_name}"
)
@then('the s3 type should have cli arg "{arg_name}"')
def step_s3_has_cli_arg(context: Any, arg_name: str) -> None:
"""Assert aws-s3-bucket has the given CLI arg."""
cli_args = context.type_def.get("cli_args", [])
arg_names = [a["name"] for a in cli_args]
assert arg_name in arg_names, (
f"Expected {context.type_name} to have cli arg '{arg_name}', got {arg_names}"
)
@then('the s3 type cli arg "{arg_name}" should be required')
def step_s3_arg_required(context: Any, arg_name: str) -> None:
"""Assert the given S3 CLI arg is required."""
cli_args = context.type_def.get("cli_args", [])
arg = next((a for a in cli_args if a["name"] == arg_name), None)
assert arg is not None, f"CLI arg '{arg_name}' not found"
assert arg.get("required") is True, f"Expected cli arg '{arg_name}' to be required"
@then("all physical types should have physical True")
def step_all_physical_have_true(context: Any) -> None:
"""Assert all physical types have physical=True."""
for t in context.all_types:
if t.get("physical") is True:
assert t.get("physical") is True, (
f"Type '{t['name']}' has physical={t.get('physical')}"
)
@then("no type should have a resource_kind field")
def step_no_resource_kind_field(context: Any) -> None:
"""Assert no built-in type has a resource_kind field."""
for t in context.all_types:
assert "resource_kind" not in t, (
f"Type '{t['name']}' still has deprecated 'resource_kind' field"
)
@then("all virtual types should have physical False")
def step_all_virtual_have_false(context: Any) -> None:
"""Assert all virtual types have physical=False."""
for t in context.all_types:
if t.get("physical") is False:
assert t.get("physical") is False, (
f"Type '{t['name']}' has physical={t.get('physical')}"
)
@@ -222,7 +222,7 @@ def step_vcore_schema_name(context: object, name: str) -> None:
assert _ctx(context).schema.name == name
@then('the vcore schema resource_kind should be "{kind}"')
@then("the vcore schema physical should be {value}")
def step_vcore_schema_kind(context: object, kind: str) -> None:
assert _ctx(context).schema.resource_kind == kind
@@ -375,7 +375,7 @@ def step_found_builtin_not_addable(context: object) -> None:
)
@then('the found builtin type resource_kind should be "{kind}" for vcore')
@then("the found builtin type physical should be {value} for vcore")
def step_found_builtin_kind(context: object, kind: str) -> None:
entry: dict[str, Any] = _ctx(context).found_builtin
assert entry["resource_kind"] == kind, (
@@ -411,7 +411,7 @@ def step_virtual_config_missing_equiv(context: object) -> None:
"""Create a virtual type config dict without equivalence."""
_ctx(context).test_config = {
"name": "test-virtual-no-equiv",
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -432,7 +432,7 @@ def step_virtual_config_empty_criteria(context: object) -> None:
"""Create a virtual type config with equivalence but empty criteria."""
_ctx(context).test_config = {
"name": "test-virtual-empty-criteria",
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
+4 -4
View File
@@ -13,14 +13,14 @@ Contextual Binding Resolves Single Resource
... from unittest.mock import MagicMock
... from cleveragents.core.exceptions import NotFoundError
... from cleveragents.domain.models.core.resource import Resource, PhysVirt
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, ResourceKind, SandboxStrategy
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, SandboxStrategy
... from cleveragents.domain.models.core.project import NamespacedProject, LinkedResource
... from cleveragents.domain.models.core.tool import Tool, ToolSource, ToolType, ResourceSlot, ResourceAccessMode, BindingMode
... from cleveragents.application.services.binding_resolution_service import BindingResolutionService
... registry = MagicMock()
... res = Resource(resource_id="01HGZ6FE0AQDYTR4BX00000001", name=None, resource_type_name="git-checkout", classification=PhysVirt.PHYSICAL)
... registry.show_resource = lambda x: res
... registry.show_type = lambda x: ResourceTypeSpec(name="git-checkout", resource_kind=ResourceKind.PHYSICAL, sandbox_strategy=SandboxStrategy.NONE, built_in=True)
... registry.show_type = lambda x: ResourceTypeSpec(name="git-checkout", physical=True, sandbox_strategy=SandboxStrategy.NONE, built_in=True)
... project = NamespacedProject(name="proj", namespace="local", linked_resources=[LinkedResource(resource_id="01HGZ6FE0AQDYTR4BX00000001", alias="repo")])
... tool = Tool(name="local/reader", description="Read", source=ToolSource.BUILTIN, tool_type=ToolType.TOOL, resource_slots=[ResourceSlot(name="repo", resource_type="git-checkout", access=ResourceAccessMode.READ_WRITE, binding=BindingMode.CONTEXTUAL)])
... svc = BindingResolutionService(registry)
@@ -40,14 +40,14 @@ Static Binding Resolves Named Resource
... from unittest.mock import MagicMock
... from cleveragents.core.exceptions import NotFoundError
... from cleveragents.domain.models.core.resource import Resource, PhysVirt
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, ResourceKind, SandboxStrategy
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, SandboxStrategy
... from cleveragents.domain.models.core.project import NamespacedProject
... from cleveragents.domain.models.core.tool import Tool, ToolSource, ToolType, ResourceSlot, ResourceAccessMode, BindingMode
... from cleveragents.application.services.binding_resolution_service import BindingResolutionService
... registry = MagicMock()
... res = Resource(resource_id="01HGZ6FE0AQDYTR4BX00000020", name="local/shared-config", resource_type_name="fs-directory", classification=PhysVirt.PHYSICAL)
... registry.show_resource = lambda x: res
... registry.show_type = lambda x: ResourceTypeSpec(name="fs-directory", resource_kind=ResourceKind.PHYSICAL, sandbox_strategy=SandboxStrategy.NONE, built_in=True)
... registry.show_type = lambda x: ResourceTypeSpec(name="fs-directory", physical=True, sandbox_strategy=SandboxStrategy.NONE, built_in=True)
... project = NamespacedProject(name="proj", namespace="local")
... tool = Tool(name="local/deployer", description="Deploy", source=ToolSource.BUILTIN, tool_type=ToolType.TOOL, resource_slots=[ResourceSlot(name="config_dir", resource_type="fs-directory", access=ResourceAccessMode.READ_ONLY, binding=BindingMode.STATIC, static_resource="local/shared-config")])
... svc = BindingResolutionService(registry)
@@ -46,7 +46,7 @@ def cmd_check_registration() -> None:
assert cn in names, f"{cn} not in BUILTIN_TYPES: {names}"
assert cn in ResourceTypeSpec.BUILTIN_NAMES, f"{cn} not in BUILTIN_NAMES"
entry = next(d for d in BUILTIN_TYPES if d["name"] == cn)
assert entry["resource_kind"] == "physical"
assert entry["physical"] is True
assert entry["built_in"] is True
print("check-registration-ok")
@@ -101,7 +101,7 @@ def cmd_check_yaml_loading() -> None:
assert path.exists(), f"Missing YAML: {path}"
schema = ResourceTypeConfigSchema.from_yaml_file(path)
assert schema.name == cn
assert schema.resource_kind == "physical"
assert schema.physical is True
spec = ResourceTypeSpec.from_config(schema.model_dump())
assert spec.name == cn
print("check-yaml-loading-ok")
+2 -2
View File
@@ -51,8 +51,8 @@ def _db_roundtrip() -> None:
for name in ("executable", "lsp-server", "lsp-workspace", "lsp-document"):
spec = svc.show_type(name)
assert spec is not None, f"{name} not found after bootstrap"
assert str(spec.resource_kind) == "physical", (
f"{name} kind: {spec.resource_kind}"
assert spec.physical is True, (
f"{name} kind: physical={spec.physical}"
)
assert str(spec.sandbox_strategy) == "none", (
f"{name} strategy: {spec.sandbox_strategy}"
+8 -8
View File
@@ -16,7 +16,7 @@ Link Child And Verify Tree
... from sqlalchemy.orm import sessionmaker
... from cleveragents.infrastructure.database.models import Base
... from cleveragents.infrastructure.database.repositories import ResourceTypeRepository, ResourceRepository
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, ResourceKind, SandboxStrategy
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, SandboxStrategy
... from cleveragents.domain.models.core.resource import Resource, PhysVirt, ResourceCapabilities
... engine = create_engine("sqlite:///:memory:")
... @event.listens_for(engine, "connect")
@@ -26,8 +26,8 @@ Link Child And Verify Tree
... shared_session = factory()
... rt_repo = ResourceTypeRepository(lambda: shared_session)
... res_repo = ResourceRepository(lambda: shared_session)
... parent_spec = ResourceTypeSpec(name="robot/dag-parent", description="Parent", resource_kind=ResourceKind.PHYSICAL, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=["robot/dag-child"], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... child_spec = ResourceTypeSpec(name="robot/dag-child", description="Child", resource_kind=ResourceKind.PHYSICAL, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=[], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... parent_spec = ResourceTypeSpec(name="robot/dag-parent", description="Parent", physical=True, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=["robot/dag-child"], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... child_spec = ResourceTypeSpec(name="robot/dag-child", description="Child", physical=True, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=[], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... rt_repo.create(parent_spec)
... rt_repo.create(child_spec)
... p = Resource(resource_id="01HDAGR0B0T0000000PARENT01", name=None, resource_type_name="robot/dag-parent", classification=PhysVirt.PHYSICAL, properties={}, location=None, capabilities=ResourceCapabilities(), created_at=datetime.now(tz=UTC), updated_at=datetime.now(tz=UTC))
@@ -52,7 +52,7 @@ Cycle Detection Rejects A To B To A
... from sqlalchemy.orm import sessionmaker
... from cleveragents.infrastructure.database.models import Base
... from cleveragents.infrastructure.database.repositories import ResourceTypeRepository, ResourceRepository, CycleDetectedError
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, ResourceKind, SandboxStrategy
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, SandboxStrategy
... from cleveragents.domain.models.core.resource import Resource, PhysVirt, ResourceCapabilities
... engine = create_engine("sqlite:///:memory:")
... @event.listens_for(engine, "connect")
@@ -62,7 +62,7 @@ Cycle Detection Rejects A To B To A
... shared_session = factory()
... rt_repo = ResourceTypeRepository(lambda: shared_session)
... res_repo = ResourceRepository(lambda: shared_session)
... spec = ResourceTypeSpec(name="robot/cycle-type", description="Cycle", resource_kind=ResourceKind.PHYSICAL, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=["robot/cycle-type"], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... spec = ResourceTypeSpec(name="robot/cycle-type", description="Cycle", physical=True, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=["robot/cycle-type"], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... rt_repo.create(spec)
... a = Resource(resource_id="01HDAGCYC000000000000000A1", name=None, resource_type_name="robot/cycle-type", classification=PhysVirt.PHYSICAL, properties={}, location=None, capabilities=ResourceCapabilities(), created_at=datetime.now(tz=UTC), updated_at=datetime.now(tz=UTC))
... b = Resource(resource_id="01HDAGCYC000000000000000B1", name=None, resource_type_name="robot/cycle-type", classification=PhysVirt.PHYSICAL, properties={}, location=None, capabilities=ResourceCapabilities(), created_at=datetime.now(tz=UTC), updated_at=datetime.now(tz=UTC))
@@ -89,7 +89,7 @@ Auto Discover Children
... from sqlalchemy.orm import sessionmaker
... from cleveragents.infrastructure.database.models import Base
... from cleveragents.infrastructure.database.repositories import ResourceTypeRepository, ResourceRepository
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, ResourceKind, SandboxStrategy
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, SandboxStrategy
... from cleveragents.domain.models.core.resource import Resource, PhysVirt, ResourceCapabilities
... engine = create_engine("sqlite:///:memory:")
... @event.listens_for(engine, "connect")
@@ -99,8 +99,8 @@ Auto Discover Children
... shared_session = factory()
... rt_repo = ResourceTypeRepository(lambda: shared_session)
... res_repo = ResourceRepository(lambda: shared_session)
... parent_spec = ResourceTypeSpec(name="robot/disc-parent", description="Discoverer", resource_kind=ResourceKind.PHYSICAL, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=["robot/disc-child"], auto_discovery={"enabled": True, "rules": [{"type": "robot/disc-child", "pattern": "*"}]}, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... child_spec = ResourceTypeSpec(name="robot/disc-child", description="Discovered", resource_kind=ResourceKind.PHYSICAL, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=[], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... parent_spec = ResourceTypeSpec(name="robot/disc-parent", description="Discoverer", physical=True, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=["robot/disc-child"], auto_discovery={"enabled": True, "rules": [{"type": "robot/disc-child", "pattern": "*"}]}, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... child_spec = ResourceTypeSpec(name="robot/disc-child", description="Discovered", physical=True, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=[], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... rt_repo.create(parent_spec)
... rt_repo.create(child_spec)
... p = Resource(resource_id="01HDAGR0B0TDSC00PARENT0001", name=None, resource_type_name="robot/disc-parent", classification=PhysVirt.PHYSICAL, properties={}, location=None, capabilities=ResourceCapabilities(), created_at=datetime.now(tz=UTC), updated_at=datetime.now(tz=UTC))
+4 -4
View File
@@ -14,14 +14,14 @@ ResourceTypeRepository Create And Get
... from sqlalchemy.orm import sessionmaker
... from cleveragents.infrastructure.database.models import Base, ResourceTypeModel
... from cleveragents.infrastructure.database.repositories import ResourceTypeRepository
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, ResourceKind, SandboxStrategy
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, SandboxStrategy
... engine = create_engine("sqlite:///:memory:")
... @event.listens_for(engine, "connect")
... def _fk(conn, _): conn.cursor().execute("PRAGMA foreign_keys=ON")
... Base.metadata.create_all(engine)
... factory = sessionmaker(bind=engine)
... repo = ResourceTypeRepository(factory)
... spec = ResourceTypeSpec(name="robot/test-type", description="Robot test", resource_kind=ResourceKind.PHYSICAL, sandbox_strategy=SandboxStrategy.GIT_WORKTREE, user_addable=True, cli_args=[], parent_types=[], child_types=[], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... spec = ResourceTypeSpec(name="robot/test-type", description="Robot test", physical=True, sandbox_strategy=SandboxStrategy.GIT_WORKTREE, user_addable=True, cli_args=[], parent_types=[], child_types=[], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... repo.create(spec)
... result = repo.get("robot/test-type")
... assert result is not None, "Resource type not found"
@@ -40,7 +40,7 @@ ResourceRepository Create And Resolve
... from sqlalchemy.orm import sessionmaker
... from cleveragents.infrastructure.database.models import Base
... from cleveragents.infrastructure.database.repositories import ResourceTypeRepository, ResourceRepository
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, ResourceKind, SandboxStrategy
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, SandboxStrategy
... from cleveragents.domain.models.core.resource import Resource, PhysVirt, ResourceCapabilities
... engine = create_engine("sqlite:///:memory:")
... @event.listens_for(engine, "connect")
@@ -49,7 +49,7 @@ ResourceRepository Create And Resolve
... factory = sessionmaker(bind=engine)
... rt_repo = ResourceTypeRepository(factory)
... res_repo = ResourceRepository(factory)
... spec = ResourceTypeSpec(name="robot/res-type", description="Type", resource_kind=ResourceKind.PHYSICAL, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=[], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... spec = ResourceTypeSpec(name="robot/res-type", description="Type", physical=True, sandbox_strategy=SandboxStrategy.NONE, user_addable=True, cli_args=[], parent_types=[], child_types=[], auto_discovery=None, equivalence=None, handler=None, capabilities={"read": True, "write": True, "sandbox": True, "checkpoint": False}, built_in=False)
... rt_repo.create(spec)
... res = Resource(resource_id="01HGZ6FE0AQDYTR4BX00000001", name="robot/test-res", resource_type_name="robot/res-type", classification=PhysVirt.PHYSICAL, description="Test", properties={}, location="/tmp/test", content_hash=None, sandbox_strategy=None, capabilities=ResourceCapabilities(), created_at=datetime.now(tz=UTC), updated_at=datetime.now(tz=UTC))
... res_repo.create(res)
+1 -1
View File
@@ -27,7 +27,7 @@ Bootstrap Seeds Fs Directory Type Into Registry
... assert "fs-directory" in registered, f"fs-directory not in registered: {registered}"
... spec = service.show_type("fs-directory")
... assert spec.name == "fs-directory", f"name mismatch: {spec.name}"
... kind = spec.resource_kind.value if hasattr(spec.resource_kind, "value") else str(spec.resource_kind)
... kind = "physical" if spec.physical else "virtual"
... 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}"
+1 -1
View File
@@ -27,7 +27,7 @@ Bootstrap Seeds Fs Mount Type Into Registry
... 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)
... kind = "physical" if spec.physical else "virtual"
... 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}"
+1 -1
View File
@@ -55,7 +55,7 @@ Git Checkout Type Exists After Bootstrap
... assert "git-checkout" in registered, f"git-checkout not in registered: {registered}"
... spec = service.show_type("git-checkout")
... assert spec.name == "git-checkout", f"name mismatch: {spec.name}"
... rk = spec.resource_kind.value if hasattr(spec.resource_kind, "value") else str(spec.resource_kind)
... rk = "physical" if spec.physical else "virtual"
... assert rk == "physical", f"kind mismatch: {rk}"
... ss = spec.sandbox_strategy.value if hasattr(spec.sandbox_strategy, "value") else str(spec.sandbox_strategy)
... assert ss == "git_worktree", f"sandbox mismatch: {ss}"
+2 -2
View File
@@ -15,7 +15,7 @@ Load Git Checkout Built-in YAML And Validate
... from cleveragents.resource.schema import ResourceTypeConfigSchema
... schema = ResourceTypeConfigSchema.from_yaml_file("${GIT_CHECKOUT_YAML}")
... assert schema.name == "git-checkout", f"name: {schema.name}"
... assert schema.resource_kind == "physical", f"kind: {schema.resource_kind}"
... assert schema.physical is True, f"kind: physical={schema.physical}"
... assert schema.sandbox_strategy == "git_worktree", f"strategy: {schema.sandbox_strategy}"
... assert schema.built_in is True, f"built_in: {schema.built_in}"
... assert len(schema.cli_args) == 2, f"cli_args: {len(schema.cli_args)}"
@@ -31,7 +31,7 @@ Load Fs Directory Built-in YAML And Validate
... from cleveragents.resource.schema import ResourceTypeConfigSchema
... schema = ResourceTypeConfigSchema.from_yaml_file("${FS_DIRECTORY_YAML}")
... assert schema.name == "fs-directory", f"name: {schema.name}"
... assert schema.resource_kind == "physical", f"kind: {schema.resource_kind}"
... assert schema.physical is True, f"kind: physical={schema.physical}"
... assert schema.sandbox_strategy == "copy_on_write", f"strategy: {schema.sandbox_strategy}"
... assert schema.built_in is True, f"built_in: {schema.built_in}"
... assert len(schema.cli_args) == 1, f"cli_args: {len(schema.cli_args)}"
+2 -2
View File
@@ -30,7 +30,7 @@ Load All Git Taxonomy YAMLs And Validate
... ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}config = yaml.safe_load(f)
... ${SPACE}${SPACE}${SPACE}${SPACE}spec = ResourceTypeSpec.from_config(config)
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.name == name, f"name: {spec.name}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.resource_kind.value == "physical", f"kind: {spec.resource_kind}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.physical is True, f"kind: physical={spec.physical}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.sandbox_strategy.value == sandbox, f"sandbox: {spec.sandbox_strategy}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.user_addable == addable, f"addable: {spec.user_addable}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.built_in is True, f"built_in: {spec.built_in}"
@@ -52,7 +52,7 @@ Load Filesystem Link YAMLs And Validate
... ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}config = yaml.safe_load(f)
... ${SPACE}${SPACE}${SPACE}${SPACE}spec = ResourceTypeSpec.from_config(config)
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.name == name, f"name: {spec.name}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.resource_kind.value == "physical"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.physical is True, f"kind: physical={spec.physical}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.sandbox_strategy.value == "copy_on_write"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.user_addable is False
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.built_in is True
+5 -5
View File
@@ -16,7 +16,7 @@ Load Remote Virtual Type YAML And Validate
... from cleveragents.resource.schema import ResourceTypeConfigSchema
... schema = ResourceTypeConfigSchema.from_yaml_file("${REMOTE_YAML}")
... assert schema.name == "remote", f"name: {schema.name}"
... assert schema.resource_kind == "virtual", f"kind: {schema.resource_kind}"
... assert schema.physical is False, f"kind: physical={schema.physical}"
... assert schema.sandbox_strategy == "none", f"strategy: {schema.sandbox_strategy}"
... assert schema.built_in is True, f"built_in: {schema.built_in}"
... assert schema.user_addable is False, f"user_addable: {schema.user_addable}"
@@ -35,7 +35,7 @@ Load Submodule Virtual Type YAML And Validate
... from cleveragents.resource.schema import ResourceTypeConfigSchema
... schema = ResourceTypeConfigSchema.from_yaml_file("${SUBMODULE_YAML}")
... assert schema.name == "submodule", f"name: {schema.name}"
... assert schema.resource_kind == "virtual", f"kind: {schema.resource_kind}"
... assert schema.physical is False, f"kind: physical={schema.physical}"
... assert schema.sandbox_strategy == "none", f"strategy: {schema.sandbox_strategy}"
... assert schema.built_in is True, f"built_in: {schema.built_in}"
... assert schema.user_addable is False, f"user_addable: {schema.user_addable}"
@@ -55,7 +55,7 @@ Load Symlink Virtual Type YAML And Validate
... from cleveragents.resource.schema import ResourceTypeConfigSchema
... schema = ResourceTypeConfigSchema.from_yaml_file("${SYMLINK_YAML}")
... assert schema.name == "symlink", f"name: {schema.name}"
... assert schema.resource_kind == "virtual", f"kind: {schema.resource_kind}"
... assert schema.physical is False, f"kind: physical={schema.physical}"
... assert schema.sandbox_strategy == "none", f"strategy: {schema.sandbox_strategy}"
... assert schema.built_in is True, f"built_in: {schema.built_in}"
... assert schema.user_addable is False, f"user_addable: {schema.user_addable}"
@@ -80,7 +80,7 @@ Load All Deferred Virtual YAMLs As Domain Models
... ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}config = yaml.safe_load(f)
... ${SPACE}${SPACE}${SPACE}${SPACE}spec = ResourceTypeSpec.from_config(config)
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.name == expected_name, f"name mismatch: {spec.name}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert str(spec.resource_kind) == "virtual", f"kind: {spec.resource_kind}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.physical is False, f"kind: physical={spec.physical}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.equivalence is not None, "equivalence missing"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.equivalence["criteria"] == expected_criteria
... ${SPACE}${SPACE}${SPACE}${SPACE}cli_dict = spec.as_cli_dict()
@@ -107,7 +107,7 @@ Virtual Types Survive DB Bootstrap Roundtrip
... for name, expected_criteria in [("remote", ["url"]), ("submodule", ["url", "path"]), ("symlink", ["name", "target_path"])]:
... ${SPACE}${SPACE}${SPACE}${SPACE}spec = svc.show_type(name)
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec is not None, f"{name} not found after bootstrap"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert str(spec.resource_kind.value) == "virtual", f"{name} kind: {spec.resource_kind}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.physical is False, f"{name} kind: physical={spec.physical}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.user_addable is False, f"{name} user_addable: {spec.user_addable}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.equivalence is not None, f"{name} missing equivalence"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.equivalence["criteria"] == expected_criteria, f"{name} criteria: {spec.equivalence}"
+2 -2
View File
@@ -16,7 +16,7 @@ Load Fs Mount Built-in YAML And Validate
... from cleveragents.resource.schema import ResourceTypeConfigSchema
... schema = ResourceTypeConfigSchema.from_yaml_file("${FS_MOUNT_YAML}")
... assert schema.name == "fs-mount", f"name: {schema.name}"
... assert schema.resource_kind == "physical", f"kind: {schema.resource_kind}"
... assert schema.physical is True, f"kind: physical={schema.physical}"
... assert schema.sandbox_strategy == "copy_on_write", f"strategy: {schema.sandbox_strategy}"
... assert schema.built_in is True, f"built_in: {schema.built_in}"
... assert schema.user_addable is True, f"user_addable: {schema.user_addable}"
@@ -38,7 +38,7 @@ Load Fs File Built-in YAML And Validate
... from cleveragents.resource.schema import ResourceTypeConfigSchema
... schema = ResourceTypeConfigSchema.from_yaml_file("${FS_FILE_YAML}")
... assert schema.name == "fs-file", f"name: {schema.name}"
... assert schema.resource_kind == "physical", f"kind: {schema.resource_kind}"
... assert schema.physical is True, f"kind: physical={schema.physical}"
... assert schema.sandbox_strategy == "copy_on_write", f"strategy: {schema.sandbox_strategy}"
... assert schema.built_in is True, f"built_in: {schema.built_in}"
... assert schema.user_addable is False, f"user_addable: {schema.user_addable}"
+2 -2
View File
@@ -16,7 +16,7 @@ Load Resource Type YAML Fixture And Assert Fields
... from cleveragents.resource.schema import ResourceTypeConfigSchema
... schema = ResourceTypeConfigSchema.from_yaml_file("${FIXTURE_YAML}")
... assert schema.name == "myorg/robot-test", f"name mismatch: {schema.name}"
... assert schema.resource_kind == "physical", f"kind mismatch: {schema.resource_kind}"
... assert schema.physical is True, f"kind mismatch: physical={schema.physical}"
... assert schema.sandbox_strategy == "git_worktree", f"strategy mismatch: {schema.sandbox_strategy}"
... assert len(schema.cli_args) == 2, f"cli_args count mismatch: {len(schema.cli_args)}"
... assert schema.cli_args[0].name == "path", f"first arg name mismatch: {schema.cli_args[0].name}"
@@ -38,7 +38,7 @@ Resource Type Domain Model From Config
... f.close()
... spec = ResourceTypeSpec.from_config(config)
... assert spec.name == "myorg/robot-test", f"name: {spec.name}"
... assert spec.resource_kind.value == "physical"
... assert spec.physical is True, f"kind mismatch: physical={spec.physical}"
... assert spec.sandbox_strategy.value == "git_worktree"
... cli_dict = spec.as_cli_dict()
... assert "name" in cli_dict
+5 -5
View File
@@ -28,7 +28,7 @@ Load All Virtual Core YAMLs And Validate Schema
... for name, path in VIRTUAL_TYPES.items():
... ${SPACE}${SPACE}${SPACE}${SPACE}schema = ResourceTypeConfigSchema.from_yaml_file(path)
... ${SPACE}${SPACE}${SPACE}${SPACE}assert schema.name == name, f"name mismatch: {schema.name} != {name}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert schema.resource_kind == "virtual", f"kind: {schema.resource_kind}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert schema.physical is False, f"kind: physical={schema.physical}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert schema.sandbox_strategy == "none", f"strategy: {schema.sandbox_strategy}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert schema.user_addable is False, f"addable: {schema.user_addable}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert schema.built_in is True, f"built_in: {schema.built_in}"
@@ -43,7 +43,7 @@ Load Virtual Core YAMLs As Domain Models
[Documentation] Load all virtual core YAMLs as ResourceTypeSpec domain models
${script}= Catenate SEPARATOR=\n
... import yaml
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec, ResourceKind
... from cleveragents.domain.models.core.resource_type import ResourceTypeSpec
... PATHS = {
... ${SPACE}${SPACE}${SPACE}${SPACE}"file": "${FILE_YAML}",
... ${SPACE}${SPACE}${SPACE}${SPACE}"directory": "${DIR_YAML}",
@@ -57,7 +57,7 @@ Load Virtual Core YAMLs As Domain Models
... ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}config = yaml.safe_load(f)
... ${SPACE}${SPACE}${SPACE}${SPACE}spec = ResourceTypeSpec.from_config(config)
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.name == name, f"name: {spec.name}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.resource_kind == ResourceKind.VIRTUAL
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.physical is False, f"kind: physical={spec.physical}"
... ${SPACE}${SPACE}${SPACE}${SPACE}cli_dict = spec.as_cli_dict()
... ${SPACE}${SPACE}${SPACE}${SPACE}assert "equivalence" in cli_dict, f"no equivalence in cli_dict for {name}"
... print("All 6 virtual core domain models validated successfully")
@@ -74,7 +74,7 @@ Verify Virtual Types In Bootstrap Registration
... for vt in expected:
... ${SPACE}${SPACE}${SPACE}${SPACE}assert vt in names, f"{vt} not in builtin types: {names}"
... ${SPACE}${SPACE}${SPACE}${SPACE}entry = next(d for d in BUILTIN_TYPES if d["name"] == vt)
... ${SPACE}${SPACE}${SPACE}${SPACE}assert entry["resource_kind"] == "virtual", f"{vt} kind: {entry['resource_kind']}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert entry["physical"] is False, f"{vt} kind: physical={entry.get('physical')}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert entry["user_addable"] is False, f"{vt} addable: {entry['user_addable']}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert entry["sandbox_strategy"] == "none", f"{vt} strategy: {entry['sandbox_strategy']}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert entry.get("equivalence") is not None, f"{vt} missing equivalence"
@@ -113,7 +113,7 @@ Virtual Types Appear In CLI Resource Type List
... for vt in expected:
... ${SPACE}${SPACE}${SPACE}${SPACE}assert vt in type_names, f"{vt} not in listed types: {type_names}"
... ${SPACE}${SPACE}${SPACE}${SPACE}spec = next(t for t in types if t.name == vt)
... ${SPACE}${SPACE}${SPACE}${SPACE}assert str(spec.resource_kind) == "virtual", f"{vt} kind: {spec.resource_kind}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.physical is False, f"{vt} kind: physical={spec.physical}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.user_addable is False, f"{vt} user_addable: {spec.user_addable}"
... ${SPACE}${SPACE}${SPACE}${SPACE}assert spec.equivalence is not None, f"{vt} missing equivalence"
... print(f"CLI list path: all 6 virtual types present and correctly typed")
@@ -5,11 +5,10 @@ placeholders. Extracted from ``_resource_registry_data.py``.
.. note::
This module is 754 lines, exceeding the 500-line CONTRIBUTING
guideline. The overage is accepted because splitting the 36 AWS
type definitions mid-file would break the ``_aws_type()`` helper
pattern. A follow-up extraction to ``_resource_registry_cloud_aws``
is tracked but not blocking.
This module exceeds the 500-line CONTRIBUTING guideline. The overage
is accepted because splitting the AWS type definitions mid-file would
break the ``_aws_type()`` helper pattern. A follow-up extraction to
``_resource_registry_cloud_aws`` is tracked but not blocking.
"""
from __future__ import annotations
@@ -47,7 +46,7 @@ def _cloud_base(
return {
"name": name,
"description": description,
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -74,7 +73,7 @@ def _aws_type(
d: dict[str, Any] = {
"name": name,
"description": description,
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": user_addable,
"built_in": True,
@@ -93,7 +92,7 @@ def _aws_type(
# Built-in resource type definitions (registered at startup)
# ---------------------------------------------------------------------------
# IMPORTANT: Order matters children must appear after their parents so
# IMPORTANT: Order matters -- children must appear after their parents so
# that ``bootstrap_builtin_types()`` can resolve ``inherits`` references.
# ===== Git / Filesystem / Container types (unchanged) =====
@@ -102,7 +101,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
{
"name": "git-checkout",
"description": "A local git checkout (cloned repository or worktree).",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "git_worktree",
"user_addable": True,
"built_in": True,
@@ -140,7 +139,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
{
"name": "fs-directory",
"description": "A local filesystem directory.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"user_addable": True,
"built_in": True,
@@ -182,7 +181,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
{
"name": "container-instance",
"description": "A container execution environment instance.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "snapshot",
"user_addable": True,
"built_in": True,
@@ -211,7 +210,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
"A devcontainer execution environment auto-discovered "
"from .devcontainer/ configuration."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "snapshot",
"user_addable": True,
"built_in": True,
@@ -246,7 +245,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
{
"name": "devcontainer-file",
"description": "A single devcontainer.json configuration file resource.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"user_addable": False,
"built_in": True,
@@ -268,7 +267,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
#
# Abstract base types that define common cloud concepts. Provider-
# specific types (``aws-*``, ``azure-*``, etc.) inherit from these.
# Not user-addable users create provider-specific account types.
# Not user-addable -- users create provider-specific account types.
CLOUD_BASE_TYPES: list[dict[str, Any]] = [
# -- Account & region structure --
@@ -311,6 +310,11 @@ CLOUD_BASE_TYPES: list[dict[str, Any]] = [
"cloud-block-storage",
"Abstract base: block storage volume.",
),
# -- Database --
_cloud_base(
"cloud-database",
"Abstract base: a managed relational or NoSQL database instance.",
),
# -- IAM --
_cloud_base(
"cloud-identity-principal",
@@ -427,6 +431,7 @@ AWS_TYPES: list[dict[str, Any]] = [
"aws-launch-template",
"aws-asg",
"aws-s3-bucket",
"aws-rds-instance",
"aws-ebs-volume",
"aws-efs-filesystem",
"aws-cloudwatch-log-group",
@@ -544,6 +549,63 @@ AWS_TYPES: list[dict[str, Any]] = [
"An AWS S3 object storage bucket.",
inherits="cloud-object-store",
parent_types=["aws-region"],
cli_args=[
{
"name": "bucket-name",
"type": "string",
"required": True,
"description": "S3 bucket name (globally unique).",
},
{
"name": "region",
"type": "string",
"required": False,
"description": "AWS region where the bucket is located.",
},
{
"name": "versioning",
"type": "boolean",
"required": False,
"description": "Whether S3 versioning is enabled.",
"default": None,
},
],
capabilities=_CLOUD_CAPS_RW,
),
_aws_type(
"aws-rds-instance",
"An AWS RDS managed relational database instance.",
inherits="cloud-database",
parent_types=["aws-region"],
cli_args=[
{
"name": "engine",
"type": "string",
"required": True,
"description": "DB engine (mysql, postgres, mariadb, oracle).",
},
{
"name": "instance-class",
"type": "string",
"required": True,
"description": "RDS instance class (e.g. db.t3.micro).",
},
{
"name": "db-name",
"type": "string",
"required": False,
"description": "Initial database name to create.",
"default": None,
},
{
"name": "port",
"type": "integer",
"required": False,
"description": "Database port (defaults to engine default).",
"default": None,
},
],
capabilities=_CLOUD_CAPS_RW,
),
_aws_type(
"aws-ebs-volume",
@@ -683,7 +745,7 @@ GCP_AZURE_TYPES: list[dict[str, Any]] = [
{
"name": "gcp",
"description": "Google Cloud Platform resource (hierarchy pending).",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": True,
"built_in": True,
@@ -716,7 +778,7 @@ GCP_AZURE_TYPES: list[dict[str, Any]] = [
{
"name": "azure",
"description": "Microsoft Azure cloud resource (hierarchy pending).",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": True,
"built_in": True,
@@ -22,7 +22,7 @@ from typing import Any
__all__ = ["CONTAINER_INFRA_TYPES"]
# Handler references per spec line 25096-25114. These handler classes
# do not exist yet the references are forward declarations that will
# do not exist yet -- the references are forward declarations that will
# be resolved when the container runtime integration is implemented.
_RUNTIME_HANDLER = "cleveragents.resource.handlers.container:ContainerRuntimeHandler"
_IMAGE_HANDLER = "cleveragents.resource.handlers.container:ContainerImageHandler"
@@ -39,7 +39,7 @@ CONTAINER_INFRA_TYPES: list[dict[str, Any]] = [
"description": (
"A container runtime engine (Docker, Podman, containerd, etc.)."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": True,
"built_in": True,
@@ -103,7 +103,7 @@ CONTAINER_INFRA_TYPES: list[dict[str, Any]] = [
{
"name": "container-image",
"description": ("A container image identified by tag and/or digest."),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": True,
"built_in": True,
@@ -149,7 +149,7 @@ CONTAINER_INFRA_TYPES: list[dict[str, Any]] = [
{
"name": "container-mount",
"description": ("A bind mount or volume mount inside a container instance."),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "snapshot",
"user_addable": False,
"built_in": True,
@@ -173,7 +173,7 @@ CONTAINER_INFRA_TYPES: list[dict[str, Any]] = [
"Execution environment variables and configuration "
"inside a container instance."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "snapshot",
"user_addable": False,
"built_in": True,
@@ -196,7 +196,7 @@ CONTAINER_INFRA_TYPES: list[dict[str, Any]] = [
"description": (
"A port mapping between host and container (e.g. 8080:80/tcp)."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "snapshot",
"user_addable": False,
"built_in": True,
@@ -217,7 +217,7 @@ CONTAINER_INFRA_TYPES: list[dict[str, Any]] = [
{
"name": "container-volume",
"description": ("A named container volume managed by the runtime engine."),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "snapshot",
"user_addable": True,
"built_in": True,
@@ -254,7 +254,7 @@ CONTAINER_INFRA_TYPES: list[dict[str, Any]] = [
"description": (
"A container network (bridge, host, overlay, macvlan, or none)."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -1,6 +1,6 @@
"""Internal data helpers for the Resource Registry Service.
Contains the built-in type definitions and DB domain conversion
Contains the built-in type definitions and DB <-> domain conversion
functions. Extracted from ``resource_registry_service.py`` to keep
individual modules under the 500-line CONTRIBUTING limit.
@@ -39,7 +39,6 @@ from cleveragents.application.services._resource_registry_virtual_deferred impor
)
from cleveragents.domain.models.core.resource import PhysVirt, Resource
from cleveragents.domain.models.core.resource_type import (
ResourceKind,
ResourceTypeSpec,
SandboxStrategy,
)
@@ -66,7 +65,7 @@ __all__ = [
# Built-in resource type definitions (registered at startup)
# ---------------------------------------------------------------------------
# IMPORTANT: Order matters children must appear after their parents so
# IMPORTANT: Order matters -- children must appear after their parents so
# that ``bootstrap_builtin_types()`` can resolve ``inherits`` references.
# ===== Git / Filesystem / Container types (unchanged) =====
@@ -75,7 +74,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
{
"name": "fs-mount",
"description": "A physical mount point on the local system.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"user_addable": True,
"built_in": True,
@@ -107,7 +106,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
{
"name": "git-checkout",
"description": "A local git checkout (cloned repository or worktree).",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "git_worktree",
"user_addable": True,
"built_in": True,
@@ -145,7 +144,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
{
"name": "fs-directory",
"description": "A local filesystem directory.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"user_addable": True,
"built_in": True,
@@ -187,7 +186,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
{
"name": "container-instance",
"description": "A container execution environment instance.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "snapshot",
"user_addable": True,
"built_in": True,
@@ -249,7 +248,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
"A devcontainer execution environment auto-discovered "
"from .devcontainer/ configuration."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "snapshot",
"user_addable": True,
"built_in": True,
@@ -284,7 +283,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
{
"name": "devcontainer-file",
"description": "A single devcontainer.json configuration file resource.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"user_addable": False,
"built_in": True,
@@ -306,7 +305,7 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [
#
# Abstract base types that define common cloud concepts. Provider-
# specific types (``aws-*``, ``azure-*``, etc.) inherit from these.
# Not user-addable users create provider-specific account types.
# Not user-addable -- users create provider-specific account types.
# ===== Assemble final list =====
@@ -332,7 +331,7 @@ BUILTIN_TYPES: list[dict[str, Any]] = [
# ---------------------------------------------------------------------------
# DB domain conversion helpers
# DB <-> domain conversion helpers
# ---------------------------------------------------------------------------
@@ -382,13 +381,15 @@ def spec_to_db(spec: ResourceTypeSpec, source: str) -> ResourceTypeModel:
if spec.equivalence is not None:
equivalence_json = json.dumps(spec.equivalence)
# Convert physical bool to resource_kind string for DB storage
# (DB column still uses the legacy string format for backward compat)
resource_kind_str = "physical" if spec.physical else "virtual"
return ResourceTypeModel(
name=spec.name,
namespace=namespace,
description=spec.description or None,
resource_kind=spec.resource_kind.value
if isinstance(spec.resource_kind, ResourceKind)
else spec.resource_kind,
resource_kind=resource_kind_str,
sandbox_strategy=spec.sandbox_strategy.value
if isinstance(spec.sandbox_strategy, SandboxStrategy)
else spec.sandbox_strategy,
@@ -459,6 +460,8 @@ def db_to_spec(row: ResourceTypeModel) -> ResourceTypeSpec:
raw_inherits = getattr(row, "inherits", None)
inherits_str: str | None = str(raw_inherits) if raw_inherits is not None else None
# Pass resource_kind from DB for backward compat -- from_config handles
# both "physical"/"virtual" string (legacy) and physical bool (new).
return ResourceTypeSpec.from_config(
{
"name": name_str,
@@ -34,14 +34,14 @@ __all__ = ["LSP_RESOURCE_TYPES"]
# - lsp-document: text document tracked by an LSP server
LSP_RESOURCE_TYPES: list[dict[str, Any]] = [
# ── executable ───────────────────────────────────────────────
# -- executable -----------------------------------------------
{
"name": "executable",
"description": (
"System executable (language runtime, compiler, LSP server "
"binary). Discovered from container or host PATH."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": True,
"built_in": True,
@@ -74,14 +74,14 @@ LSP_RESOURCE_TYPES: list[dict[str, Any]] = [
"checkpoint": False,
},
},
# ── lsp-server ───────────────────────────────────────────────
# -- lsp-server -----------------------------------------------
{
"name": "lsp-server",
"description": (
"LSP server definition and running process. Provides "
"semantic services for one or more languages."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": True,
"built_in": True,
@@ -141,14 +141,14 @@ LSP_RESOURCE_TYPES: list[dict[str, Any]] = [
"checkpoint": False,
},
},
# ── lsp-workspace ────────────────────────────────────────────
# -- lsp-workspace --------------------------------------------
{
"name": "lsp-workspace",
"description": (
"Workspace root tracked by an LSP server. Corresponds to "
"a workspaceFolder in the LSP specification."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -167,14 +167,14 @@ LSP_RESOURCE_TYPES: list[dict[str, Any]] = [
"checkpoint": False,
},
},
# ── lsp-document ─────────────────────────────────────────────
# -- lsp-document ---------------------------------------------
{
"name": "lsp-document",
"description": (
"Text document tracked by an LSP server. Provides semantic "
"access: types, diagnostics, completions, references, symbols."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -30,7 +30,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
"description": (
"A git repository \u2014 the object database, refs, and full history."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": True,
"built_in": True,
@@ -84,7 +84,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
"A remote URL configured on a git repo "
"(e.g., origin \u2192 https://github.com/org/repo)."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -102,7 +102,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
{
"name": "git-branch",
"description": "A named branch ref (e.g., main, feature/auth).",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -126,12 +126,12 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
},
{
# NOTE: git-tag has no auto_discovery (unlike git-branch) because
# tags are passive refs they point to a commit but don't "own"
# children that need discovery. The tagcommit relationship is
# tags are passive refs -- they point to a commit but don't "own"
# children that need discovery. The tag->commit relationship is
# expressed via child_types only.
"name": "git-tag",
"description": "A tag ref \u2014 lightweight or annotated (e.g., v1.0.0).",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -140,7 +140,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
# Spec S23518 omits git-commit as child of git-tag, but annotated tags
# target commits directly. Added for DAG consistency with git-commit
# listing git-tag as a parent.
# NOTE: This is NOT a cycle git-tag is a parent of git-commit
# NOTE: This is NOT a cycle -- git-tag is a parent of git-commit
# (tag -> commit), and git-commit lists git-tag as parent (commit
# can be reached FROM a tag). The DAG remains acyclic.
"child_types": ["git-commit"],
@@ -155,7 +155,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
{
"name": "git-commit",
"description": "A specific commit object.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -182,7 +182,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
{
"name": "git-tree",
"description": "A tree object \u2014 a directory listing at a specific commit.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -215,7 +215,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
"A blob entry in a tree \u2014 a specific file's content "
"at a specific path and mode."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -233,7 +233,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
{
"name": "git-stash",
"description": "A stash entry (e.g., stash@{0}).",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -254,7 +254,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
"A submodule reference \u2014 a pointer to another git "
"repository at a specific commit and path."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -272,7 +272,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
{
"name": "fs-symlink",
"description": "A symbolic link on the local filesystem.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"user_addable": False,
"built_in": True,
@@ -292,7 +292,7 @@ DEFERRED_PHYSICAL_TYPES: list[dict[str, Any]] = [
"description": (
"A hard link on the local filesystem \u2014 a file with link count > 1."
),
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "copy_on_write",
"user_addable": False,
"built_in": True,
@@ -38,7 +38,7 @@ VIRTUAL_CORE_TYPES: list[dict[str, Any]] = [
"Cross-layer file identity linking equivalent physical files "
"by content hash, filename, and permissions."
),
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -66,7 +66,7 @@ VIRTUAL_CORE_TYPES: list[dict[str, Any]] = [
"Cross-layer directory identity linking equivalent physical "
"directories by recursive content."
),
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -98,7 +98,7 @@ VIRTUAL_CORE_TYPES: list[dict[str, Any]] = [
"Cross-repo commit identity linking commits with "
"the same hash across repositories."
),
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -126,7 +126,7 @@ VIRTUAL_CORE_TYPES: list[dict[str, Any]] = [
"Cross-repo branch identity linking branches with "
"the same name and HEAD commit hash."
),
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -153,7 +153,7 @@ VIRTUAL_CORE_TYPES: list[dict[str, Any]] = [
"description": (
"Cross-repo tag identity linking tags with the same name and target object."
),
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -180,7 +180,7 @@ VIRTUAL_CORE_TYPES: list[dict[str, Any]] = [
"description": (
"Cross-repo/cross-commit tree identity linking trees with the same hash."
),
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -33,7 +33,7 @@ VIRTUAL_DEFERRED_TYPES: list[dict[str, Any]] = [
"Cross-repo remote identity. Links git-remote resources "
"across different repos that point to the same URL."
),
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -61,7 +61,7 @@ VIRTUAL_DEFERRED_TYPES: list[dict[str, Any]] = [
"Cross-repo submodule identity. Links git-submodule resources "
"across different repos with the same submodule URL and path."
),
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
@@ -90,7 +90,7 @@ VIRTUAL_DEFERRED_TYPES: list[dict[str, Any]] = [
"git-tree-entry (mode 120000) resources with the same "
"name and target."
),
"resource_kind": "virtual",
"physical": False,
"sandbox_strategy": "none",
"user_addable": False,
"built_in": True,
+3 -3
View File
@@ -133,7 +133,7 @@ def _resource_type_dict(spec: Any) -> dict[str, object]:
result: dict[str, object] = {
"name": spec.name,
"description": spec.description or "",
"resource_kind": str(spec.resource_kind),
"physical": spec.physical,
"sandbox_strategy": str(spec.sandbox_strategy),
"user_addable": spec.user_addable,
"built_in": spec.built_in,
@@ -384,7 +384,7 @@ def type_list(
inherits = getattr(spec, "inherits", None) or ""
table.add_row(
spec.name,
str(spec.resource_kind),
"physical" if spec.physical else "virtual",
str(spec.sandbox_strategy),
inherits,
"yes" if spec.built_in else "no",
@@ -479,7 +479,7 @@ def _print_type_panel(spec: Any) -> None:
f"[bold]Description:[/bold] {spec.description or '(none)'}\n"
f"[bold]Inherits:[/bold] {inherits_display}\n"
f"[bold]Inheritance Chain:[/bold] {chain_display}\n"
f"[bold]Kind:[/bold] {spec.resource_kind}\n"
f"[bold]Kind:[/bold] {'physical' if spec.physical else 'virtual'}\n"
f"[bold]Sandbox Strategy:[/bold] {spec.sandbox_strategy}\n"
f"[bold]Built-in:[/bold] {'yes' if spec.built_in else 'no'}\n"
f"[bold]User-addable:[/bold] {'yes' if spec.user_addable else 'no'}\n"
@@ -44,7 +44,7 @@ from cleveragents.domain.models.core._resource_type_validation import (
#: CLI-safe argument name: lowercase alphanumeric, hyphens, underscores.
_CLI_ARG_NAME_RE = re.compile(r"^[a-z][a-z0-9_-]*$")
#: Namespaced name: ``namespace/name`` both components must start with a letter.
#: Namespaced name: ``namespace/name`` -- both components must start with a letter.
_NAMESPACED_RE = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]*/[a-zA-Z][a-zA-Z0-9_-]*$")
#: Built-in (unnamespaced) name: simple alphanumeric + hyphens/underscores.
@@ -62,6 +62,11 @@ _VALID_ARG_TYPES = frozenset({"string", "integer", "float", "boolean", "path"})
class ResourceKind(StrEnum):
"""Classification of a resource type as physical or virtual.
.. deprecated::
Use the ``physical: bool`` field on :class:`ResourceTypeSpec` instead.
``ResourceKind`` is retained for backward compatibility with legacy
configuration files that still use ``resource_kind``.
Physical resources represent tangible assets (files, directories, repos).
Virtual resources represent derived/computed assets (branches, tree entries).
"""
@@ -174,7 +179,7 @@ class ResourceTypeSpec(BaseModel):
# Built-in resource type names (unnamespaced).
# The actual frozenset lives in _resource_type_validation to keep
# this module under the 500-line CONTRIBUTING limit.
# Built-in resource type names sourced from _resource_type_validation.py
# Built-in resource type names -- sourced from _resource_type_validation.py
# to keep this module under the 500-line CONTRIBUTING limit.
BUILTIN_NAMES: ClassVar[frozenset[str]] = BUILTIN_TYPE_NAMES
@@ -187,9 +192,13 @@ class ResourceTypeSpec(BaseModel):
"",
description="Human-readable description of this resource type.",
)
resource_kind: ResourceKind = Field(
physical: bool = Field(
...,
description="Physical or virtual classification.",
description=(
"true for physical types (concrete manifestations), "
"false for virtual types (abstract identity linking equivalent "
"physical resources). Replaces the legacy resource_kind enum field."
),
)
sandbox_strategy: SandboxStrategy = Field(
...,
@@ -319,7 +328,7 @@ class ResourceTypeSpec(BaseModel):
)
# Virtual type constraints (equivalence, no sandbox, etc.)
if self.resource_kind == ResourceKind.VIRTUAL:
if not self.physical:
_validate_virtual_type(
self.name,
self.equivalence,
@@ -355,6 +364,9 @@ class ResourceTypeSpec(BaseModel):
def from_config(cls, config: dict[str, Any]) -> ResourceTypeSpec:
"""Create a ResourceTypeSpec from a YAML configuration dict.
Supports both the new ``physical`` boolean field (per spec) and the
legacy ``resource_kind`` string enum for backward compatibility.
Args:
config: Parsed YAML configuration with resource type fields.
@@ -366,11 +378,29 @@ class ResourceTypeSpec(BaseModel):
"""
if "name" not in config:
raise ValueError("Resource type config must include 'name'.")
if "resource_kind" not in config:
raise ValueError("Resource type config must include 'resource_kind'.")
if "sandbox_strategy" not in config:
raise ValueError("Resource type config must include 'sandbox_strategy'.")
# Resolve physical boolean from either new or legacy field
if "physical" in config:
physical = bool(config["physical"])
elif "resource_kind" in config:
# Legacy backward compatibility: convert resource_kind enum to bool
rk = config["resource_kind"]
if rk == "physical":
physical = True
elif rk == "virtual":
physical = False
else:
raise ValueError(
f"Invalid resource_kind '{rk}'. Must be 'physical' or 'virtual'."
)
else:
raise ValueError(
"Resource type config must include 'physical' (boolean) field. "
"Legacy 'resource_kind' is also accepted for backward compat."
)
# Parse CLI arguments from list of mappings
cli_args: list[ResourceTypeArgument] = []
for arg_data in config.get("cli_args", []):
@@ -384,7 +414,7 @@ class ResourceTypeSpec(BaseModel):
return cls(
name=config["name"],
description=config.get("description", ""),
resource_kind=ResourceKind(config["resource_kind"]),
physical=physical,
sandbox_strategy=SandboxStrategy(config["sandbox_strategy"]),
user_addable=config.get("user_addable", True),
cli_args=cli_args,
@@ -401,7 +431,7 @@ class ResourceTypeSpec(BaseModel):
"sandbox": False,
"checkpoint": False,
}
if config.get("resource_kind") == "virtual"
if not physical
else {
"read": True,
"write": True,
@@ -422,7 +452,7 @@ class ResourceTypeSpec(BaseModel):
"""
result: dict[str, Any] = {
"name": self.name,
"resource_kind": self.resource_kind.value,
"physical": self.physical,
"sandbox_strategy": self.sandbox_strategy.value,
}
@@ -454,7 +484,7 @@ class ResourceTypeSpec(BaseModel):
result["capabilities"] = dict(self.capabilities)
if self.auto_discovery is not None:
# Deep copy two schemas coexist: rules-based (list[dict])
# Deep copy -- two schemas coexist: rules-based (list[dict])
# and trigger-based (list[str]). copy.deepcopy handles both.
import copy
@@ -1817,9 +1817,7 @@ class ResourceTypeRepository:
if "/" in resource_type.name
else "builtin",
description=resource_type.description or None,
resource_kind=resource_type.resource_kind.value
if hasattr(resource_type.resource_kind, "value")
else resource_type.resource_kind,
resource_kind="physical" if resource_type.physical else "virtual",
sandbox_strategy=resource_type.sandbox_strategy.value
if hasattr(resource_type.sandbox_strategy, "value")
else resource_type.sandbox_strategy,
@@ -1939,11 +1937,7 @@ class ResourceTypeRepository:
raise ResourceTypeNotFoundError(resource_type.name)
row.description = resource_type.description or None # type: ignore[assignment]
row.resource_kind = ( # type: ignore[assignment]
resource_type.resource_kind.value
if hasattr(resource_type.resource_kind, "value")
else resource_type.resource_kind
)
row.resource_kind = "physical" if resource_type.physical else "virtual" # type: ignore[assignment]
row.sandbox_strategy = ( # type: ignore[assignment]
resource_type.sandbox_strategy.value
if hasattr(resource_type.sandbox_strategy, "value")
@@ -2048,7 +2042,6 @@ class ResourceTypeRepository:
def _to_domain(row: ResourceTypeModel) -> Any:
"""Convert a ``ResourceTypeModel`` row to a ``ResourceTypeSpec``."""
from cleveragents.domain.models.core.resource_type import (
ResourceKind,
ResourceTypeArgument,
ResourceTypeSpec,
SandboxStrategy,
@@ -2096,7 +2089,7 @@ class ResourceTypeRepository:
return ResourceTypeSpec(
name=name_str,
description=cast(str, row.description or ""),
resource_kind=ResourceKind(cast(str, row.resource_kind)),
physical=cast(str, row.resource_kind) == "physical",
sandbox_strategy=SandboxStrategy(cast(str, row.sandbox_strategy)),
user_addable=cast(bool, row.user_addable),
cli_args=cli_args,
@@ -63,7 +63,7 @@ logger = logging.getLogger(__name__)
POSTGRES_TYPE_DEF: dict[str, Any] = {
"name": "postgres",
"description": "PostgreSQL database connection.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "transaction_rollback",
"user_addable": True,
"built_in": True,
@@ -134,7 +134,7 @@ POSTGRES_TYPE_DEF: dict[str, Any] = {
MYSQL_TYPE_DEF: dict[str, Any] = {
"name": "mysql",
"description": "MySQL database connection.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "transaction_rollback",
"user_addable": True,
"built_in": True,
@@ -205,7 +205,7 @@ MYSQL_TYPE_DEF: dict[str, Any] = {
SQLITE_TYPE_DEF: dict[str, Any] = {
"name": "sqlite",
"description": "SQLite database file.",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "transaction_rollback",
"user_addable": True,
"built_in": True,
@@ -233,7 +233,7 @@ SQLITE_TYPE_DEF: dict[str, Any] = {
DUCKDB_TYPE_DEF: dict[str, Any] = {
"name": "duckdb",
"description": "DuckDB database (file-based or in-memory).",
"resource_kind": "physical",
"physical": True,
"sandbox_strategy": "transaction_rollback",
"user_addable": True,
"built_in": True,
+38 -22
View File
@@ -10,8 +10,8 @@ Provides :class:`ResourceTypeConfigSchema`, a Pydantic model that:
Schema definition lives in ``docs/schema/resource_type.schema.yaml``.
Based on:
- docs/specification.md Resource Types, Resource Registry
- implementation_plan.md Task B0.type-model
- docs/specification.md -- Resource Types, Resource Registry
- implementation_plan.md -- Task B0.type-model
"""
from __future__ import annotations
@@ -43,9 +43,6 @@ _ENV_VAR_RE = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
#: Supported schema versions.
_SUPPORTED_VERSIONS = frozenset({"1", "1.0"})
#: Valid resource kinds.
_VALID_KINDS = frozenset({"physical", "virtual"})
#: Valid sandbox strategies.
_VALID_STRATEGIES = frozenset(
{
@@ -147,8 +144,8 @@ class ResourceTypeConfigSchema(BaseModel):
Create instances via the factory class methods:
- :meth:`from_yaml` parse a YAML string
- :meth:`from_yaml_file` parse a YAML file from disk
- :meth:`from_yaml` -- parse a YAML string
- :meth:`from_yaml_file` -- parse a YAML file from disk
"""
schema_version: str = Field(
@@ -163,9 +160,20 @@ class ResourceTypeConfigSchema(BaseModel):
"",
description="Short description of the resource type.",
)
resource_kind: str = Field(
...,
description="Classification: physical | virtual.",
physical: bool | None = Field(
default=None,
description=(
"true for physical types (concrete manifestations), "
"false for virtual types (abstract identity linking equivalent "
"physical resources). Per spec JSON Schema line 34580."
),
)
resource_kind: str | None = Field(
default=None,
description=(
"Deprecated: use physical boolean instead. "
"Classification: physical | virtual."
),
)
sandbox_strategy: str = Field(
...,
@@ -281,17 +289,6 @@ class ResourceTypeConfigSchema(BaseModel):
)
return v
@field_validator("resource_kind")
@classmethod
def validate_resource_kind(cls, v: str) -> str:
"""Validate resource kind is physical or virtual."""
v_lower = v.lower()
if v_lower not in _VALID_KINDS:
raise ValueError(
f"Invalid resource_kind '{v}'. Must be 'physical' or 'virtual'."
)
return v_lower
@field_validator("sandbox_strategy")
@classmethod
def validate_sandbox_strategy(cls, v: str) -> str:
@@ -311,6 +308,25 @@ class ResourceTypeConfigSchema(BaseModel):
@model_validator(mode="after")
def validate_cross_fields(self) -> ResourceTypeConfigSchema:
"""Cross-field validation for resource type config."""
# Resolve physical from resource_kind for backward compatibility
if self.physical is None:
if self.resource_kind is not None:
rk = self.resource_kind.lower()
if rk == "physical":
object.__setattr__(self, "physical", True)
elif rk == "virtual":
object.__setattr__(self, "physical", False)
else:
raise ValueError(
f"Invalid resource_kind '{self.resource_kind}'. "
"Must be 'physical' or 'virtual'."
)
else:
raise ValueError(
"Resource type config must include 'physical' (boolean) "
"or legacy 'resource_kind' field."
)
# Custom types must be namespaced
if not self.built_in and "/" not in self.name:
raise ValueError(
@@ -320,7 +336,7 @@ class ResourceTypeConfigSchema(BaseModel):
)
# Virtual types require equivalence
if self.resource_kind == "virtual" and self.equivalence is None:
if not self.physical and self.equivalence is None:
raise ValueError(
f"Virtual resource type '{self.name}' requires an "
"'equivalence' configuration."