feat(resource): implement 6-level execution environment precedence chain
CI / build (pull_request) Successful in 16s
CI / helm (pull_request) Successful in 21s
CI / lint (pull_request) Successful in 3m18s
CI / quality (pull_request) Successful in 3m47s
CI / typecheck (pull_request) Successful in 3m57s
CI / security (pull_request) Successful in 4m6s
CI / benchmark-publish (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 7m6s
CI / unit_tests (pull_request) Successful in 7m18s
CI / docker (pull_request) Successful in 1m19s
CI / coverage (pull_request) Successful in 11m49s
CI / e2e_tests (pull_request) Successful in 20m44s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-regression (pull_request) Successful in 55m9s
CI / build (pull_request) Successful in 16s
CI / helm (pull_request) Successful in 21s
CI / lint (pull_request) Successful in 3m18s
CI / quality (pull_request) Successful in 3m47s
CI / typecheck (pull_request) Successful in 3m57s
CI / security (pull_request) Successful in 4m6s
CI / benchmark-publish (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 7m6s
CI / unit_tests (pull_request) Successful in 7m18s
CI / docker (pull_request) Successful in 1m19s
CI / coverage (pull_request) Successful in 11m49s
CI / e2e_tests (pull_request) Successful in 20m44s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-regression (pull_request) Successful in 55m9s
Implement the spec's 6-level execution environment precedence chain (spec lines 19324-19386): 1. Plan override (priority=override) — always wins 2. Project override (priority=override) — wins over devcontainer 3. Nearest-ancestor devcontainer — auto-discovered 4. Plan fallback (priority=fallback) — defers to devcontainer 5. Project fallback (priority=fallback) — defers to closer scopes 6. Host default — final fallback - New resolve_with_precedence() API on ExecutionEnvironmentResolver - Added execution_env_priority field to ContextConfig (project model) - has_devcontainer() helper for devcontainer-instance detection - Legacy 4-level resolve() preserved for backward compatibility - _parse_priority() defaults missing priority to FALLBACK - 13 new Behave scenarios testing all 6 levels + edge cases - Updated CHANGELOG ISSUES CLOSED: #877
This commit is contained in:
@@ -383,3 +383,155 @@ def step_error_contains(context: Context, text: str) -> None:
|
||||
@then('exec-env the error message should not contain "{text}"')
|
||||
def step_error_not_contains(context: Context, text: str) -> None:
|
||||
assert text not in str(context.container_error)
|
||||
|
||||
|
||||
# ── 6-Level Precedence Chain Steps (#877) ────────────────
|
||||
|
||||
|
||||
def _resolver():
|
||||
from cleveragents.application.services.execution_environment_resolver import (
|
||||
ExecutionEnvironmentResolver,
|
||||
)
|
||||
|
||||
return ExecutionEnvironmentResolver()
|
||||
|
||||
|
||||
@when(
|
||||
'I resolve with plan_env "{plan}" plan_priority "{pp}" '
|
||||
'project_env "{proj}" project_priority "{projp}" '
|
||||
"devcontainer {dc}"
|
||||
)
|
||||
def step_resolve_6level(
|
||||
context: Context, plan: str, pp: str, proj: str, projp: str, dc: str
|
||||
) -> None:
|
||||
context.resolved_env = _resolver().resolve_with_precedence(
|
||||
plan_env=plan,
|
||||
plan_priority=pp,
|
||||
project_env=proj,
|
||||
project_priority=projp,
|
||||
devcontainer_available=dc.lower() == "true",
|
||||
)
|
||||
|
||||
|
||||
@when(
|
||||
'I resolve with tool_env "{tool}" plan_env "{plan}" '
|
||||
'plan_priority "{pp}" devcontainer {dc}'
|
||||
)
|
||||
def step_resolve_tool_wins(
|
||||
context: Context, tool: str, plan: str, pp: str, dc: str
|
||||
) -> None:
|
||||
context.resolved_env = _resolver().resolve_with_precedence(
|
||||
tool_env=tool,
|
||||
plan_env=plan,
|
||||
plan_priority=pp,
|
||||
devcontainer_available=dc.lower() == "true",
|
||||
)
|
||||
|
||||
|
||||
@when(
|
||||
'I resolve with project_env "{proj}" project_priority "{projp}" devcontainer {dc}'
|
||||
)
|
||||
def step_resolve_project_prio(context: Context, proj: str, projp: str, dc: str) -> None:
|
||||
context.resolved_env = _resolver().resolve_with_precedence(
|
||||
project_env=proj,
|
||||
project_priority=projp,
|
||||
devcontainer_available=dc.lower() == "true",
|
||||
)
|
||||
|
||||
|
||||
@when("I resolve with devcontainer {dc}")
|
||||
def step_resolve_dc_only(context: Context, dc: str) -> None:
|
||||
context.resolved_env = _resolver().resolve_with_precedence(
|
||||
devcontainer_available=dc.lower() == "true",
|
||||
)
|
||||
|
||||
|
||||
@when('I resolve with plan_env "{plan}" project_env "{proj}" devcontainer {dc}')
|
||||
def step_resolve_both_fallback(context: Context, plan: str, proj: str, dc: str) -> None:
|
||||
context.resolved_env = _resolver().resolve_with_precedence(
|
||||
plan_env=plan,
|
||||
project_env=proj,
|
||||
devcontainer_available=dc.lower() == "true",
|
||||
)
|
||||
|
||||
|
||||
@when('I check has_devcontainer with types "{types}"')
|
||||
def step_has_devcontainer(context: Context, types: str) -> None:
|
||||
type_list = [t.strip() for t in types.split(",")]
|
||||
context.has_dc_result = _resolver().has_devcontainer(type_list)
|
||||
|
||||
|
||||
@then("exec-env has_devcontainer should be {expected}")
|
||||
def step_has_dc_assert(context: Context, expected: str) -> None:
|
||||
assert context.has_dc_result == (expected.lower() == "true")
|
||||
|
||||
|
||||
@when('I create a ContextConfig with execution_env_priority "{priority}"')
|
||||
def step_context_config_priority(context: Context, priority: str) -> None:
|
||||
from cleveragents.domain.models.core.project import ContextConfig
|
||||
|
||||
config = ContextConfig(
|
||||
execution_environment="container",
|
||||
execution_env_priority=priority,
|
||||
)
|
||||
context.ctx_config = config
|
||||
|
||||
|
||||
@then('exec-env the context config priority should be "{priority}"')
|
||||
def step_ctx_config_priority_check(context: Context, priority: str) -> None:
|
||||
assert context.ctx_config.execution_env_priority == priority
|
||||
|
||||
|
||||
# ── DAG walk and container ref steps (#877 subtasks 2-4) ──
|
||||
|
||||
|
||||
@when('I resolve with DAG walk from "{resource_id}" with devcontainer ancestor')
|
||||
def step_dag_walk_with_dc(context: Context, resource_id: str) -> None:
|
||||
resolver = _resolver()
|
||||
# Simulate a DAG: res-file -> res-dir -> res-dc (devcontainer-instance)
|
||||
linked_types = {
|
||||
resource_id: "fs-file",
|
||||
"res-dir": "fs-directory",
|
||||
"res-dc": "devcontainer-instance",
|
||||
}
|
||||
parent_map = {
|
||||
resource_id: ["res-dir"],
|
||||
"res-dir": ["res-dc"],
|
||||
}
|
||||
context.resolved_env = resolver.resolve_with_dag(
|
||||
resource_id=resource_id,
|
||||
linked_resource_types=linked_types,
|
||||
parent_map=parent_map,
|
||||
)
|
||||
|
||||
|
||||
@when('I resolve with DAG walk from "{resource_id}" without devcontainer ancestor')
|
||||
def step_dag_walk_no_dc(context: Context, resource_id: str) -> None:
|
||||
resolver = _resolver()
|
||||
linked_types = {
|
||||
resource_id: "fs-file",
|
||||
"res-dir": "fs-directory",
|
||||
"res-checkout": "git-checkout",
|
||||
}
|
||||
parent_map = {
|
||||
resource_id: ["res-dir"],
|
||||
"res-dir": ["res-checkout"],
|
||||
}
|
||||
context.resolved_env = resolver.resolve_with_dag(
|
||||
resource_id=resource_id,
|
||||
linked_resource_types=linked_types,
|
||||
parent_map=parent_map,
|
||||
)
|
||||
|
||||
|
||||
@when('I parse container ref "{ref}"')
|
||||
def step_parse_container_ref(context: Context, ref: str) -> None:
|
||||
resolver = _resolver()
|
||||
env, name = resolver.parse_container_ref(ref)
|
||||
context.resolved_env = env
|
||||
context.container_ref_name = name
|
||||
|
||||
|
||||
@then('exec-env the container ref name should be "{name}"')
|
||||
def step_container_ref_name(context: Context, name: str) -> None:
|
||||
assert context.container_ref_name == name
|
||||
|
||||
Reference in New Issue
Block a user