f2f7aa5dc9
Implement multiple Stage A/B/E/SEC milestones for the v3 lifecycle system: - Stage A5.3+A5.4: Add LifecycleActionModel and LifecyclePlanModel SQLAlchemy models with to_domain()/from_domain() conversion methods - Stage A5.6: Implement ActionRepository with full CRUD, namespace/state queries, referential integrity checks, and retry decorator - Stage E1: Add subplan domain models (ExecutionMode, SubplanMergeStrategy, SubplanConfig, SubplanStatus, SubplanAttempt, SubplanFailureHandler) with computed properties on Plan (is_subplan, is_root_plan, depth, has_subplans) - Stage A6: Add AutomationLevel enum (MANUAL, REVIEW_BEFORE_APPLY, FULL_AUTOMATION), settings integration, PlanLifecycleService auto-progression, pause/resume, and CLI commands (--automation-level, set-automation-level) - Stage SEC1: Remove eval()/exec() from stream_router.py, replace with named operation and transform registries; code blocks and unregistered transforms now raise StreamRoutingError - Add langchain-anthropic dependency - Update BDD tests for security changes and relax ADR directory requirement
161 lines
6.8 KiB
Gherkin
161 lines
6.8 KiB
Gherkin
@phase1 @infrastructure @sandbox @merge
|
|
Feature: Sandbox Merge Strategies
|
|
As a system managing parallel sandbox changes
|
|
I want multiple strategies for merging concurrent edits to the same resource
|
|
So that conflicts are detected, reported, or resolved automatically
|
|
|
|
# --- Git three-way merge ---
|
|
|
|
@git_merge
|
|
Scenario: A clean merge combines non-overlapping changes from both sides
|
|
Given a base document with three paragraphs
|
|
And one side appends a paragraph at the end
|
|
And the other side prepends a paragraph at the start
|
|
When the changes are merged using the git strategy
|
|
Then the merge should succeed without conflicts
|
|
And the merged document should contain content from both sides
|
|
|
|
@git_merge
|
|
Scenario: Overlapping edits to the same line produce a conflict
|
|
Given a base document with a single line
|
|
And one side changes the line to "alpha version"
|
|
And the other side changes the line to "beta version"
|
|
When the changes are merged using the git strategy
|
|
Then the merge should report a conflict
|
|
And the merged output should contain conflict markers
|
|
And the conflict regions should be identified with line ranges
|
|
|
|
@git_merge
|
|
Scenario: Merging three empty documents yields an empty result
|
|
Given all three merge inputs are empty
|
|
When the changes are merged using the git strategy
|
|
Then the merge should succeed without conflicts
|
|
And the merged content should be empty
|
|
|
|
@git_merge
|
|
Scenario: The git strategy cleans up temporary files after merging
|
|
Given a base document with a single line
|
|
And one side leaves the document unchanged
|
|
And the other side appends a new line
|
|
When the changes are merged using the git strategy
|
|
Then no temporary merge files should remain on disk
|
|
|
|
@git_merge
|
|
Scenario: The git strategy falls back to sequential merge when git is unavailable
|
|
Given a base document with a single line
|
|
And one side changes the line to "ours edit"
|
|
And the other side changes the line to "theirs edit"
|
|
When the changes are merged using the git strategy with git unavailable
|
|
Then the merge should succeed without conflicts
|
|
And the merged content should be the incoming side
|
|
|
|
# --- Conflict marker detection ---
|
|
|
|
@git_merge @conflict_markers
|
|
Scenario: No conflict markers are found in clean content
|
|
Given a document with no conflict markers
|
|
When the document is scanned for conflict regions
|
|
Then no conflict regions should be found
|
|
|
|
@git_merge @conflict_markers
|
|
Scenario: A single conflict region is correctly identified
|
|
Given a document containing one conflict region
|
|
When the document is scanned for conflict regions
|
|
Then exactly one conflict region should be found
|
|
And the conflict region should span the expected lines
|
|
|
|
@git_merge @conflict_markers
|
|
Scenario: Multiple conflict regions are all identified
|
|
Given a document containing two conflict regions
|
|
When the document is scanned for conflict regions
|
|
Then exactly two conflict regions should be found
|
|
|
|
# --- Sequential last-write-wins merge ---
|
|
|
|
@sequential_merge
|
|
Scenario: The incoming content always wins regardless of changes
|
|
Given a base document, a current version, and an incoming version
|
|
When the changes are merged using the sequential strategy
|
|
Then the merge should succeed without conflicts
|
|
And the merged content should be the incoming version
|
|
|
|
@sequential_merge
|
|
Scenario: An empty incoming version results in empty content
|
|
Given a base document, a current version, and an empty incoming version
|
|
When the changes are merged using the sequential strategy
|
|
Then the merged content should be empty
|
|
|
|
# --- JSON deep merge ---
|
|
|
|
@json_merge
|
|
Scenario: Non-overlapping keys from both sides are combined
|
|
Given a current JSON document with key "alpha"
|
|
And an incoming JSON document with key "beta"
|
|
When the documents are merged using the JSON strategy
|
|
Then the merge should succeed without conflicts
|
|
And the merged JSON should contain both keys
|
|
|
|
@json_merge
|
|
Scenario: A shared key is recursively merged when both values are objects
|
|
Given a current JSON document with nested object under "config"
|
|
And an incoming JSON document with additional nested keys under "config"
|
|
When the documents are merged using the JSON strategy
|
|
Then the merged JSON "config" should contain keys from both sides
|
|
|
|
@json_merge
|
|
Scenario: A shared scalar key is overwritten by the incoming value
|
|
Given a current JSON document with "version" set to 1
|
|
And an incoming JSON document with "version" set to 2
|
|
When the documents are merged using the JSON strategy
|
|
Then the merged JSON "version" should be the incoming value
|
|
|
|
@json_merge
|
|
Scenario: Arrays are replaced by the incoming side in replace mode
|
|
Given a current JSON document with an array under "items"
|
|
And an incoming JSON document with a different array under "items"
|
|
When the documents are merged using the JSON strategy in replace mode
|
|
Then the merged JSON "items" should be the incoming array
|
|
|
|
@json_merge
|
|
Scenario: Arrays are concatenated in concat mode
|
|
Given a current JSON document with an array under "items"
|
|
And an incoming JSON document with a different array under "items"
|
|
When the documents are merged using the JSON strategy in concat mode
|
|
Then the merged JSON "items" should contain elements from both arrays
|
|
|
|
@json_merge
|
|
Scenario: Invalid JSON in one document produces a failed merge
|
|
Given a current document containing invalid JSON
|
|
And an incoming JSON document with key "valid"
|
|
When the documents are merged using the JSON strategy
|
|
Then the merge should fail
|
|
And the failed merge content should fall back to the incoming text
|
|
|
|
@json_merge
|
|
Scenario: An empty current document is treated as an empty object
|
|
Given an empty current JSON document
|
|
And an incoming JSON document with key "data"
|
|
When the documents are merged using the JSON strategy
|
|
Then the merge should succeed without conflicts
|
|
And the merged JSON should contain key "data"
|
|
|
|
@json_merge
|
|
Scenario: An empty incoming document is treated as an empty object
|
|
Given a current JSON document with key "existing"
|
|
And an empty incoming JSON document
|
|
When the documents are merged using the JSON strategy
|
|
Then the merge should succeed without conflicts
|
|
And the merged JSON should contain key "existing"
|
|
|
|
@json_merge
|
|
Scenario: An unrecognized array mode is rejected during configuration
|
|
When the JSON strategy is configured with an invalid array mode
|
|
Then a configuration error should be raised
|
|
|
|
@json_merge
|
|
Scenario: A type mismatch between sides is resolved by the incoming value
|
|
Given a current JSON document with "field" as a string
|
|
And an incoming JSON document with "field" as a number
|
|
When the documents are merged using the JSON strategy
|
|
Then the merged JSON "field" should be the incoming number value
|