Files
Z. Cliffe Schreuders fbb69f9c53 Implement comprehensive server-side validation and client integration for inventory and container management
- Added server-side validation for inventory actions, ensuring items are validated before being added to the player's inventory.
- Updated client-side `addToInventory()` function to include server validation and error handling for inventory actions.
- Implemented container loading logic to fetch contents from the server, handling locked containers appropriately.
- Enhanced player state initialization to ensure starting items are correctly added to the inventory.
- Clarified NPC `itemsHeld` structure and updated validation logic to match the expected item format.
- Improved error handling for room access and container interactions, including transaction safety for state mutations.
- Introduced new endpoints for scenario mapping and bulk inventory synchronization.
- Added performance optimizations, including caching filtered room data and implementing JSON schema validation for item structures.
- Established a detailed implementation plan with phased priorities and testing gaps identified for future work.
2025-11-21 15:27:54 +00:00

1363 lines
40 KiB
Markdown

# Implementation Plan Review: Client Action Validation & Data Filtering
**Date:** November 21, 2025
**Reviewer:** GitHub Copilot
**Status:** Recommendations for Success
**Confidence Level:** High
---
## Executive Summary
The implementation plan is **well-structured and security-focused**, with clear phasing and good separation of concerns. However, there are **9 critical areas** where improvements would significantly increase chances of successful execution and reduce technical debt. This review provides actionable recommendations organized by priority and implementation impact.
---
## 1. CRITICAL ISSUES (Must Fix Before Starting)
### Issue 1.1: Race Condition in Player State Initialization
**Location:** Phase 1.2, Method `initialize_player_state!`
**Problem:**
The current approach initializes player state on `before_create` callback, but:
- Multiple requests might create games simultaneously
- `initializedAt` check only prevents re-initialization within Rails, not across requests
- No transaction-level guarantee that state isn't initialized twice
**Impact:**
- Duplicate inventory items
- Starting items collected twice on concurrent requests
- Inventory inconsistency
**Recommendation:**
```ruby
# Instead of initializedAt flag, use database uniqueness constraint
# and idempotent initialization pattern:
def initialize_player_state!
# Use safe navigation and merge pattern
base_state = {
'currentRoom' => scenario_data['startRoom'],
'unlockedRooms' => [scenario_data['startRoom']].uniq,
'unlockedObjects' => [],
'inventory' => scenario_data['startItemsInInventory']&.dup || [],
'encounteredNPCs' => [],
'globalVariables' => {},
'biometricSamples' => [],
'biometricUnlocks' => [],
'bluetoothDevices' => [],
'notes' => [],
'health' => 100,
'initializedAt' => Time.current.iso8601
}
# Only set if completely empty (first time)
self.player_state = base_state if player_state.blank? || player_state.empty?
end
```
**Action Items:**
- [ ] Create migration to backfill player_state for existing games
- [ ] Add database index on `(id, player_state)` for quick state checks
- [ ] Test concurrent game creation with `AB` testing tool
- [ ] Document idempotent initialization in code comments
---
### Issue 1.2: No Validation of Scenario Data Integrity
**Location:** Phase 1, Entire initialization flow
**Problem:**
- Plan assumes `scenario_data['startRoom']` exists, but doesn't validate
- Plan assumes `startItemsInInventory` is well-formed array, but doesn't validate
- No checks for circular room connections
- No checks for orphaned items (items in rooms that don't exist)
**Impact:**
- Silent failures if scenario JSON is malformed
- Players stuck in non-existent rooms
- Items unreachable
**Recommendation:**
Add validation layer before initialization:
```ruby
class Game < ApplicationRecord
validates :scenario_data, presence: true
validate :scenario_data_integrity
private
def scenario_data_integrity
return if scenario_data.blank?
errors.add(:scenario_data, 'missing startRoom') unless scenario_data['startRoom'].present?
errors.add(:scenario_data, 'startRoom not found') unless scenario_data['rooms']&.key?(scenario_data['startRoom'])
# Validate startItemsInInventory structure
if scenario_data['startItemsInInventory'].present?
unless scenario_data['startItemsInInventory'].is_a?(Array)
errors.add(:scenario_data, 'startItemsInInventory must be an array')
end
end
# Validate all rooms exist (connection integrity)
scenario_data['rooms']&.each do |room_id, room_data|
next unless room_data['connections'].present?
room_data['connections'].values.flatten.each do |connected_room|
unless scenario_data['rooms'].key?(connected_room)
errors.add(:scenario_data, "room #{room_id} connects to non-existent room #{connected_room}")
end
end
end
end
end
```
**Action Items:**
- [ ] Add scenario integrity validator before game creation
- [ ] Create integration tests with malformed scenarios
- [ ] Document expected scenario format as comments in validator
- [ ] Add error messages to help developers debug scenario issues
---
### Issue 1.3: Incomplete Specification of `scenario_data` Structure
**Location:** Phase 2-4, All filtering methods
**Problem:**
- Plan doesn't define what fields exist at each level (rooms, objects, NPCs, items)
- Example shows `lockType`, `locked`, `requires`, but no complete list
- Plan mentions `itemsHeld` on NPCs but structure unclear
- No guidance on optional vs required fields
**Impact:**
- Developers guess at field names
- Filtering logic breaks if fields are named differently
- Tests fail because mock data doesn't match real data structure
**Recommendation:**
Create a formal scenario schema document before implementation:
```ruby
# lib/break_escape/scenario_schema.rb
module BreakEscape
SCENARIO_SCHEMA = {
rooms: {
room_id: {
type: String, # e.g., "room_office"
connections: Hash, # { "north" => "office_2", "south" => "reception" }
locked: Boolean, # optional
lockType: String, # "key", "password", "pin", "biometric", optional
requires: String, # HIDDEN FROM CLIENT - answer/key_id
objects: Array, # Array of object definitions
npcs: Array # Array of NPC definitions
},
objects: {
id: String, # unique within room
name: String, # display name
type: String, # "door", "container", "key", "notes", etc.
takeable: Boolean,
interactable: Boolean, # optional
locked: Boolean, # optional
lockType: String, # optional
requires: String, # HIDDEN - answer/solution
observations: String, # description shown to player
contents: Array, # HIDDEN UNTIL UNLOCKED - nested items
itemsHeld: Array # for NPC objects
},
npcs: {
id: String, # unique NPC identifier
displayName: String, # shown to player
storyPath: String, # path to .ink or .json
npcType: String, # optional: "phone", "person", etc.
itemsHeld: Array # items NPC holds { type, id, name, ... }
}
}
}
end
```
**Action Items:**
- [ ] Create `lib/break_escape/scenario_schema.rb` with complete field definitions
- [ ] Add JSON Schema validator to verify scenarios match spec
- [ ] Document which fields are client-visible in inline comments
- [ ] Generate test fixture scenarios from schema definition
---
## 2. DESIGN ISSUES (High Impact on Success)
### Issue 2.1: `filter_contents_field` Method is Inefficient
**Location:** Phase 2.3, controller filtering
**Problem:**
- Recursively walks entire object tree for each filter operation
- Modifies objects in-place (using `.delete()`), which mutates original in memory
- No memoization of filtered results
- Same room data might be filtered multiple times per request
**Impact:**
- Performance degrades with large scenarios (many rooms/objects)
- Memory pressure from deep copies
- Defensive copying needed to prevent mutations
**Recommendation:**
Create dedicated filtering service class:
```ruby
# app/services/break_escape/data_filter_service.rb
module BreakEscape
class DataFilterService
# Hide sensitive fields during room data exposure
SENSITIVE_FIELDS = ['requires', 'contents'].freeze
def initialize(scenario_data)
@scenario_data = scenario_data
@filter_cache = {}
end
# Filter object recursively, returning new object (immutable)
def filter_object(obj, hide_fields: SENSITIVE_FIELDS)
cache_key = "#{obj.object_id}_#{hide_fields.join(',')}"
return @filter_cache[cache_key] if @filter_cache.key?(cache_key)
result = case obj
when Hash
obj.each_with_object({}) do |(key, value), acc|
next if hide_fields.include?(key)
acc[key] = filter_object(value, hide_fields: hide_fields)
end
when Array
obj.map { |item| filter_object(item, hide_fields: hide_fields) }
else
obj
end
@filter_cache[cache_key] = result
end
# Filter room data (remove 'requires' but keep 'lockType', 'locked')
def filtered_room_data(room_id)
room = @scenario_data['rooms']&.[](room_id)
return nil unless room
filter_object(room, hide_fields: ['requires', 'contents'])
end
# Filter contents of a specific container (remove nested 'requires')
def filtered_container_contents(container_id)
container = find_container_in_scenario(container_id)
return nil unless container
(container['contents'] || []).map do |item|
filter_object(item, hide_fields: ['requires', 'contents'])
end
end
end
end
```
**Action Items:**
- [ ] Create `app/services/break_escape/data_filter_service.rb`
- [ ] Add caching strategy for repeated filters
- [ ] Add unit tests for filter performance
- [ ] Update controller to use service instead of inline filtering
- [ ] Add instrumentation to measure filter overhead
---
### Issue 2.2: Validation Helper Methods Are Complex and Scattered
**Location:** Phase 4, Inventory validation, multiple helper methods
**Problem:**
- `find_item_in_scenario`, `find_item_container`, `find_item_room`, `find_npc_holding_item` all do similar traversals
- Each method might have different traversal logic (depth-first vs breadth-first)
- No shared traversal pattern
- Code duplication increases bug surface area
**Impact:**
- Bugs in one finder don't get fixed in others
- Hard to maintain consistency
- Performance suffers from repeated traversals
**Recommendation:**
Create a scenario traversal service:
```ruby
# app/services/break_escape/scenario_traverser.rb
module BreakEscape
class ScenarioTraverser
def initialize(scenario_data)
@scenario_data = scenario_data
@index = {} # Cache of item/container/NPC lookups
build_index
end
# Find item by type and ID anywhere in scenario
def find_item(item_type, item_id)
@index[:items]["#{item_type}:#{item_id}"]
end
# Find which container holds an item
def find_containing_container(item_type, item_id)
item = find_item(item_type, item_id)
return nil unless item
@index[:container_map]["#{item_type}:#{item_id}"]
end
# Find which room contains an item
def find_containing_room(item_type, item_id)
item = find_item(item_type, item_id)
return nil unless item
@index[:room_map]["#{item_type}:#{item_id}"]
end
# Find NPC holding an item
def find_npc_holding(item_type, item_id)
@index[:npc_holdings]["#{item_type}:#{item_id}"]
end
# Find container by ID
def find_container(container_id)
@index[:containers][container_id]
end
# Find NPC by ID
def find_npc(npc_id)
@index[:npcs][npc_id]
end
private
def build_index
@index = {
items: {}, # "type:id" => item_data
containers: {}, # "container_id" => container_data
npcs: {}, # "npc_id" => npc_data
container_map: {}, # "type:id" => container_id (what container holds this item)
room_map: {}, # "type:id" => room_id (what room contains this item)
npc_holdings: {} # "type:id" => npc_id (what NPC holds this)
}
@scenario_data['rooms']&.each do |room_id, room_data|
index_room_objects(room_id, room_data['objects'], container_id: nil)
index_npcs(room_id, room_data['npcs'])
end
end
def index_room_objects(room_id, objects, container_id: nil)
return unless objects
objects.each do |obj|
# Index the object itself
key = "#{obj['type']}:#{obj['id']}"
@index[:items][key] = obj
@index[:room_map][key] = room_id
# If inside a container, record that relationship
@index[:container_map][key] = container_id if container_id
# If this object is a container, index it
if obj['contents'].present?
@index[:containers][obj['id']] = obj
# Recursively index nested contents
index_room_objects(room_id, obj['contents'], container_id: obj['id'])
end
end
end
def index_npcs(room_id, npcs)
return unless npcs
npcs.each do |npc|
@index[:npcs][npc['id']] = npc
# Index items held by NPC
next unless npc['itemsHeld'].present?
npc['itemsHeld'].each do |item|
key = "#{item['type']}:#{item['id']}"
@index[:npc_holdings][key] = npc['id']
end
end
end
end
end
```
**Action Items:**
- [ ] Create `app/services/break_escape/scenario_traverser.rb`
- [ ] Add unit tests for each finder method
- [ ] Update inventory validation to use traverser
- [ ] Benchmark performance improvement (should eliminate duplicate traversals)
- [ ] Add memoization for scenario_traverser instances per game
---
### Issue 2.3: Missing Container Nested Contents Handling
**Location:** Phase 3, Container endpoint
**Problem:**
- Plan says "containers can be nested" (containers inside containers)
- But `filter_contents_field` method is incomplete (pseudocode)
- No handling for deeply nested containers
- Unclear: Can items inside nested containers be collected?
**Impact:**
- Nested container structure breaks at runtime
- Items in nested containers unreachable
- Filtering logic incomplete
**Recommendation:**
Extend container traversal to handle nesting:
```ruby
# In ScenarioTraverser class:
def container_chain(container_id)
# Returns all containers in nesting order
# e.g., [safe_id, inner_box_id, final_container_id]
chain = []
current_id = container_id
while current_id
container = @index[:containers][current_id]
return nil unless container # Safety check: container not found
chain.unshift(current_id)
current_id = @index[:container_map]["#{container['type']}:#{current_id}"]
end
chain
end
def is_container_accessible?(container_id, unlocked_objects)
# Container only accessible if ALL parent containers in chain are unlocked
chain = container_chain(container_id)
return false unless chain
chain.all? { |cid| unlocked_objects.include?(cid) }
end
```
**Action Items:**
- [ ] Add container chain tracking to traverser
- [ ] Add accessibility check for nested containers
- [ ] Update container endpoint to validate entire chain is unlocked
- [ ] Add tests for nested container scenarios
- [ ] Document nesting rules in scenario schema
---
## 3. TESTING & VALIDATION ISSUES
### Issue 3.1: Insufficient Test Coverage Specification
**Location:** Phase 9, Testing & Validation (very high level)
**Problem:**
- Phase 9 lists test names but no test cases/assertions
- No coverage requirements specified
- No "happy path" vs "unhappy path" breakdown
- No edge case identification
**Impact:**
- Tests will be incomplete or miss critical paths
- Integration test failure doesn't help developers debug
- No regression test foundation for future features
**Recommendation:**
Create detailed test matrix:
```ruby
# test/integration/client_action_validation_test.rb
describe "Client Action Validation" do
describe "Room Access Control" do
test "player can access starting room without unlock" do
# Game created, player_state['unlockedRooms'] contains start room
# Request room endpoint for startRoom → 200 OK
end
test "player cannot access locked room without unlock" do
# Request room endpoint for locked room → 403 Forbidden
end
test "player can access room after unlock" do
# Unlock room → Update unlockedRooms
# Request room endpoint → 200 OK
end
end
describe "Data Filtering" do
test "room response does not include 'requires' field" do
# Check all objects in room response
# Assert no 'requires' key exists at any level
end
test "room response does not include 'contents' of locked containers" do
# Check all containers in room response
# Assert 'contents' key does not exist if locked
end
test "room response includes 'lockType' and 'locked' status" do
# Verify these fields present for client UI
end
end
describe "Container Lazy Loading" do
test "container contents not accessible before unlock" do
# GET /container/safe_1 before unlock → 403 Forbidden
end
test "container contents accessible after unlock" do
# Unlock container → 200 OK with contents
end
test "nested containers require full chain unlock" do
# Scenario with: safe → inner_box → item
# Each must be unlocked separately
end
end
describe "Inventory Validation" do
test "item cannot be collected from locked room" do
# POST /inventory { add, item } for item in locked room
# → Error: "Room not unlocked"
end
test "item cannot be collected from locked container" do
# Item in locked container
# → Error: "Container not unlocked"
end
test "NPC-held item cannot be collected before encounter" do
# NPC in room not visited yet
# → Error: "NPC not encountered"
end
test "item can be collected from accessible location" do
# Room unlocked, container unlocked or not in container, NPC encountered
# → Success, inventory updated
end
end
describe "NPC Encounter Tracking" do
test "NPC added to encounteredNPCs when room loaded" do
# Load room with NPC
# Check player_state['encounteredNPCs'] includes NPC
end
test "NPC not in encounteredNPCs if room never visited" do
# Scenario with NPC in locked room
# encounteredNPCs should not include that NPC
end
end
describe "Unlock Tracking" do
test "room added to unlockedRooms after successful unlock" do
# Valid unlock attempt
# unlockedRooms includes the room
end
test "container added to unlockedObjects after unlock" do
# Valid container unlock
# unlockedObjects includes the container
end
end
describe "Edge Cases" do
test "simultaneous unlock attempts don't duplicate" do
# Two concurrent unlock requests for same door
# unlockedRooms includes room only once
end
test "inventory doesn't accept duplicates" do
# Collect item, remove it, collect again
# Inventory correctly updated each time
end
test "player state persists across requests" do
# Create game, unlock room, fetch game
# player_state still contains unlock
end
end
end
```
**Action Items:**
- [ ] Create comprehensive test matrix before implementation
- [ ] Assign each test to specific phase where it should pass
- [ ] Define acceptance criteria for each test
- [ ] Create fixtures (scenarios) for each test case
- [ ] Add performance benchmarks (response time, memory usage)
---
### Issue 3.2: Missing Rollback & Recovery Strategy
**Location:** All phases, implicit assumption things work
**Problem:**
- Plan doesn't mention what happens if a phase fails
- No specification of backwards compatibility
- No plan for debugging failed unlocks
- No admin tools for resetting game state
**Impact:**
- If implementation partially succeeds, hard to debug
- Players can't recover from bugs
- No way to fix inconsistent state in production
**Recommendation:**
Add Phase 11: Rollback & Recovery Tools
```ruby
# app/services/break_escape/game_state_manager.rb
module BreakEscape
class GameStateManager
def initialize(game)
@game = game
end
# Snapshot current state for rollback
def create_snapshot
{
timestamp: Time.current,
snapshot_id: SecureRandom.uuid,
player_state: @game.player_state.deep_dup
}
end
# Restore from snapshot
def restore_snapshot(snapshot_id)
# Find and restore snapshot
snapshot = @game.snapshots.find_by(snapshot_id: snapshot_id)
raise "Snapshot not found: #{snapshot_id}" unless snapshot
@game.player_state = snapshot.data['player_state']
@game.save!
end
# Reset game to initial state
def reset_to_start
@game.initialize_player_state!
end
# Validate state consistency
def validate_consistency
errors = []
# Check all unlockedRooms exist in scenario
@game.player_state['unlockedRooms'].each do |room_id|
unless @game.scenario_data['rooms'].key?(room_id)
errors << "Room #{room_id} in unlockedRooms but not in scenario"
end
end
# Check all inventory items exist in scenario or are starting items
@game.player_state['inventory'].each do |item|
# Item should be findable in scenario
# OR be a starting item
end
# Check NPC encounters are in scenario
# Check container unlocks are in scenario
errors
end
end
end
```
**Action Items:**
- [ ] Add Phase 11: Recovery & Debugging Tools to plan
- [ ] Create `app/services/break_escape/game_state_manager.rb`
- [ ] Add state validation checks before responding to client requests
- [ ] Create admin endpoint to inspect game state
- [ ] Create admin endpoint to reset specific games
- [ ] Add logging for state changes
---
## 4. IMPLEMENTATION SEQUENCING ISSUES
### Issue 4.1: Wrong Phase Dependencies
**Location:** Implementation TODO List, Phase ordering
**Problem:**
- Phase 6 (Unlock Tracking) depends on Phase 4 (Inventory Validation)
- But plan lists them in sequence as if Phase 6 is independent
- Phase 7 (Sync State Validation) depends on Phase 1 (Initialization)
- No explicit dependency graph
**Impact:**
- Developer might implement phases out of order and get stuck
- Unclear which tests should pass after each phase
**Recommendation:**
Create explicit dependency diagram:
```
Phase 1: Data Model & Initialization
├─ Phase 2: Data Filtering
│ ├─ Phase 3: Container Lazy-Loading
│ └─ Phase 6: Unlock Validation & Tracking
├─ Phase 4: Inventory Validation
│ └─ Phase 5: NPC Encounter Tracking
└─ Phase 7: Sync State Validation
Phase 8: Routing (can start once controllers exist)
Phase 9: Testing (runs continuously after each phase)
Phase 10: Client Updates (after server complete)
Phase 11: Recovery Tools (after Phase 1-7 complete)
```
**Recommended Implementation Order:**
1. Phase 1 ✓ (prerequisite for everything)
2. Phase 2 ✓ (data filtering needed by Phase 6)
3. Phase 8 ✓ (add routes as you go)
4. Phase 3 ✓ (container endpoint)
5. Phase 6 ✓ (unlock tracking)
6. Phase 5 ✓ (NPC encounter tracking)
7. Phase 4 ✓ (inventory validation)
8. Phase 7 ✓ (sync state validation)
9. Phase 11 ✓ (recovery tools)
10. Phase 9 ✓ (comprehensive testing)
11. Phase 10 (client updates)
**Action Items:**
- [ ] Create dependency graph in planning document
- [ ] Identify any additional dependencies not listed
- [ ] Reorder phases based on dependencies
- [ ] Create "ready to start" checklist for each phase
---
### Issue 4.2: Client Updates Are Last (Should Be Earlier for Testing)
**Location:** Phase 10, Client Updates
**Problem:**
- Client updates are Phase 10, last
- But without client changes, server endpoints can't be tested
- Manual integration testing required with curl/Postman before client ready
- Creates bottleneck: server done but can't validate until client updated
**Impact:**
- Hard to find bugs (can't end-to-end test without client)
- Delay in finding integration issues
- Client developers blocked waiting for server
**Recommendation:**
Split client updates into two phases:
```markdown
## Phase 8B: Client Integration (After Phase 3, Before Phase 4)
- Create stub client endpoints that call new server endpoints
- Add error handling for new response format
- Test with curl/Postman until working
- Update game.js to use scenario_map instead of full scenario
- Update room endpoint calls with filtering awareness
## Phase 10: Client Polish (After Phase 9)
- Add UI for error states
- Add loading indicators
- Optimize performance
- Add user feedback for validation errors
```
**Action Items:**
- [ ] Create Phase 8B: Client Integration
- [ ] Add stub client implementation before server complete
- [ ] Create curl/Postman test suite for each endpoint
- [ ] Parallel development: Server Phase 4-7 while Client Phase 8B ongoing
- [ ] Move UI polish to Phase 10 (after all features working)
---
## 5. DOCUMENTATION & COMMUNICATION GAPS
### Issue 5.1: No Specification of Error Response Formats
**Location:** Error Handling section, incomplete examples
**Problem:**
- Plan shows a few error examples but inconsistent format
- "Missing room_id parameter" vs "Container not unlocked" use different patterns
- No specification of HTTP status codes for different error types
- No error code system for client to handle programmatically
**Impact:**
- Client code might not correctly handle errors
- Hard to test all error paths
- Developers guess at response format
**Recommendation:**
Formalize error response schema:
```ruby
# Error Response Format:
{
success: false,
error: {
code: "ERROR_CODE", # Machine-readable error classification
message: "Human readable", # User-facing message
details: {} # Additional context (optional)
}
}
# HTTP Status Codes:
# 400 - Bad Request: Missing/invalid parameters (developer error)
# 403 - Forbidden: Permission denied (gameplay state issue)
# 404 - Not Found: Resource not found
# 422 - Unprocessable Entity: Validation failed (gameplay logic)
# 500 - Internal Server Error: Unexpected error (bug)
# Error Codes:
MISSING_PARAMETER = "MISSING_PARAMETER"
ROOM_NOT_FOUND = "ROOM_NOT_FOUND"
ROOM_NOT_ACCESSIBLE = "ROOM_NOT_ACCESSIBLE"
CONTAINER_NOT_FOUND = "CONTAINER_NOT_FOUND"
CONTAINER_NOT_UNLOCKED = "CONTAINER_NOT_UNLOCKED"
ITEM_NOT_FOUND = "ITEM_NOT_FOUND"
ITEM_NOT_TAKEABLE = "ITEM_NOT_TAKEABLE"
ITEM_LOCATION_LOCKED = "ITEM_LOCATION_LOCKED" # Room or container
NPC_NOT_ENCOUNTERED = "NPC_NOT_ENCOUNTERED"
INVALID_UNLOCK_ATTEMPT = "INVALID_UNLOCK_ATTEMPT"
```
**Action Items:**
- [ ] Create error code constants in `lib/break_escape/error_codes.rb`
- [ ] Create error response builder method in ApplicationController
- [ ] Update all error responses to use consistent format
- [ ] Document error codes in API documentation
- [ ] Create error handling guide for client developers
---
### Issue 5.2: No API Documentation or Examples
**Location:** Implementation plan, missing entirely
**Problem:**
- Plan doesn't specify HTTP verbs, parameter names exactly as they'll be
- Plan shows pseudocode but not exact API contracts
- Client developers won't know what to call
- No spec for developers following later
**Impact:**
- Integration painful and error-prone
- Clients might call endpoints incorrectly
- Hard to onboard new developers
**Recommendation:**
Create OpenAPI/Swagger spec:
```yaml
# Break Escape API - Validation & Filtering Endpoints
/games/{id}/room/{room_id}:
get:
summary: Load room data with filtering
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: room_id
in: path
required: true
schema:
type: string
responses:
200:
description: Room data (requires and contents fields hidden)
content:
application/json:
schema:
type: object
properties:
room_id: { type: string }
room: { type: object }
403:
description: Room not accessible
404:
description: Room not found
/games/{id}/container/{container_id}:
get:
summary: Load locked container contents
parameters:
- name: id
in: path
required: true
- name: container_id
in: path
required: true
responses:
200:
description: Container contents with filtering applied
403:
description: Container not unlocked
404:
description: Container not found
/games/{id}/scenario_map:
get:
summary: Get minimal scenario layout (no secrets exposed)
responses:
200:
description: Room connections and accessibility
/games/{id}/unlock:
post:
parameters:
- name: targetType
in: body
required: true
schema:
enum: [door, object]
- name: targetId
in: body
required: true
- name: attempt
in: body
required: true
- name: method
in: body
required: true
responses:
200:
description: Unlock successful
422:
description: Invalid attempt
/games/{id}/inventory:
post:
parameters:
- name: actionType
in: body
enum: [add, remove]
- name: item
in: body
schema:
type: object
required: [type, id]
responses:
200:
description: Inventory updated
422:
description: Item not collectible
```
**Action Items:**
- [ ] Create OpenAPI/Swagger spec in `docs/api.openapi.yaml`
- [ ] Generate interactive API docs from spec
- [ ] Add code examples for each endpoint in Ruby/JavaScript
- [ ] Create API testing guide (curl examples)
- [ ] Update README with API overview
---
## 6. PERFORMANCE & SCALABILITY ISSUES
### Issue 6.1: No Consideration of Large Scenarios
**Location:** All phases, implicit assumption scenarios are small
**Problem:**
- Plan uses linear search through all rooms/objects
- No pagination or lazy-loading of scenario data
- Scenarios could have thousands of objects
- Every room request triggers full traversal
**Impact:**
- Slow response times for large scenarios
- High CPU/memory on server
- Client perceived as sluggish
**Recommendation:**
Add performance considerations to Phase 2:
```ruby
# app/services/break_escape/scenario_cache.rb
module BreakEscape
class ScenarioCache
# Cache filtered room data for performance
def initialize(game)
@game = game
@room_cache = {}
@filter_service = DataFilterService.new(game.scenario_data)
end
def filtered_room(room_id)
@room_cache[room_id] ||= begin
@filter_service.filtered_room_data(room_id)
end
end
def clear_cache
@room_cache.clear
end
def invalidate_room(room_id)
@room_cache.delete(room_id)
end
end
end
# Measure and optimize:
# - Response time: Room endpoint should respond < 100ms
# - Scenario size: Support scenarios up to 100 rooms, 1000 objects
# - Memory: Scenario data should fit in memory (not stream from disk)
```
**Action Items:**
- [ ] Add caching layer for filtered room data
- [ ] Profile room endpoint response time with large scenario
- [ ] Set performance targets: < 100ms per room request
- [ ] Add N+1 query detection for database calls
- [ ] Test with scenarios of varying sizes (10, 100, 1000 objects)
---
### Issue 6.2: Inventory Validation Performance
**Location:** Phase 4, multiple find operations
**Problem:**
- Current approach does 4+ full scenario traversals per inventory add
- `find_item_in_scenario`, `find_item_container`, `find_item_room`, `find_npc_holding_item`
- Each traversal walks entire object tree
**Impact:**
- Inventory operations slow (multiple seconds for large scenario)
- Scales poorly: inventory add time grows linearly with scenario size
**Recommendation:**
Use ScenarioTraverser index (Issue 2.2 already addresses this)
```ruby
# Measurement:
# Current: O(n * m) where n = scenarios traversals, m = objects
# With index: O(1) lookup after O(m) index build
# Improvement: Build index once per game, reuse for all operations
```
**Action Items:**
- [ ] Implement ScenarioTraverser index (see Issue 2.2)
- [ ] Benchmark inventory add: before/after
- [ ] Target: Inventory add < 50ms
- [ ] Add index build time to game initialization
- [ ] Cache traverser instance in @game object
---
## 7. SECURITY REVIEW
### Issue 7.1: Missing Authorization Checks
**Location:** All controller actions
**Problem:**
- Plan shows `authorize @game if defined?(Pundit)` but conditional
- Not all endpoints might have this
- No specification of what authorization means
- Could allow one player to access another player's game
**Impact:**
- Player A could unlock rooms in Player B's game
- Data leak between players
- Game state modification by unauthorized user
**Recommendation:**
Strengthen authorization:
```ruby
# app/policies/break_escape/game_policy.rb
module BreakEscape
class GamePolicy
attr_reader :user, :game
def initialize(user, game)
@user = user
@game = game
end
def show?
game.player == user # Only owner can access
end
def room?
show? # Same auth as show
end
def container?
show?
end
def unlock?
show?
end
def inventory?
show?
end
def sync_state?
show?
end
end
end
# In controller, always authorize:
module BreakEscape
class GamesController < ApplicationController
before_action :set_game, only: [:show, :scenario, :ink, :room, :container, :sync_state, :unlock, :inventory, :scenario_map]
before_action :authorize_game
private
def authorize_game
authorize @game
end
end
end
```
**Action Items:**
- [ ] Create `app/policies/break_escape/game_policy.rb`
- [ ] Make authorization non-conditional (always required)
- [ ] Test authorization on each endpoint
- [ ] Add integration tests for authorization failures
- [ ] Document who can perform each action
---
### Issue 7.2: Data Filtering Completeness Not Verified
**Location:** All filtering methods
**Problem:**
- Plan assumes filter methods catch all instances of hidden fields
- But what if scenario has new field type added later?
- What if nested structure is deeper than expected?
- No way to verify filtering is complete
**Impact:**
- Accidentally expose "requires" field if code changes
- Security regression from future maintenance
**Recommendation:**
Add verification layer:
```ruby
# app/services/break_escape/data_verifier.rb
module BreakEscape
class DataVerifier
FORBIDDEN_FIELDS = ['requires'].freeze
def self.verify_filtered_data(data)
errors = []
verify_recursive(data, errors)
errors
end
private
def self.verify_recursive(obj, errors, path = "root")
case obj
when Hash
obj.each do |key, value|
if FORBIDDEN_FIELDS.include?(key)
errors << "Found forbidden field '#{key}' at #{path}"
end
verify_recursive(value, errors, "#{path}.#{key}")
end
when Array
obj.each_with_index do |item, idx|
verify_recursive(item, errors, "#{path}[#{idx}]")
end
end
end
end
end
# Use in tests:
test "room data never exposes requires field" do
response = get "/games/#{game.id}/room/office"
room_data = JSON.parse(response.body)['room']
errors = DataVerifier.verify_filtered_data(room_data)
assert errors.empty?, "Found forbidden fields: #{errors.join(', ')}"
end
```
**Action Items:**
- [ ] Create `app/services/break_escape/data_verifier.rb`
- [ ] Add verification to all filtered data responses
- [ ] Create test helper to assert no forbidden fields
- [ ] Run verification in tests automatically
- [ ] Add flag to log warnings in development if forbidden fields found
---
## 8. MINOR ISSUES (Nice-to-Have Improvements)
### Issue 8.1: Debugging Visibility
**Problem:**
- No logging of filtering/validation operations
- Hard to debug "why can't player access room?"
- Hard to trace validation decisions
**Recommendation:**
```ruby
class DataFilterService
def filter_object(obj, hide_fields: SENSITIVE_FIELDS)
Rails.logger.debug "[BreakEscape] Filtering object #{obj['id']} with fields: #{hide_fields}"
# ... rest of method
end
end
class Game < ApplicationRecord
def validate_unlock(...)
Rails.logger.info "[BreakEscape] Validating unlock for #{target_id}"
# ... rest of method
end
end
```
**Action Items:**
- [ ] Add structured logging using `Rails.logger.tagged`
- [ ] Create log format consistent with other endpoints
- [ ] Add instrumentation gem for performance monitoring
- [ ] Document logging format for debugging guide
---
### Issue 8.2: Consistency Between Endpoints
**Problem:**
- Room endpoint might filter differently than container endpoint
- Scenario map might expose different fields than room
- No specification that all are consistent
**Recommendation:**
```ruby
# Create shared constants
module BreakEscape
module DataFiltering
# Fields always hidden from client (solutions)
ALWAYS_HIDDEN = ['requires'].freeze
# Fields hidden until unlocked (contents)
HIDDEN_UNTIL_UNLOCKED = ['contents'].freeze
# Fields always visible to client (needed for UI)
ALWAYS_VISIBLE = ['lockType', 'locked', 'observations', 'id', 'name', 'type'].freeze
end
end
```
**Action Items:**
- [ ] Create `lib/break_escape/data_filtering.rb` with shared constants
- [ ] Use constants in all filtering methods
- [ ] Document field visibility policy in comments
- [ ] Add assertions to tests that verify consistency
---
## SUMMARY: Risk & Mitigation Matrix
| Risk | Probability | Impact | Mitigation | Priority |
|------|-------------|--------|-----------|----------|
| Race condition in initialization | High | Medium | Add idempotent pattern (Issue 1.1) | CRITICAL |
| Malformed scenario breaks game | Medium | High | Add validation (Issue 1.2) | CRITICAL |
| Incomplete scenario schema | High | Medium | Create formal schema (Issue 1.3) | CRITICAL |
| Performance degrades with large scenarios | Medium | High | Add caching + traverser index (Issues 6.1, 6.2, 2.1) | HIGH |
| Authorization bypass | Low | Critical | Strengthen auth checks (Issue 7.1) | HIGH |
| Accidentally expose secrets in future | Low | Critical | Add verification layer (Issue 7.2) | HIGH |
| Nested containers break | Medium | Medium | Extend traversal (Issue 2.3) | HIGH |
| Incomplete test coverage | High | Medium | Create test matrix (Issue 3.1) | HIGH |
| Player stuck in failed state | Medium | Medium | Add recovery tools (Issue 3.2) | MEDIUM |
| Wrong phase dependencies | Low | Medium | Create dependency graph (Issue 4.1) | MEDIUM |
| Client blocked on server | Low | Medium | Parallel Phase 8B (Issue 4.2) | MEDIUM |
| Integration testing difficult | Medium | Low | Create curl/Postman suite (Issue 4.2) | MEDIUM |
| API unclear to developers | Low | Low | Create OpenAPI spec (Issue 5.2) | LOW |
| Debugging difficult | Low | Low | Add logging (Issue 8.1) | LOW |
---
## RECOMMENDED PRIORITY: Critical Path First
**Must Fix Before Starting:**
1. Issue 1.1: Race condition handling
2. Issue 1.2: Scenario validation
3. Issue 1.3: Scenario schema documentation
**Must Fix During Design Phase:**
4. Issue 2.1: Efficient filtering service
5. Issue 2.2: Scenario traverser with index
6. Issue 3.1: Comprehensive test matrix
7. Issue 4.1: Dependency graph
**Can Parallelize:**
8. Issue 2.3: Nested container handling (parallel with Phase 3)
9. Issue 4.2: Phase 8B client integration (parallel with server)
10. Issue 7.1: Authorization policy (parallel with controller work)
**Add After MVP:**
11. Issue 3.2: Recovery tools (Phase 11)
12. Issue 6.1: Performance optimization
13. Issue 7.2: Data verification
14. Issue 5.2: OpenAPI documentation
15. Issue 8.1: Enhanced logging
---
## Next Steps
1. **Review with team** - Present this review to development team
2. **Prioritize** - Decide which issues to address immediately vs. defer
3. **Update plan** - Incorporate recommendations into IMPLEMENTATION_PLAN.md
4. **Create PR** - Implement high-priority fixes to game.rb and add data_filter_service.rb
5. **Begin Phase 1** - Start with data model improvements and validation
---
## Appendix: Quick Reference Checklist
### Before Any Code Changes:
- [ ] Create scenario schema document
- [ ] Add scenario data validation
- [ ] Make authorization non-conditional
- [ ] Create dependency graph
### During Implementation:
- [ ] Use DataFilterService for filtering
- [ ] Use ScenarioTraverser for lookups
- [ ] Create test matrix before phase
- [ ] Run DataVerifier on all responses
- [ ] Add error code constants and consistent response format
### Before Calling Complete:
- [ ] Zero authorization bypasses
- [ ] No forbidden fields in any response
- [ ] Performance < 100ms per room request
- [ ] All test matrix items passing
- [ ] Recovery tools available for admins
- [ ] API documentation complete
---
**End of Review**