Files
cleveragents-core/features/semantic_validation.feature
T
CoreRasurae a2be3e67b0
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 13s
CI / build (pull_request) Successful in 17s
CI / quality (pull_request) Successful in 18s
CI / security (pull_request) Successful in 30s
CI / typecheck (pull_request) Successful in 59s
CI / integration_tests (pull_request) Successful in 4m40s
CI / unit_tests (pull_request) Successful in 11m32s
CI / docker (pull_request) Successful in 1m1s
CI / benchmark-regression (pull_request) Successful in 28m54s
CI / coverage (pull_request) Successful in 44m11s
CI / lint (push) Successful in 12s
CI / build (push) Successful in 14s
CI / quality (push) Successful in 16s
CI / typecheck (push) Successful in 31s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 42s
CI / integration_tests (push) Successful in 2m53s
CI / unit_tests (push) Successful in 11m20s
CI / docker (push) Successful in 9s
CI / benchmark-publish (push) Successful in 12m18s
CI / coverage (push) Successful in 43m33s
feat(validation): add semantic validation service
Added SemanticValidationService with built-in Python checks for syntax
errors, missing imports, broken references, dependency cycles, API misuse,
and missing symbols. Implemented SemanticRuleRegistry for extensible
validator registration. Integrated with ValidationPipeline as informational
by default. Added per-project/per-plan config keys, severity mapping
(info/warn/error), output schema normalization, and file-hash-based caching.

Includes Behave BDD scenarios, Robot integration tests, ASV benchmarks,
and reference documentation.

ISSUES CLOSED: #207
2026-02-28 17:46:54 +00:00

468 lines
21 KiB
Gherkin

