Files
cleveragents-core/features/change_tracking.feature

181 lines
7.0 KiB
Gherkin

Feature: ChangeSet Models and Invocation Tracking
As a developer
I want ChangeSet models with deterministic ordering and invocation tracking
So that all tool executions and their changes are auditable
# ---- ChangeType Alias ----
Scenario: ChangeType is an alias for ChangeOperation
Given I import ChangeType and ChangeOperation
Then ChangeType should be the same class as ChangeOperation
And ChangeType CREATE should equal "create"
# ---- ChangeEntry Validators ----
Scenario: CREATE entry rejects before_hash
When I create a ChangeEntry with CREATE and before_hash "abc"
Then a validation error should be raised mentioning "before_hash"
Scenario: DELETE entry rejects after_hash
When I create a ChangeEntry with DELETE and after_hash "abc"
Then a validation error should be raised mentioning "after_hash"
Scenario: CREATE entry accepts after_hash only
When I create a valid CREATE ChangeEntry with after_hash "abc123"
Then the change entry operation should be "create"
And the change entry after_hash should be "abc123"
Scenario: MODIFY entry accepts both hashes
When I create a valid MODIFY ChangeEntry with both hashes
Then the change entry operation should be "modify"
And the change entry has_integrity_hashes should be true
Scenario: RENAME entry without hashes is valid
When I create a RENAME ChangeEntry without hashes
Then the change entry operation should be "rename"
And the change entry has_integrity_hashes should be true
Scenario: CREATE entry without after_hash has no integrity
When I create a CREATE ChangeEntry without any hashes
Then the change entry has_integrity_hashes should be false
# ---- Path Normalization ----
Scenario: Normalize absolute path with repo root
Given a repo root "/home/user/project"
When I normalize the path "/home/user/project/src/main.py"
Then the normalized path should be "src/main.py"
Scenario: Normalize relative path stays unchanged
Given an empty repo root
When I normalize the path "src/main.py"
Then the normalized path should be "src/main.py"
Scenario: Normalize path outside repo root is unchanged
Given a repo root "/other/project"
When I normalize the path "/home/user/project/src/main.py"
Then the normalized path should be "home/user/project/src/main.py"
# ---- SpecChangeSet Ordering ----
Scenario: sorted_entries returns deterministic order
Given a SpecChangeSet with entries in random order
When I call sorted_entries
Then entries should be sorted by resource_id then path then timestamp
Scenario: grouped_by_resource groups correctly
Given a SpecChangeSet with entries for multiple resources
When I call grouped_by_resource
Then entries should be grouped by resource_id
And each group should be sorted by path and timestamp
Scenario: add_change appends to entries
Given an empty SpecChangeSet
When I add a change entry to it
Then the spec changeset should contain 1 entry
# ---- ToolInvocation Model ----
Scenario: Create a ToolInvocation with minimal fields
When I create a ToolInvocation for plan "plan-1" and tool "builtin/file-write"
Then the invocation should have a ULID invocation_id
And the invocation plan_id should be "plan-1"
And the invocation tool_name should be "builtin/file-write"
And the invocation success should be true
Scenario: Create a failed ToolInvocation
When I create a failed ToolInvocation with error "file not found"
Then the invocation success should be false
And the invocation error should be "file not found"
Scenario: ToolInvocation with change_ids
When I create a ToolInvocation with change_ids
Then the invocation should have 2 change_ids
Scenario: ToolInvocation with provider metadata
When I create a ToolInvocation with provider metadata
Then the invocation provider_metadata should contain model name
# ---- InMemoryInvocationTracker ----
Scenario: Track invocations and retrieve by plan
Given an InMemoryInvocationTracker
When I track 3 invocations for plan "plan-A"
And I track 2 invocations for plan "plan-B"
Then get_invocations for "plan-A" should return 3
And get_invocations for "plan-B" should return 2
Scenario: Retrieve invocations for a specific skill
Given an InMemoryInvocationTracker
When I track invocations for different skills
Then get_invocations_for_skill should filter correctly
Scenario: Get change IDs across invocations
Given an InMemoryInvocationTracker
When I track invocations with change_ids
Then get_changes should return all change_ids for the plan
Scenario: Invocations are ordered by sequence number
Given an InMemoryInvocationTracker
When I track invocations out of order
Then get_invocations should return them in sequence order
# ---- SpecChangeSet summary ----
Scenario: summary returns correct counts for mixed operations
Given a SpecChangeSet with mixed operation entries
When I call summary on the changeset
Then the summary total should be 4
And the summary creates should be 1
And the summary modifies should be 1
And the summary deletes should be 1
And the summary renames should be 1
And the summary paths_changed should be 4
And the summary resources_involved should be 2
# ---- add_change plan_id validation ----
Scenario: add_change rejects entry with mismatched plan_id
Given an empty SpecChangeSet
When I add a change entry with a different plan_id
Then a validation error should be raised mentioning "plan_id"
# ---- Empty tracker queries ----
Scenario: get_invocations returns empty for unknown plan
Given an InMemoryInvocationTracker
Then get_invocations for "nonexistent-plan" should return 0
Scenario: get_changes returns empty for unknown plan
Given an InMemoryInvocationTracker
Then get_changes for "nonexistent-plan" should return 0 ids
Scenario: get_invocations_for_skill returns empty for unknown skill
Given an InMemoryInvocationTracker
When I track 1 invocations for plan "plan-X"
Then get_invocations_for_skill "plan-X" "no-such-skill" should return 0
# ---- get_changes ordering proof ----
Scenario: get_changes respects sequence order not insertion order
Given an InMemoryInvocationTracker
When I track change invocations in reverse sequence order
Then get_changes should return change_ids in sequence order
# ---- Path normalization edge cases ----
Scenario: Normalize path that equals repo root returns dot
Given a repo root "/home/user/project"
When I normalize the path "/home/user/project"
Then the normalized path should be "."
Scenario: Normalize path with dot-dot components preserves them
Given a repo root "/repo"
When I normalize the path "/repo/foo/../bar/file.py"
Then the normalized path should be "foo/../bar/file.py"
Scenario: Normalize empty path returns dot
Given an empty repo root
When I normalize the path "."
Then the normalized path should be "."