From 51b6d46d843d3466e6a3c8f4de26e6d7305de69f Mon Sep 17 00:00:00 2001 From: Trey t Date: Sun, 18 Jan 2026 14:48:10 -0600 Subject: [PATCH] docs(02): create phase 2 constraint validation plans Phase 2: Constraint Validation - 2 plans in 1 wave (parallel) - Both plans autonomous (no checkpoints) Plan 02-01: Migrate 13 XCTest tests to Swift Testing Plan 02-02: Add edge case tests and document constraint API Note: ItineraryConstraints is already fully implemented. This phase verifies and standardizes tests. Co-Authored-By: Claude Opus 4.5 --- .planning/ROADMAP.md | 8 +- .../02-constraint-validation/02-01-PLAN.md | 189 ++++++++ .../02-constraint-validation/02-02-PLAN.md | 449 ++++++++++++++++++ 3 files changed, 645 insertions(+), 1 deletion(-) create mode 100644 .planning/phases/02-constraint-validation/02-01-PLAN.md create mode 100644 .planning/phases/02-constraint-validation/02-02-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index ac6b246..302ff17 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -42,6 +42,12 @@ Plans: **Dependencies:** Phase 1 (semantic position model) +**Plans:** 2 plans + +Plans: +- [ ] 02-01-PLAN.md - Migrate XCTest constraint tests to Swift Testing +- [ ] 02-02-PLAN.md - Add edge case tests and document constraint API + **Requirements:** - CONS-01: Games cannot be moved (fixed by schedule) - CONS-02: Travel segments constrained to valid day range @@ -104,7 +110,7 @@ Plans: | Phase | Status | Requirements | Completed | |-------|--------|--------------|-----------| | 1 - Semantic Position Model | ✓ Complete | 8 | 8 | -| 2 - Constraint Validation | Not Started | 4 | 0 | +| 2 - Constraint Validation | Planned | 4 | 0 | | 3 - Visual Flattening | Not Started | 3 | 0 | | 4 - Drag Interaction | Not Started | 8 | 0 | diff --git a/.planning/phases/02-constraint-validation/02-01-PLAN.md b/.planning/phases/02-constraint-validation/02-01-PLAN.md new file mode 100644 index 0000000..ce6f793 --- /dev/null +++ b/.planning/phases/02-constraint-validation/02-01-PLAN.md @@ -0,0 +1,189 @@ +--- +phase: 02-constraint-validation +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - SportsTimeTests/ItineraryConstraintsTests.swift +autonomous: true +user_setup: [] + +must_haves: + truths: + - "All CONS-01 through CONS-04 requirements have corresponding passing tests" + - "Tests use Swift Testing framework (@Test, @Suite) matching Phase 1 patterns" + - "ItineraryConstraints API is fully tested with no coverage gaps" + artifacts: + - path: "SportsTimeTests/Domain/ItineraryConstraintsTests.swift" + provides: "Migrated constraint validation tests" + contains: "@Suite" + min_lines: 200 + key_links: + - from: "SportsTimeTests/Domain/ItineraryConstraintsTests.swift" + to: "SportsTime/Core/Models/Domain/ItineraryConstraints.swift" + via: "import @testable SportsTime" + pattern: "@testable import SportsTime" +--- + + +Migrate the 13 existing XCTest constraint tests to Swift Testing and move them to the Domain test folder. + +Purpose: Standardize test patterns across the project. Phase 1 established Swift Testing as the project standard; constraint tests should follow. +Output: `SportsTimeTests/Domain/ItineraryConstraintsTests.swift` with all tests passing using @Test/@Suite syntax. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/02-constraint-validation/02-RESEARCH.md + +# Pattern reference from Phase 1 +@SportsTimeTests/Domain/SortOrderProviderTests.swift +@SportsTimeTests/Domain/SemanticPositionPersistenceTests.swift + +# Source test file to migrate +@SportsTimeTests/ItineraryConstraintsTests.swift + +# Implementation being tested +@SportsTime/Core/Models/Domain/ItineraryConstraints.swift + + + + + + Task 1: Verify requirements coverage in existing tests + SportsTimeTests/ItineraryConstraintsTests.swift + +Read the existing 13 XCTest tests and map them to requirements: + +| Requirement | Test(s) | Coverage | +|-------------|---------|----------| +| CONS-01 (games cannot move) | test_gameItem_cannotBeMoved | Verify complete | +| CONS-02 (travel day range) | test_travel_validDayRange_simpleCase, test_travel_cannotGoOutsideValidDayRange | Verify complete | +| CONS-03 (travel sortOrder on game days) | test_travel_mustBeAfterDepartureGames, test_travel_mustBeBeforeArrivalGames, test_travel_mustBeAfterAllDepartureGamesOnSameDay, test_travel_mustBeBeforeAllArrivalGamesOnSameDay, test_travel_canBeAnywhereOnRestDays | Verify complete | +| CONS-04 (custom no constraints) | test_customItem_canGoOnAnyDay, test_customItem_canGoBeforeOrAfterGames | Verify complete | + +Document any gaps found. If all requirements are covered, proceed to migration. + + Requirements coverage table is complete with no gaps + All CONS-01 through CONS-04 requirements map to at least one existing test + + + + Task 2: Migrate tests to Swift Testing + SportsTimeTests/Domain/ItineraryConstraintsTests.swift, SportsTimeTests/ItineraryConstraintsTests.swift + +1. Create new file at `SportsTimeTests/Domain/ItineraryConstraintsTests.swift` + +2. Convert XCTest syntax to Swift Testing: + - `final class ItineraryConstraintsTests: XCTestCase` -> `@Suite("ItineraryConstraints") struct ItineraryConstraintsTests` + - `func test_*()` -> `@Test("description") func *()` (preserve test names, add descriptive strings) + - `XCTAssertTrue(x)` -> `#expect(x == true)` or `#expect(x)` + - `XCTAssertFalse(x)` -> `#expect(x == false)` or `#expect(!x)` + - `XCTAssertEqual(a, b)` -> `#expect(a == b)` + - `XCTAssertNil(x)` -> `#expect(x == nil)` + - `import XCTest` -> `import Testing` + +3. Organize tests into logical groups using MARK comments: + - `// MARK: - Custom Item Tests (CONS-04)` + - `// MARK: - Travel Day Range Tests (CONS-02)` + - `// MARK: - Travel SortOrder Tests (CONS-03)` + - `// MARK: - Game Immutability Tests (CONS-01)` + - `// MARK: - Edge Cases` + - `// MARK: - Barrier Games` + - `// MARK: - Helpers` + +4. Preserve all helper methods (makeConstraints, makeGameItem, makeTravelItem, makeCustomItem) + +5. Delete the old file at `SportsTimeTests/ItineraryConstraintsTests.swift` + +Pattern reference - follow SortOrderProviderTests.swift style: +```swift +import Testing +import Foundation +@testable import SportsTime + +@Suite("ItineraryConstraints") +struct ItineraryConstraintsTests { + + // MARK: - Custom Item Tests (CONS-04) + + @Test("custom: can go on any day") + func custom_canGoOnAnyDay() { + let constraints = makeConstraints(tripDays: 5, gameDays: [1, 5]) + let customItem = makeCustomItem(day: 1, sortOrder: 50) + + for day in 1...5 { + #expect(constraints.isValidPosition(for: customItem, day: day, sortOrder: 50)) + } + } + // ... +} +``` + + +Run tests: +``` +xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/ItineraryConstraintsTests test 2>&1 | grep -E "(Test Suite|Executed|passed|failed)" +``` +All 13 tests pass. + + New file at Domain/ItineraryConstraintsTests.swift passes all 13 tests, old file deleted + + + + Task 3: Run full test suite and commit + None (verification only) + +1. Run full test suite to verify no regressions: +``` +xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test 2>&1 | grep -E "(Test Suite|Executed|passed|failed)" +``` + +2. Commit the migration: +``` +git add SportsTimeTests/Domain/ItineraryConstraintsTests.swift +git rm SportsTimeTests/ItineraryConstraintsTests.swift +git commit -m "test(02-01): migrate ItineraryConstraints tests to Swift Testing + +Migrate 13 XCTest tests to Swift Testing framework: +- Move to Domain/ folder to match project structure +- Convert XCTestCase to @Suite/@Test syntax +- Update assertions to #expect macros +- Verify all CONS-01 through CONS-04 requirements covered + +Co-Authored-By: Claude Opus 4.5 " +``` + + Full test suite passes with no regressions + Migration committed, all tests pass including existing 34 Phase 1 tests + + + + + +After all tasks: +1. `SportsTimeTests/Domain/ItineraryConstraintsTests.swift` exists with @Suite/@Test syntax +2. Old `SportsTimeTests/ItineraryConstraintsTests.swift` is deleted +3. All 13 constraint tests pass +4. Full test suite passes (no regressions) +5. Tests organized by requirement (CONS-01 through CONS-04) + + + +- 13 tests migrated from XCTest to Swift Testing +- Tests use @Test/@Suite syntax matching Phase 1 patterns +- All CONS-01 through CONS-04 requirements have corresponding tests +- Full test suite passes + + + +After completion, create `.planning/phases/02-constraint-validation/02-01-SUMMARY.md` + diff --git a/.planning/phases/02-constraint-validation/02-02-PLAN.md b/.planning/phases/02-constraint-validation/02-02-PLAN.md new file mode 100644 index 0000000..d1eef45 --- /dev/null +++ b/.planning/phases/02-constraint-validation/02-02-PLAN.md @@ -0,0 +1,449 @@ +--- +phase: 02-constraint-validation +plan: 02 +type: execute +wave: 1 +depends_on: [] +files_modified: + - SportsTimeTests/Domain/ItineraryConstraintsTests.swift +autonomous: true +user_setup: [] + +must_haves: + truths: + - "Edge cases are tested (empty trip, single-day trip, boundary sortOrders)" + - "Success criteria from roadmap are verifiable by tests" + - "Phase 4 has clear API documentation for drag-drop integration" + artifacts: + - path: "SportsTimeTests/Domain/ItineraryConstraintsTests.swift" + provides: "Complete constraint test suite with edge cases" + contains: "Edge Cases" + min_lines: 280 + - path: ".planning/phases/02-constraint-validation/CONSTRAINT-API.md" + provides: "API documentation for Phase 4" + contains: "isValidPosition" + key_links: + - from: ".planning/phases/02-constraint-validation/CONSTRAINT-API.md" + to: "SportsTime/Core/Models/Domain/ItineraryConstraints.swift" + via: "documents public API" + pattern: "ItineraryConstraints" +--- + + +Add edge case tests and create API documentation for Phase 4 integration. + +Purpose: Ensure constraint system handles boundary conditions and provide clear reference for drag-drop implementation. +Output: Enhanced test suite with edge cases, API documentation for Phase 4. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/02-constraint-validation/02-RESEARCH.md + +# Implementation being tested +@SportsTime/Core/Models/Domain/ItineraryConstraints.swift + +# Test file (will be enhanced) +@SportsTimeTests/Domain/ItineraryConstraintsTests.swift + + + + + + Task 1: Add edge case tests + SportsTimeTests/Domain/ItineraryConstraintsTests.swift + +Add the following edge case tests to the `// MARK: - Edge Cases` section: + +```swift +// MARK: - Edge Cases + +@Test("edge: single-day trip accepts valid positions") +func edge_singleDayTrip_acceptsValidPositions() { + let constraints = makeConstraints(tripDays: 1, gameDays: []) + let custom = makeCustomItem(day: 1, sortOrder: 50) + + #expect(constraints.isValidPosition(for: custom, day: 1, sortOrder: 50)) + #expect(!constraints.isValidPosition(for: custom, day: 0, sortOrder: 50)) + #expect(!constraints.isValidPosition(for: custom, day: 2, sortOrder: 50)) +} + +@Test("edge: day 0 is always invalid") +func edge_day0_isAlwaysInvalid() { + let constraints = makeConstraints(tripDays: 5, gameDays: []) + let custom = makeCustomItem(day: 1, sortOrder: 50) + + #expect(!constraints.isValidPosition(for: custom, day: 0, sortOrder: 50)) +} + +@Test("edge: day beyond trip is invalid") +func edge_dayBeyondTrip_isInvalid() { + let constraints = makeConstraints(tripDays: 3, gameDays: []) + let custom = makeCustomItem(day: 1, sortOrder: 50) + + #expect(!constraints.isValidPosition(for: custom, day: 4, sortOrder: 50)) + #expect(!constraints.isValidPosition(for: custom, day: 100, sortOrder: 50)) +} + +@Test("edge: travel at exact game sortOrder boundary is invalid") +func edge_travelAtExactGameSortOrder_isInvalid() { + // Game at sortOrder 100 + let constraints = makeConstraints( + tripDays: 3, + games: [makeGameItem(city: "Chicago", day: 1, sortOrder: 100)] + ) + let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 100) + + // Exactly AT game sortOrder should be invalid (must be AFTER) + #expect(!constraints.isValidPosition(for: travel, day: 1, sortOrder: 100)) + + // Just after should be valid + #expect(constraints.isValidPosition(for: travel, day: 1, sortOrder: 100.001)) +} + +@Test("edge: travel with no games in either city has full range") +func edge_travelNoGamesInEitherCity_hasFullRange() { + let constraints = makeConstraints(tripDays: 5, games: []) + let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 50) + + // Valid on any day + for day in 1...5 { + #expect(constraints.isValidPosition(for: travel, day: day, sortOrder: 50)) + } + + // Full range + #expect(constraints.validDayRange(for: travel) == 1...5) +} + +@Test("edge: negative sortOrder is valid for custom items") +func edge_negativeSortOrder_validForCustomItems() { + let constraints = makeConstraints(tripDays: 3, gameDays: []) + let custom = makeCustomItem(day: 2, sortOrder: -100) + + #expect(constraints.isValidPosition(for: custom, day: 2, sortOrder: -100)) +} + +@Test("edge: very large sortOrder is valid for custom items") +func edge_veryLargeSortOrder_validForCustomItems() { + let constraints = makeConstraints(tripDays: 3, gameDays: []) + let custom = makeCustomItem(day: 2, sortOrder: 10000) + + #expect(constraints.isValidPosition(for: custom, day: 2, sortOrder: 10000)) +} +``` + +Also add a test verifying the roadmap success criteria are testable: + +```swift +// MARK: - Success Criteria Verification + +@Test("success: game row shows no drag interaction (game not draggable)") +func success_gameNotDraggable() { + // Games return false for ANY position, making them non-draggable + let game = makeGameItem(city: "Chicago", day: 2, sortOrder: 100) + let constraints = makeConstraints(tripDays: 5, games: [game]) + + // Same position + #expect(!constraints.isValidPosition(for: game, day: 2, sortOrder: 100)) + // Different day + #expect(!constraints.isValidPosition(for: game, day: 1, sortOrder: 100)) + // Different sortOrder + #expect(!constraints.isValidPosition(for: game, day: 2, sortOrder: 50)) +} + +@Test("success: custom note can be placed anywhere") +func success_customNotePlacedAnywhere() { + // Custom items can be placed before, between, or after games on any day + let constraints = makeConstraints( + tripDays: 3, + games: [ + makeGameItem(city: "Chicago", day: 1, sortOrder: 100), + makeGameItem(city: "Chicago", day: 1, sortOrder: 200), + makeGameItem(city: "Detroit", day: 3, sortOrder: 100) + ] + ) + let custom = makeCustomItem(day: 1, sortOrder: 50) + + // Before games on day 1 + #expect(constraints.isValidPosition(for: custom, day: 1, sortOrder: 50)) + // Between games on day 1 + #expect(constraints.isValidPosition(for: custom, day: 1, sortOrder: 150)) + // After games on day 1 + #expect(constraints.isValidPosition(for: custom, day: 1, sortOrder: 250)) + // On rest day (day 2) + #expect(constraints.isValidPosition(for: custom, day: 2, sortOrder: 50)) + // On day 3 with different city games + #expect(constraints.isValidPosition(for: custom, day: 3, sortOrder: 50)) +} + +@Test("success: invalid position returns false (rejection)") +func success_invalidPositionReturnsRejection() { + // Travel segment cannot be placed before departure game + let constraints = makeConstraints( + tripDays: 3, + games: [makeGameItem(city: "Chicago", day: 2, sortOrder: 100)] + ) + let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 50) + + // Day 1 is before Chicago game on Day 2, so invalid + #expect(!constraints.isValidPosition(for: travel, day: 1, sortOrder: 50)) +} +``` + + +Run updated tests: +``` +xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/ItineraryConstraintsTests test 2>&1 | grep -E "(Test Suite|Executed|passed|failed)" +``` +All tests pass (original 13 + 10 new edge cases = 23 total). + + 10 additional edge case tests added and passing + + + + Task 2: Create API documentation for Phase 4 + .planning/phases/02-constraint-validation/CONSTRAINT-API.md + +Create API documentation that Phase 4 can reference for drag-drop integration: + +```markdown +# ItineraryConstraints API + +**Location:** `SportsTime/Core/Models/Domain/ItineraryConstraints.swift` +**Verified by:** 23 tests in `SportsTimeTests/Domain/ItineraryConstraintsTests.swift` + +## Overview + +`ItineraryConstraints` validates item positions during drag-drop operations. It enforces: + +- **Games cannot move** (CONS-01) +- **Travel segments have day range limits** (CONS-02) +- **Travel segments must respect game sortOrder on same day** (CONS-03) +- **Custom items have no constraints** (CONS-04) + +## Construction + +```swift +let constraints = ItineraryConstraints( + tripDayCount: days.count, + items: allItineraryItems // All items including games +) +``` + +**Parameters:** +- `tripDayCount`: Total days in trip (1-indexed, so a 5-day trip has days 1-5) +- `items`: All itinerary items (games, travel, custom). Games are used to calculate constraints for travel items. + +## Public API + +### `isValidPosition(for:day:sortOrder:) -> Bool` + +Check if a specific position is valid for an item. + +```swift +func isValidPosition(for item: ItineraryItem, day: Int, sortOrder: Double) -> Bool +``` + +**Usage during drag:** +```swift +// On each drag position update +let dropPosition = calculateDropPosition(at: touchLocation) +let isValid = constraints.isValidPosition( + for: draggedItem, + day: dropPosition.day, + sortOrder: dropPosition.sortOrder +) + +if isValid { + showValidDropIndicator() +} else { + showInvalidDropIndicator() +} +``` + +**Returns:** +- `true`: Position is valid, allow drop +- `false`: Position is invalid, reject drop (snap back) + +**Rules by item type:** +| Item Type | Day Constraint | SortOrder Constraint | +|-----------|----------------|----------------------| +| `.game` | Always `false` | Always `false` | +| `.travel` | Within valid day range | After departure games, before arrival games | +| `.custom` | Any day 1...tripDayCount | Any sortOrder | + +### `validDayRange(for:) -> ClosedRange?` + +Get the valid day range for a travel item (for visual feedback). + +```swift +func validDayRange(for item: ItineraryItem) -> ClosedRange? +``` + +**Usage at drag start:** +```swift +// When drag begins, precompute valid range +guard case .travel = draggedItem.kind, + let validRange = constraints.validDayRange(for: draggedItem) else { + // Not a travel item or impossible constraints + return +} + +// Use range to dim invalid days +for day in 1...tripDayCount { + if !validRange.contains(day) { + dimDay(day) + } +} +``` + +**Returns:** +- `ClosedRange`: Valid day range (e.g., `2...4`) +- `nil`: Constraints are impossible (e.g., departure game after arrival game) + +### `barrierGames(for:) -> [ItineraryItem]` + +Get games that constrain a travel item (for visual highlighting). + +```swift +func barrierGames(for item: ItineraryItem) -> [ItineraryItem] +``` + +**Usage for visual feedback:** +```swift +// Highlight barrier games during drag +let barriers = constraints.barrierGames(for: travelItem) +for barrier in barriers { + highlightAsBarrier(barrier) // e.g., gold border +} +``` + +**Returns:** +- Array of game items: Last departure city game + first arrival city game +- Empty array: Not a travel item or no constraining games + +## Integration Points + +### ItineraryTableViewController (existing) + +```swift +// In reloadData() +self.constraints = ItineraryConstraints(tripDayCount: tripDayCount, items: itineraryItems) + +// In drag handling +if constraints.isValidPosition(for: draggedItem, day: targetDay, sortOrder: targetSortOrder) { + // Allow drop +} else { + // Reject drop, snap back +} +``` + +### Phase 4 Implementation Notes + +1. **Drag Start:** + - Check `item.isReorderable` (games return `false`) + - Call `validDayRange(for:)` to precompute valid days + - Call `barrierGames(for:)` to identify visual barriers + +2. **Drag Move:** + - Calculate target (day, sortOrder) from touch position + - Call `isValidPosition(for:day:sortOrder:)` for real-time feedback + - Update insertion line (valid) or red indicator (invalid) + +3. **Drag End:** + - Final `isValidPosition(for:day:sortOrder:)` check + - Valid: Update item's day/sortOrder, animate settle + - Invalid: Animate snap back, haptic feedback + +## Test Coverage + +| Requirement | Tests | Verified | +|-------------|-------|----------| +| CONS-01 (games cannot move) | 2 | Yes | +| CONS-02 (travel day range) | 5 | Yes | +| CONS-03 (travel sortOrder) | 5 | Yes | +| CONS-04 (custom flexibility) | 4 | Yes | +| Edge cases | 7 | Yes | +| **Total** | **23** | **100%** | + +--- +*API documented: Phase 02* +*Ready for: Phase 04 (Drag Interaction)* +``` + + File exists and contains all three public methods with usage examples + CONSTRAINT-API.md created with complete API documentation + + + + Task 3: Run full test suite and commit + None (verification only) + +1. Run full test suite to verify no regressions: +``` +xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test 2>&1 | grep -E "(Test Suite|Executed|passed|failed)" +``` + +2. Commit the edge case tests: +``` +git add SportsTimeTests/Domain/ItineraryConstraintsTests.swift +git commit -m "test(02-02): add edge case tests for constraint validation + +Add 10 edge case tests: +- Single-day trip boundaries +- Day 0 and beyond-trip validation +- Exact sortOrder boundary behavior +- Travel with no games in cities +- Negative and large sortOrders +- Success criteria verification tests + +Total: 23 constraint tests + +Co-Authored-By: Claude Opus 4.5 " +``` + +3. Commit the API documentation: +``` +git add .planning/phases/02-constraint-validation/CONSTRAINT-API.md +git commit -m "docs(02-02): document ItineraryConstraints API for Phase 4 + +Document public API for drag-drop integration: +- isValidPosition() for position validation +- validDayRange() for precomputing valid days +- barrierGames() for visual highlighting +- Integration patterns for ItineraryTableViewController + +Co-Authored-By: Claude Opus 4.5 " +``` + + Full test suite passes with no regressions + Edge case tests and API documentation committed + + + + + +After all tasks: +1. 23 total constraint tests pass (13 migrated + 10 edge cases) +2. Full test suite passes (no regressions) +3. CONSTRAINT-API.md exists with complete documentation +4. All commits follow project conventions + + + +- Edge cases tested: single-day, day boundaries, sortOrder boundaries, no-games scenarios +- Roadmap success criteria are verifiable by tests +- API documentation complete for Phase 4 integration +- All tests pass + + + +After completion, create `.planning/phases/02-constraint-validation/02-02-SUMMARY.md` +