Feature: Semantic validation service
As a plan executor
I want semantic validation checks for Python source code
So that syntax errors, missing imports, and broken references are caught early
Background:
Given a semantic validation test environment
# ── Severity enum ──────────────────────────────────────────────
Scenario: SemanticValidationSeverity has three levels
Then the semantic severity enum has values "info", "warn", "error"
# ── SemanticCheckResult model ──────────────────────────────────
Scenario: SemanticCheckResult normalised output has passed message data
Given a semantic check result with passed true and message "All good"
Then the semantic check result passed is true
And the semantic check result message is "All good"
And the semantic check result data is none
Scenario: SemanticCheckResult with failure and data
Given a semantic check result with passed false and message "Error found" and data
Then the semantic check result passed is false
And the semantic check result has data key "detail"
# ── SyntaxCheckRule ────────────────────────────────────────────
Scenario: SyntaxCheckRule passes for valid Python
Given valid Python source code
When the syntax check rule runs
Then the semantic check result passed is true
Scenario: SyntaxCheckRule fails for invalid Python
Given Python source code with a syntax error
When the syntax check rule runs
Then the semantic check result passed is false
And the semantic check result message contains "Syntax error"
And the semantic check result severity is "error"
# ── MissingImportRule ──────────────────────────────────────────
Scenario: MissingImportRule passes for valid imports
Given Python source with standard imports
When the missing import rule runs
Then the semantic check result passed is true
Scenario: MissingImportRule flags suspicious private imports
Given Python source with suspicious private imports
When the missing import rule runs
Then the semantic check result passed is false
And the semantic check result message contains "suspicious import"
Scenario: MissingImportRule skips on syntax error
Given Python source code with a syntax error
When the missing import rule runs
Then the semantic check result passed is true
And the semantic check result message contains "Skipped"
# ── BrokenReferenceRule ────────────────────────────────────────
Scenario: BrokenReferenceRule passes for well-defined code
Given Python source with all names defined
When the broken reference rule runs
Then the semantic check result passed is true
Scenario: BrokenReferenceRule flags undefined names
Given Python source with undefined references
When the broken reference rule runs
Then the semantic check result passed is false
And the semantic check result message contains "broken reference"
# ── DependencyCycleRule ────────────────────────────────────────
Scenario: DependencyCycleRule passes for no duplicate relative imports
Given Python source with unique relative imports
When the dependency cycle rule runs
Then the semantic check result passed is true
Scenario: DependencyCycleRule flags duplicate relative imports
Given Python source with duplicate relative imports
When the dependency cycle rule runs
Then the semantic check result passed is false
And the semantic check result message contains "Duplicate relative imports"
# ── APIMisuseRule ──────────────────────────────────────────────
Scenario: APIMisuseRule passes for clean code
Given Python source without API misuse
When the API misuse rule runs
Then the semantic check result passed is true
Scenario: APIMisuseRule flags eval usage
Given Python source using eval
When the API misuse rule runs
Then the semantic check result passed is false
And the semantic check result message contains "API misuse"
# ── MissingSymbolRule ──────────────────────────────────────────
Scenario: MissingSymbolRule passes for well-scoped functions
Given Python source with well-scoped functions
When the missing symbol rule runs
Then the semantic check result passed is true
Scenario: MissingSymbolRule flags undefined symbols in functions
Given Python source with undefined symbols in functions
When the missing symbol rule runs
Then the semantic check result passed is false
And the semantic check result message contains "missing symbol"
# ── SemanticRuleRegistry ───────────────────────────────────────
Scenario: Default registry has all six built-in rules
Given a default semantic rule registry
Then the registry has 6 rules
And the registry contains rule "syntax_error"
And the registry contains rule "missing_import"
And the registry contains rule "broken_reference"
And the registry contains rule "duplicate_import"
And the registry contains rule "api_misuse"
And the registry contains rule "missing_symbol"
Scenario: Registry lookup returns None for unknown rule
Given a default semantic rule registry
Then the registry returns None for rule "nonexistent"
Scenario: Registry remove returns true for existing rule
Given a default semantic rule registry
When the rule "syntax_error" is removed from the registry
Then the registry has 5 rules
And the registry returns None for rule "syntax_error"
Scenario: Registry remove returns false for unknown rule
Given a default semantic rule registry
Then removing rule "nonexistent" returns false
# ── SemanticValidationCache ────────────────────────────────────
Scenario: Cache returns None for uncached entries
Given a semantic validation cache
Then the cache returns None for rule "check" and hash "abc123"
Scenario: Cache stores and retrieves results by rule and hash
Given a semantic validation cache
And a cached semantic result for rule "check" and hash "abc123"
Then the cache returns the result for rule "check" and hash "abc123"
Scenario: Cache invalidation removes entries
Given a semantic validation cache
And a cached semantic result for rule "check" and hash "abc123"
When the cache entry for rule "check" and hash "abc123" is invalidated
Then the cache returns None for rule "check" and hash "abc123"
Scenario: Cache clear removes all entries
Given a semantic validation cache
And a cached semantic result for rule "a" and hash "h1"
And a cached semantic result for rule "b" and hash "h2"
When the semantic cache is cleared
Then the semantic cache has 0 entries
Scenario: Cache compute_hash returns consistent SHA-256
Then the semantic cache hash for "hello" is consistent
# ── Severity mapping ───────────────────────────────────────────
Scenario: ERROR severity maps to REQUIRED mode
Then semantic severity "error" maps to validation mode "required"
Scenario: WARN severity maps to INFORMATIONAL mode
Then semantic severity "warn" maps to validation mode "informational"
Scenario: INFO severity maps to INFORMATIONAL mode
Then semantic severity "info" maps to validation mode "informational"
Scenario: resolve_severity uses default mapping
Then resolve_severity for "syntax_error" returns "error"
And resolve_severity for "missing_import" returns "warn"
And resolve_severity for "duplicate_import" returns "error"
And resolve_severity for "missing_symbol" returns "info"
Scenario: resolve_severity uses custom mapping
Given a custom severity mapping with "syntax_error" as "warn"
Then resolve_severity with custom mapping for "syntax_error" returns "warn"
Scenario: resolve_severity falls back to info for unknown rule
Then resolve_severity for "unknown_rule" returns "info"
# ── SemanticValidationService ──────────────────────────────────
Scenario: Service check_file runs all rules on valid Python
Given a semantic validation service
And valid Python source code
When the service checks the file
Then all semantic check results passed
Scenario: Service check_file detects syntax error
Given a semantic validation service
And Python source code with a syntax error
When the service checks the file
Then at least one semantic check result failed
And a failing result has message containing "Syntax error"
Scenario: Service check_file with specific rules
Given a semantic validation service
And valid Python source code
When the service checks the file with rules "syntax_error"
Then only 1 semantic check result is returned
Scenario: Service returns empty when disabled
Given a semantic validation service with semantic disabled
And valid Python source code
When the service checks the file
Then 0 semantic check results are returned
Scenario: Service returns empty for Python when python disabled
Given a semantic validation service with python disabled
And valid Python source code
When the service checks the python file
Then 0 semantic check results are returned
Scenario: Service uses cache for repeated checks
Given a semantic validation service
And valid Python source code
When the service checks the file twice
Then the cache has entries
# ── Pipeline integration ───────────────────────────────────────
Scenario: as_pipeline_results returns normalised dicts
Given a semantic validation service
And valid Python source code
When the service returns pipeline results
Then each pipeline result has keys "passed" and "message" and "data"
And each pipeline result has key "severity"
And each pipeline result has key "mode"
Scenario: normalise_output returns standard schema
Given a semantic check result with passed true and message "OK"
When the result is normalised
Then the normalised output has keys "passed" and "message" and "data"
# ── Config keys ────────────────────────────────────────────────
Scenario: Config key constants are defined
Then config key "validation.semantic.enabled" exists
And config key "validation.semantic.python.enabled" exists
And config key "validation.semantic.severity_mapping" exists
# ── Required vs informational attachment modes ─────────────────
Scenario: Error severity findings integrate as required in pipeline
Given a semantic validation service
And Python source code with a syntax error
When the service returns pipeline results
Then a pipeline result with severity "error" has mode "required"
Scenario: Warn severity findings integrate as informational in pipeline
Given a semantic validation service
And Python source using eval
When the service returns pipeline results
Then a pipeline result with severity "warn" has mode "informational"
# ── BrokenReferenceRule scope-awareness ─────────────────────────
Scenario: BrokenReferenceRule passes for function-local variables
Given Python source with function-local variables
When the broken reference rule runs
Then the semantic check result passed is true
Scenario: BrokenReferenceRule passes for annotated assignments
Given Python source with annotated assignment
When the broken reference rule runs
Then the semantic check result passed is true
Scenario: BrokenReferenceRule passes for with-statement variables
Given Python source with with-statement variable
When the broken reference rule runs
Then the semantic check result passed is true
# ── MissingSymbolRule class/nested function support ────────────
Scenario: MissingSymbolRule detects undefined symbols in class methods
Given Python source with a class method using undefined symbol
When the missing symbol rule runs
Then the semantic check result passed is false
And the semantic check result message contains "missing symbol"
Scenario: MissingSymbolRule passes for nested function calls
Given Python source with nested functions
When the missing symbol rule runs
Then the semantic check result passed is true
# ── MissingSymbolRule comprehension scope ───────────────────────
Scenario: MissingSymbolRule does not flag comprehension variables
Given Python source with comprehension variables
When the missing symbol rule runs
Then the semantic check result passed is true
# ── DuplicateImportRule alias ──────────────────────────────────
Scenario: DuplicateImportRule passes for no duplicate relative imports
Given Python source with unique relative imports
When the duplicate import rule runs
Then the semantic check result passed is true
Scenario: DuplicateImportRule flags duplicate relative imports
Given Python source with duplicate relative imports
When the duplicate import rule runs
Then the semantic check result passed is false
And the semantic check result message contains "Duplicate relative imports"
# ── APIMisuseRule AST-based detection ──────────────────────────
Scenario: APIMisuseRule flags exec usage
Given Python source using exec
When the API misuse rule runs
Then the semantic check result passed is false
And the semantic check result message contains "API misuse"
Scenario: APIMisuseRule flags os.popen usage
Given Python source using os.popen
When the API misuse rule runs
Then the semantic check result passed is false
And the semantic check result message contains "API misuse"
Scenario: APIMisuseRule flags subprocess.run usage
Given Python source using subprocess.run
When the API misuse rule runs
Then the semantic check result passed is false
And the semantic check result message contains "API misuse"
Scenario: APIMisuseRule ignores eval in string literals
Given Python source with eval in a string literal
When the API misuse rule runs
Then the semantic check result passed is true
# ── Non-Python file handling ───────────────────────────────────
Scenario: Service returns empty for non-Python files
Given a semantic validation service
And valid Python source code
And a non-Python filename
When the service checks the non-python file
Then 0 semantic check results are returned
# ── Empty source ───────────────────────────────────────────────
Scenario: Service handles empty source code
Given a semantic validation service
And empty Python source code
When the service checks the file
Then all semantic check results passed
# ── Cache with max size ────────────────────────────────────────
Scenario: Cache respects max size with LRU eviction
Given a semantic validation cache with max size 2
And a cached semantic result for rule "a" and hash "h1"
And a cached semantic result for rule "b" and hash "h2"
And a cached semantic result for rule "c" and hash "h3"
Then the semantic cache has 2 entries
# ── Safe initialisation and cleanup ────────────────────────────
Scenario: Cache rejects invalid max size on construction
Then creating a semantic validation cache with max size 0 raises ValueError
Scenario: Cache put updates existing entry without growing
Given a semantic validation cache
And a cached semantic result for rule "r1" and hash "h1"
When the cache entry for rule "r1" and hash "h1" is overwritten with message "updated"
Then the cache returns the updated result for rule "r1" and hash "h1" with message "updated"
And the semantic cache has 1 entries
Scenario: Registry list_rules returns sorted names
Given a default semantic rule registry
Then the registry list_rules returns sorted rule names
Scenario: Service exposes registry property for inspection
Given a semantic validation service
Then the service registry property returns a registry with 6 rules
Scenario: resolve_severity falls back to info for invalid severity value
Given a custom severity mapping with "syntax_error" as "bogus_value"
Then resolve_severity with custom mapping for "syntax_error" returns "info"
Scenario: Service functions with non-dict severity mapping config
Given a semantic validation service with non-dict severity mapping
And valid Python source code
When the service checks the file
Then all semantic check results passed
# ── MissingImportRule from-import form ─────────────────────────
Scenario: MissingImportRule flags suspicious from-import with private module
Given Python source with from-import of private module
When the missing import rule runs
Then the semantic check result passed is false
And the semantic check result message contains "suspicious import"
# ── BrokenReferenceRule advanced scope patterns ────────────────
Scenario: BrokenReferenceRule passes for code with varargs and kwargs
Given Python source with varargs and kwargs function
When the broken reference rule runs
Then the semantic check result passed is true
Scenario: BrokenReferenceRule passes for from-import bindings
Given Python source with from-import bindings
When the broken reference rule runs
Then the semantic check result passed is true
Scenario: BrokenReferenceRule passes for except handler variable
Given Python source with except handler variable
When the broken reference rule runs
Then the semantic check result passed is true
Scenario: BrokenReferenceRule passes for tuple and starred unpacking
Given Python source with tuple and starred unpacking
When the broken reference rule runs
Then the semantic check result passed is true
Scenario: BrokenReferenceRule passes for with-statement without as clause
Given Python source with with-statement without as clause
When the broken reference rule runs
Then the semantic check result passed is true
# ── APIMisuseRule safe attribute on flagged module ──────────────
Scenario: APIMisuseRule passes for safe method call on flagged module
Given Python source calling a safe method on a flagged module
When the API misuse rule runs
Then the semantic check result passed is true
# ── MissingSymbolRule function-local binding patterns ──────────
Scenario: MissingSymbolRule passes for function with all parameter kinds
Given Python source with function using varargs kwonly and kwargs
When the missing symbol rule runs
Then the semantic check result passed is true
Scenario: MissingSymbolRule passes for function with for-loop variable
Given Python source with function containing for-loop variable
When the missing symbol rule runs
Then the semantic check result passed is true
Scenario: MissingSymbolRule passes for function with with-statement variable
Given Python source with function containing with-statement variable
When the missing symbol rule runs
Then the semantic check result passed is true
Scenario: MissingSymbolRule passes for function with except handler variable
Given Python source with function containing except handler variable
When the missing symbol rule runs
Then the semantic check result passed is true
Scenario: MissingSymbolRule passes for function with local import
Given Python source with function containing local import
When the missing symbol rule runs
Then the semantic check result passed is true
Scenario: MissingSymbolRule passes for function with local from-import
Given Python source with function containing local from-import
When the missing symbol rule runs
Then the semantic check result passed is true