docs: add project specs and documentation for Modbus relay control
Initialize project documentation structure: - Add CLAUDE.md with development guidelines and architecture principles - Add project constitution (v1.1.0) with hexagonal architecture and SOLID principles - Add MCP server configuration for Context7 integration Feature specification (001-modbus-relay-control): - Complete feature spec for web-based Modbus relay control system - Implementation plan with TDD approach using SQLx for persistence - Type-driven development design for domain types - Technical decisions document (SQLx over rusqlite, SQLite persistence) - Detailed task breakdown (94 tasks across 8 phases) - Specification templates for future features Documentation: - Modbus POE ETH Relay hardware documentation - Modbus Application Protocol specification (PDF) Project uses SQLx for compile-time verified SQL queries, aligned with type-driven development principles.
This commit is contained in:
1031
specs/001-modbus-relay-control/data-model.md
Normal file
1031
specs/001-modbus-relay-control/data-model.md
Normal file
File diff suppressed because it is too large
Load Diff
177
specs/001-modbus-relay-control/decisions.md
Normal file
177
specs/001-modbus-relay-control/decisions.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# Implementation Decisions
|
||||
|
||||
**Date**: 2025-12-28
|
||||
**Feature**: Modbus Relay Control System
|
||||
|
||||
## User Decisions
|
||||
|
||||
### Q1: Communication Pattern
|
||||
**Decision**: HTTP Polling (as specified in spec)
|
||||
**Rationale**: WebSocket would be overkill for this project scale
|
||||
|
||||
### Q2: Frontend Development Approach
|
||||
**Decision**: Develop frontend alongside backend, but API endpoints must be implemented first before corresponding frontend features
|
||||
**Approach**: API-first development - implement and test each endpoint before building UI for it
|
||||
|
||||
### Q3: Hardware Availability
|
||||
**Decision**: Physical hardware available for testing
|
||||
**Details**:
|
||||
- 8-channel Modbus relay device accessible now
|
||||
- IP address: Variable (configurable)
|
||||
- Port: 501 or 502 (confirm in docs: `docs/Modbus_POE_ETH_Relay.md`)
|
||||
- Device will be available during development phase
|
||||
|
||||
### Q4: Relay Label Persistence
|
||||
**Decision**: SQLite database with SQLx
|
||||
**Implementation Priority**:
|
||||
1. **Preferred**: SQLite database with SQLx (compile-time SQL verification, async-native, type-safe)
|
||||
2. **Alternative**: YAML file (read at startup, write on update)
|
||||
|
||||
**Recommendation**: Use SQLite with SQLx for MVP - simpler than managing YAML file updates, good for future features, aligns with type-driven development principles
|
||||
|
||||
### Q5: Error Recovery Strategy
|
||||
**Decision**: Exponential retry with timeout
|
||||
**Strategy**:
|
||||
- When device becomes unhealthy/unavailable: attempt reconnection every 5 seconds
|
||||
- Maximum retry duration: 5 minutes
|
||||
- After 5 minutes: give up and mark device as unhealthy
|
||||
- Resume connection attempts when user makes new API request
|
||||
- Background task monitors connection health
|
||||
|
||||
### Q6: Firmware Version
|
||||
**Decision**: Check docs for availability, hide if unavailable
|
||||
**Behavior**:
|
||||
- If firmware version available via Modbus: Display in health endpoint
|
||||
- If not available: Omit field entirely from health response (not null/empty string)
|
||||
- Action: Verify in `docs/Modbus_POE_ETH_Relay.md`
|
||||
|
||||
### Q7: Deployment Environment
|
||||
**Development**: Thinkpad x220 (NixOS)
|
||||
**Production Backend**: Raspberry Pi 3B+ (available next week) - on same network as relay device
|
||||
**Production Frontend**: Cloudflare Pages (or equivalent static hosting)
|
||||
**Reverse Proxy**: Traefik on Raspberry Pi with Authelia middleware for authentication
|
||||
**Network**: Raspberry Pi on same network as relay device, frontend accesses backend via HTTPS through Traefik
|
||||
|
||||
### Q8: Testing Approach
|
||||
**Decision**: Implement both real hardware tests AND mocks
|
||||
**Rationale**:
|
||||
- Hardware available now for integration testing
|
||||
- Mocks needed for future maintenance (after device shipped)
|
||||
- Mocks enable fast unit tests without hardware dependency
|
||||
- Follows TDD principles with mock-based development
|
||||
|
||||
**Testing Strategy**:
|
||||
1. **Unit Tests**: Use mocks (mockall) - fast, no hardware needed
|
||||
2. **Integration Tests**: Use real hardware - verify actual Modbus communication
|
||||
3. **CI/CD**: Use mocks (hardware not available in CI)
|
||||
4. **Manual Testing**: Use real hardware during development
|
||||
|
||||
## Derived Decisions
|
||||
|
||||
### Deployment Architecture
|
||||
**Decision**: Frontend on Cloudflare Pages, backend on Raspberry Pi behind Traefik reverse proxy
|
||||
**Components**:
|
||||
- **Frontend**: Static Vue 3 app hosted on Cloudflare Pages (fast global CDN delivery)
|
||||
- **Backend**: Rust HTTP API on Raspberry Pi (same local network as Modbus relay device)
|
||||
- **Reverse Proxy**: Traefik on Raspberry Pi providing:
|
||||
- HTTPS termination (TLS certificates)
|
||||
- Authelia middleware for user authentication
|
||||
- Reverse proxy routing to backend HTTP service
|
||||
- **Communication**: Frontend → HTTPS (via Traefik) → Backend → Modbus TCP → Relay Device
|
||||
|
||||
**Rationale**:
|
||||
- Frontend on CDN provides fast page loads from anywhere
|
||||
- Backend must be local to Modbus device (local network communication)
|
||||
- Traefik handles authentication/HTTPS without application-level complexity
|
||||
- Backend runs HTTP internally, Traefik handles TLS termination
|
||||
|
||||
**Security Layers**:
|
||||
1. Authelia authentication at reverse proxy (user login)
|
||||
2. HTTPS encryption for frontend-backend communication
|
||||
3. Unencrypted Modbus TCP on local network only (acceptable for local-only device)
|
||||
|
||||
### Architecture Approach
|
||||
**Decision**: Hexagonal Architecture with trait-based abstraction
|
||||
**Layers**:
|
||||
- **Domain**: Pure business logic (RelayId, RelayState, Relay entity)
|
||||
- **Application**: Use cases (GetRelayStatus, ToggleRelay, BulkControl)
|
||||
- **Infrastructure**: Modbus client implementation + SQLite repository
|
||||
- **Presentation**: HTTP API handlers (Poem)
|
||||
|
||||
### Database Choice
|
||||
**Decision**: SQLite with SQLx for relay labels and configuration
|
||||
|
||||
**Why SQLx over rusqlite**:
|
||||
- **Compile-time SQL verification**: Queries are checked against actual database schema during compilation
|
||||
- **Type safety**: Column types verified to match Rust types at compile time
|
||||
- **Async-native**: Built for tokio async/await (no need for `spawn_blocking` wrappers)
|
||||
- **Type-driven development alignment**: "Parse, don't validate" - SQL errors caught at compile time, not runtime
|
||||
- **Better observability**: Built-in query logging and tracing integration
|
||||
- **Macro-based queries**: `query!` and `query_as!` macros provide ergonomic, safe database access
|
||||
|
||||
**Benefits of SQLite**:
|
||||
- No external dependencies (embedded)
|
||||
- ACID transactions for label updates
|
||||
- Simple schema (one table for relay labels)
|
||||
- Easy to back up (single file)
|
||||
- Works on both NixOS and Raspberry Pi
|
||||
|
||||
**Schema**:
|
||||
```sql
|
||||
CREATE TABLE relay_labels (
|
||||
relay_id INTEGER PRIMARY KEY CHECK(relay_id >= 1 AND relay_id <= 8),
|
||||
label TEXT NOT NULL CHECK(length(label) <= 50)
|
||||
);
|
||||
```
|
||||
|
||||
**Dependencies**:
|
||||
```toml
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
||||
```
|
||||
|
||||
### Modbus Port Discovery
|
||||
**Confirmed from Documentation** (`docs/Modbus_POE_ETH_Relay.md`):
|
||||
- **Modbus RTU over TCP**: Uses TCP server mode, port is configurable (typically 8234 or custom)
|
||||
- **Modbus TCP**: Port automatically changes to **502** when "Modbus TCP protocol" is selected in Advanced Settings
|
||||
- **Recommended**: Use Modbus RTU over TCP (default, simpler configuration)
|
||||
- **Device must be configured as**: "Multi-host non-storage type" gateway (CRITICAL - storage type sends spurious queries)
|
||||
|
||||
### Firmware Version Availability
|
||||
**Confirmed from Documentation** (`docs/Modbus_POE_ETH_Relay.md:417-442`):
|
||||
- **Available**: YES - Firmware version can be read via Modbus function code 0x03
|
||||
- **Register Address**: 0x8000 (Read Holding Register)
|
||||
- **Command**: `01 03 80 00 00 01 AD CA`
|
||||
- **Response Format**: 2-byte value, convert to decimal and divide by 100 (e.g., 0x00C8 = 200 = v2.00)
|
||||
- **Implementation**: Read once at startup and cache, update on successful reconnection
|
||||
|
||||
### Connection Management
|
||||
**Decision**: Background connection health monitor
|
||||
**Behavior**:
|
||||
- Monitor task checks connection every 5 seconds
|
||||
- On failure: retry with exponential backoff (max 5 seconds interval)
|
||||
- After 5 minutes of failures: mark unhealthy, stop retrying
|
||||
- On new API request: resume connection attempts
|
||||
- On successful reconnection: reset retry counter, mark healthy
|
||||
|
||||
### Frontend Technology Stack
|
||||
**Decision**: Vue 3 + TypeScript + Vite
|
||||
**Components**:
|
||||
- OpenAPI TypeScript client generation (type-safe API calls)
|
||||
- HTTP polling with `setInterval` (2-second intervals)
|
||||
- Reactive state management (ref/reactive, no Pinia needed for this simple app)
|
||||
- UI library: TBD (Nuxt UI, Vuetify, or custom - decide during frontend implementation)
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ Verify Modbus port in documentation
|
||||
2. ✅ Design architecture approaches (minimal, clean, pragmatic)
|
||||
3. ✅ Select approach with user
|
||||
4. ✅ Create detailed implementation plan
|
||||
5. ✅ Begin TDD implementation
|
||||
|
||||
## Notes
|
||||
|
||||
- User has hardware access now, but device will ship after first version
|
||||
- Mocks are critical for long-term maintainability
|
||||
- SQLite preferred over YAML for runtime updates
|
||||
- Connection retry strategy balances responsiveness with resource usage
|
||||
2219
specs/001-modbus-relay-control/plan.md
Normal file
2219
specs/001-modbus-relay-control/plan.md
Normal file
File diff suppressed because it is too large
Load Diff
718
specs/001-modbus-relay-control/research.md
Normal file
718
specs/001-modbus-relay-control/research.md
Normal file
@@ -0,0 +1,718 @@
|
||||
# Research Document: Modbus Relay Control System
|
||||
|
||||
**Created**: 2025-12-28
|
||||
**Feature**: [spec.md](./spec.md)
|
||||
**Status**: Complete
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Executive Summary](#executive-summary)
|
||||
2. [Tokio-Modbus Research](#tokio-modbus-research)
|
||||
3. [WebSocket vs HTTP Polling](#websocket-vs-http-polling)
|
||||
4. [Existing Codebase Patterns](#existing-codebase-patterns)
|
||||
5. [Integration Recommendations](#integration-recommendations)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
### Key Decisions
|
||||
|
||||
| Decision Area | Recommendation | Rationale |
|
||||
|---------------------------|--------------------------------------|---------------------------------------------------------|
|
||||
| **Modbus Library** | tokio-modbus 0.17.0 | Native async/await, production-ready, good testability |
|
||||
| **Communication Pattern** | HTTP Polling (as in spec) | Simpler, reliable, adequate for 10 users @ 2s intervals |
|
||||
| **Connection Management** | Arc<Mutex<Context>> for MVP | Single device, simple, can upgrade later if needed |
|
||||
| **Retry Strategy** | Simple retry-once helper | Matches FR-007 requirement |
|
||||
| **Testing Approach** | Trait-based abstraction with mockall | Enables >90% coverage without hardware |
|
||||
|
||||
### User Input Analysis
|
||||
|
||||
**User requested**: "Use tokio-modbus crate, poem-openapi for REST API, Vue.js with WebSocket for real-time updates"
|
||||
|
||||
**Findings**:
|
||||
- ✅ tokio-modbus 0.17.0: Excellent choice, validated by research
|
||||
- ✅ poem-openapi: Already in use, working well
|
||||
- ⚠️ **WebSocket vs HTTP Polling**: Spec says HTTP polling (FR-028). WebSocket adds 43x complexity for negligible benefit at this scale.
|
||||
|
||||
**RECOMMENDATION**: Maintain HTTP polling as specified. WebSocket complexity not justified for 10 concurrent users with 2-second update intervals.
|
||||
|
||||
### Deployment Architecture
|
||||
|
||||
**User clarification (2025-12-29)**: Frontend on Cloudflare Pages, backend on Raspberry Pi behind Traefik with Authelia
|
||||
|
||||
**Architecture**:
|
||||
- **Frontend**: Cloudflare Pages (Vue 3 static build) - global CDN delivery
|
||||
- **Backend**: Raspberry Pi HTTP API (same local network as Modbus device)
|
||||
- **Reverse Proxy**: Traefik on Raspberry Pi
|
||||
- HTTPS termination (TLS certificates)
|
||||
- Authelia middleware for authentication
|
||||
- Routes frontend requests to backend HTTP service
|
||||
- **Communication Flow**:
|
||||
- Frontend (CDN) → HTTPS → Traefik (HTTPS termination + auth) → Backend (HTTP) → Modbus TCP → Device
|
||||
|
||||
**Security**:
|
||||
- Frontend-Backend: HTTPS via Traefik (encrypted, authenticated)
|
||||
- Backend-Device: Modbus TCP on local network (unencrypted, local only)
|
||||
|
||||
---
|
||||
|
||||
## Tokio-Modbus Research
|
||||
|
||||
### Decision: Recommended Patterns
|
||||
|
||||
**Primary Recommendation**: Use tokio-modbus 0.17.0 with a custom trait-based abstraction layer (`RelayController` trait) for testability. Implement connection management using Arc<Mutex<Context>> for MVP.
|
||||
|
||||
### Technical Details
|
||||
|
||||
**Version**: tokio-modbus 0.17.0 (latest stable, released 2025-10-22)
|
||||
|
||||
**Protocol**: Modbus RTU over TCP (NOT Modbus TCP)
|
||||
- Hardware uses RTU protocol tunneled over TCP
|
||||
- Includes CRC16 validation
|
||||
- Different from native Modbus TCP (no CRC, different framing)
|
||||
|
||||
**Connection Strategy**:
|
||||
- Shared `Arc<Mutex<Context>>` for simplicity
|
||||
- Single persistent connection (only one device)
|
||||
- Can migrate to dedicated async task pattern if reconnection logic needed
|
||||
|
||||
**Timeout Handling**:
|
||||
- Wrap all operations with `tokio::time::timeout(Duration::from_secs(3), ...)`
|
||||
- **CRITICAL**: tokio-modbus has NO built-in timeouts
|
||||
|
||||
**Retry Logic**:
|
||||
- Implement simple retry-once helper per FR-007
|
||||
- Matches specification requirement
|
||||
|
||||
**Testing**:
|
||||
- Use `mockall` crate with `async-trait` for unit testing
|
||||
- Trait abstraction enables testing without hardware
|
||||
- Supports >90% test coverage target (NFR-013)
|
||||
|
||||
### Critical Gotchas
|
||||
|
||||
1. **Device Gateway Configuration**: Hardware MUST be set to "Multi-host non-storage type" - default storage type sends spurious queries causing failures
|
||||
|
||||
2. **No Built-in Timeouts**: tokio-modbus has NO automatic timeouts - must wrap every operation with `tokio::time::timeout`
|
||||
|
||||
3. **RTU vs TCP Confusion**: Device uses Modbus RTU protocol over TCP (with CRC), not native Modbus TCP protocol
|
||||
|
||||
4. **Address Indexing**: Relays labeled 1-8, but Modbus addresses are 0-7 (use newtype pattern with conversion methods)
|
||||
|
||||
5. **Nested Result Handling**: Returns `Result<Result<T, Exception>, std::io::Error>` - must handle both layers (use `???` triple-question-mark pattern)
|
||||
|
||||
6. **Concurrent Access**: Context is not thread-safe - requires `Arc<Mutex>` or dedicated task serialization
|
||||
|
||||
### Code Examples
|
||||
|
||||
**Basic Connection Setup**:
|
||||
```rust
|
||||
use tokio_modbus::prelude::*;
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
// Connect to device
|
||||
let socket_addr = "192.168.1.200:8234".parse()?;
|
||||
let mut ctx = tcp::connect(socket_addr).await?;
|
||||
|
||||
// Set slave ID (unit identifier)
|
||||
ctx.set_slave(Slave(0x01));
|
||||
|
||||
// Read all 8 relay states with timeout
|
||||
let states = timeout(
|
||||
Duration::from_secs(3),
|
||||
ctx.read_coils(0x0000, 8)
|
||||
).await???; // Triple-? handles timeout + transport + exception errors
|
||||
```
|
||||
|
||||
**Toggle Relay with Retry**:
|
||||
```rust
|
||||
async fn toggle_relay(
|
||||
ctx: &mut Context,
|
||||
relay_id: u8, // 1-8
|
||||
) -> Result<(), RelayError> {
|
||||
let addr = (relay_id - 1) as u16; // Convert to 0-7
|
||||
|
||||
// Read current state
|
||||
let states = timeout(Duration::from_secs(3), ctx.read_coils(addr, 1))
|
||||
.await???;
|
||||
let current = states[0];
|
||||
|
||||
// Write opposite state with retry
|
||||
let new_state = !current;
|
||||
let write_op = || async {
|
||||
timeout(Duration::from_secs(3), ctx.write_single_coil(addr, new_state))
|
||||
.await
|
||||
};
|
||||
|
||||
// Retry once on failure (FR-007)
|
||||
match write_op().await {
|
||||
Ok(Ok(Ok(()))) => Ok(()),
|
||||
Err(_) | Ok(Err(_)) | Ok(Ok(Err(_))) => {
|
||||
tracing::warn!("Write failed, retrying");
|
||||
write_op().await???
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Trait-Based Abstraction for Testing**:
|
||||
```rust
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait RelayController: Send + Sync {
|
||||
async fn read_all_states(&mut self) -> Result<Vec<bool>, RelayError>;
|
||||
async fn write_state(&mut self, relay_id: RelayId, state: RelayState) -> Result<(), RelayError>;
|
||||
}
|
||||
|
||||
// Real implementation with tokio-modbus
|
||||
pub struct ModbusRelayController {
|
||||
ctx: Arc<Mutex<Context>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayController for ModbusRelayController {
|
||||
async fn read_all_states(&mut self) -> Result<Vec<bool>, RelayError> {
|
||||
let mut ctx = self.ctx.lock().await;
|
||||
timeout(Duration::from_secs(3), ctx.read_coils(0, 8))
|
||||
.await
|
||||
.map_err(|_| RelayError::Timeout)?
|
||||
.map_err(RelayError::Transport)?
|
||||
.map_err(RelayError::Exception)
|
||||
}
|
||||
// ... other methods
|
||||
}
|
||||
|
||||
// Mock for testing (using mockall)
|
||||
mock! {
|
||||
pub RelayController {}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayController for RelayController {
|
||||
async fn read_all_states(&mut self) -> Result<Vec<bool>, RelayError>;
|
||||
async fn write_state(&mut self, relay_id: RelayId, state: RelayState) -> Result<(), RelayError>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Alternatives Considered
|
||||
|
||||
1. **modbus-robust**: Provides auto-reconnection but lacks retry logic and timeouts - insufficient for production
|
||||
2. **bb8 connection pool**: Overkill for single-device scenario, adds unnecessary complexity
|
||||
3. **Synchronous modbus-rs**: Would block Tokio threads, poor scalability for concurrent users
|
||||
4. **Custom Modbus implementation**: Reinventing wheel, error-prone, significant development time
|
||||
|
||||
### Resources
|
||||
|
||||
- [GitHub - slowtec/tokio-modbus](https://github.com/slowtec/tokio-modbus)
|
||||
- [tokio-modbus on docs.rs](https://docs.rs/tokio-modbus/)
|
||||
- [Context7 MCP: `/slowtec/tokio-modbus`](mcp://context7/slowtec/tokio-modbus)
|
||||
- [Context7 MCP: `/websites/rs_tokio-modbus_0_16_3_tokio_modbus`](mcp://context7/websites/rs_tokio-modbus_0_16_3_tokio_modbus)
|
||||
|
||||
---
|
||||
|
||||
## WebSocket vs HTTP Polling
|
||||
|
||||
### Recommendation: HTTP Polling (as specified)
|
||||
|
||||
The specification's decision to use HTTP polling is technically sound. **HTTP polling is the better choice** for this specific use case.
|
||||
|
||||
### Performance at Your Scale (10 users, 2-second intervals)
|
||||
|
||||
**Bandwidth Comparison:**
|
||||
- HTTP Polling: ~20 Kbps (10 users × 0.5 req/sec × 500 bytes × 8)
|
||||
- WebSocket: ~2.4 Kbps sustained
|
||||
- **Difference: 17.6 Kbps** - negligible on any modern network
|
||||
|
||||
**Server Load:**
|
||||
- HTTP Polling: 5 requests/second system-wide (trivial)
|
||||
- WebSocket: 10 persistent connections (~80-160 KB memory)
|
||||
- **Verdict: Both are trivial at this scale**
|
||||
|
||||
### Implementation Complexity
|
||||
|
||||
**HTTP Polling:**
|
||||
- Backend: 0 lines (reuse existing `GET /api/relays`)
|
||||
- Frontend: ~10 lines (simple setInterval)
|
||||
- **Total effort: 15 minutes**
|
||||
|
||||
**WebSocket:**
|
||||
- Backend: ~115 lines (handler + background poller + channel setup)
|
||||
- Frontend: ~135 lines (WebSocket manager + reconnection logic)
|
||||
- Testing: ~180 lines (connection lifecycle + reconnection tests)
|
||||
- **Total effort: 2-3 days + ongoing maintenance**
|
||||
|
||||
**Complexity ratio: 43x more code for WebSocket**
|
||||
|
||||
### Reliability & Error Handling
|
||||
|
||||
**HTTP Polling Advantages:**
|
||||
- Stateless (automatic recovery on next poll)
|
||||
- Standard HTTP error codes
|
||||
- Works everywhere (proxies, firewalls, old browsers)
|
||||
- No connection state management
|
||||
- Simple testing
|
||||
|
||||
**WebSocket Challenges:**
|
||||
- Connection lifecycle management
|
||||
- Exponential backoff reconnection logic
|
||||
- State synchronization on reconnect
|
||||
- Thundering herd problem (all clients reconnect after server restart)
|
||||
- May fail behind corporate proxies (requires fallback to HTTP polling anyway)
|
||||
|
||||
### Decision Matrix
|
||||
|
||||
| Criterion | HTTP Polling | WebSocket | Weight |
|
||||
|-----------|--------------|-----------|--------|
|
||||
| Simplicity | 5 | 2 | 3x |
|
||||
| Reliability | 5 | 3 | 3x |
|
||||
| Testing | 5 | 2 | 2x |
|
||||
| Performance @ 10 users | 4 | 5 | 1x |
|
||||
| Scalability to 100+ | 3 | 5 | 1x |
|
||||
| Architecture fit | 5 | 3 | 2x |
|
||||
|
||||
**Weighted Scores:**
|
||||
- **HTTP Polling: 4.56/5**
|
||||
- **WebSocket: 3.19/5**
|
||||
|
||||
HTTP Polling scores **43% higher** when complexity, reliability, and testing are properly weighted for this project's scale.
|
||||
|
||||
### When WebSocket Makes Sense
|
||||
|
||||
WebSocket advantages manifest at:
|
||||
- **100+ concurrent users** (4x throughput advantage becomes meaningful)
|
||||
- **Sub-second update requirements** (<1 second intervals)
|
||||
- **High-frequency updates** where latency matters
|
||||
- **Bidirectional communication** (chat, gaming, trading systems)
|
||||
|
||||
For relay control with 2-second polling:
|
||||
- Latency: 0-4 seconds (avg 2 sec) - **acceptable for lights/pumps**
|
||||
- Not a real-time critical system (not chat, gaming, or trading)
|
||||
|
||||
### Migration Path (If Needed Later)
|
||||
|
||||
Starting with HTTP polling does NOT prevent WebSocket adoption later:
|
||||
|
||||
1. **Phase 1:** Add `/api/ws` endpoint (non-breaking change)
|
||||
2. **Phase 2:** Progressive enhancement (detect WebSocket support)
|
||||
3. **Phase 3:** Gradual rollout with monitoring
|
||||
|
||||
**Key Point:** HTTP polling provides a baseline. Adding WebSocket later is straightforward, but removing WebSocket complexity is harder.
|
||||
|
||||
### Poem WebSocket Support (For Reference)
|
||||
|
||||
Poem has excellent WebSocket support through `poem::web::websocket`:
|
||||
|
||||
```rust
|
||||
use poem::web::websocket::{WebSocket, Message};
|
||||
|
||||
#[handler]
|
||||
async fn ws_handler(
|
||||
ws: WebSocket,
|
||||
state_tx: Data<&watch::Sender<RelayCollection>>,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(move |socket| async move {
|
||||
let (mut sink, mut stream) = socket.split();
|
||||
let mut rx = state_tx.subscribe();
|
||||
|
||||
// Send initial state
|
||||
let initial = rx.borrow().clone();
|
||||
sink.send(Message::text(serde_json::to_string(&initial)?)).await?;
|
||||
|
||||
// Stream updates
|
||||
while rx.changed().await.is_ok() {
|
||||
let state = rx.borrow().clone();
|
||||
sink.send(Message::text(serde_json::to_string(&state)?)).await?;
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Broadcasting Pattern**: Use `tokio::sync::watch` channel:
|
||||
- Maintains only most recent value (perfect for relay state)
|
||||
- Automatic deduplication of identical states
|
||||
- New connections get immediate state snapshot
|
||||
- Memory-efficient (single state copy)
|
||||
|
||||
### Resources
|
||||
|
||||
- [Poem WebSocket API Documentation](https://docs.rs/poem/latest/poem/web/websocket/)
|
||||
- [HTTP vs WebSockets Performance](https://blog.feathersjs.com/http-vs-websockets-a-performance-comparison-da2533f13a77)
|
||||
- [Tokio Channels Tutorial](https://tokio.rs/tokio/tutorial/channels)
|
||||
|
||||
---
|
||||
|
||||
## Existing Codebase Patterns
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
The current codebase is a well-structured Rust backend API using Poem framework with OpenAPI support, following clean architecture principles.
|
||||
|
||||
**Current Structure**:
|
||||
```
|
||||
src/
|
||||
├── lib.rs - Library entry point, orchestrates application setup
|
||||
├── main.rs - Binary entry point, calls lib::run()
|
||||
├── startup.rs - Application builder, server configuration, route setup
|
||||
├── settings.rs - Configuration from YAML files + environment variables
|
||||
├── telemetry.rs - Logging and tracing setup
|
||||
├── route/ - HTTP endpoint handlers
|
||||
│ ├── mod.rs - API aggregation and OpenAPI tags
|
||||
│ ├── health.rs - Health check endpoints
|
||||
│ └── meta.rs - Application metadata endpoints
|
||||
└── middleware/ - Custom middleware implementations
|
||||
├── mod.rs
|
||||
└── rate_limit.rs - Rate limiting middleware using governor
|
||||
```
|
||||
|
||||
### Key Patterns Discovered
|
||||
|
||||
#### 1. Route Registration Pattern
|
||||
|
||||
**Location**: `src/startup.rs:95-107`
|
||||
|
||||
```rust
|
||||
fn setup_app(settings: &Settings) -> poem::Route {
|
||||
let api_service = OpenApiService::new(
|
||||
Api::from(settings).apis(),
|
||||
settings.application.clone().name,
|
||||
settings.application.clone().version,
|
||||
)
|
||||
.url_prefix("/api");
|
||||
let ui = api_service.swagger_ui();
|
||||
poem::Route::new()
|
||||
.nest("/api", api_service.clone())
|
||||
.nest("/specs", api_service.spec_endpoint_yaml())
|
||||
.nest("/", ui)
|
||||
}
|
||||
```
|
||||
|
||||
**Key Insights**:
|
||||
- OpenAPI service created with all API handlers via `.apis()` tuple
|
||||
- URL prefix `/api` applied to all API routes
|
||||
- Swagger UI automatically mounted at root `/`
|
||||
- OpenAPI spec YAML available at `/specs`
|
||||
|
||||
#### 2. API Handler Organization Pattern
|
||||
|
||||
**Location**: `src/route/mod.rs:14-37`
|
||||
|
||||
```rust
|
||||
#[derive(Tags)]
|
||||
enum ApiCategory {
|
||||
Health,
|
||||
Meta,
|
||||
}
|
||||
|
||||
pub(crate) struct Api {
|
||||
health: health::HealthApi,
|
||||
meta: meta::MetaApi,
|
||||
}
|
||||
|
||||
impl From<&Settings> for Api {
|
||||
fn from(value: &Settings) -> Self {
|
||||
let health = health::HealthApi;
|
||||
let meta = meta::MetaApi::from(&value.application);
|
||||
Self { health, meta }
|
||||
}
|
||||
}
|
||||
|
||||
impl Api {
|
||||
pub fn apis(self) -> (health::HealthApi, meta::MetaApi) {
|
||||
(self.health, self.meta)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Insights**:
|
||||
- `Tags` enum groups APIs into categories for OpenAPI documentation
|
||||
- Aggregator struct (`Api`) holds all API handler instances
|
||||
- Dependency injection via `From<&Settings>` trait
|
||||
- `.apis()` method returns tuple of all handlers
|
||||
|
||||
#### 3. OpenAPI Handler Definition Pattern
|
||||
|
||||
**Location**: `src/route/health.rs:7-29`
|
||||
|
||||
```rust
|
||||
#[derive(ApiResponse)]
|
||||
enum HealthResponse {
|
||||
#[oai(status = 200)]
|
||||
Ok,
|
||||
#[oai(status = 429)]
|
||||
TooManyRequests,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct HealthApi;
|
||||
|
||||
#[OpenApi(tag = "ApiCategory::Health")]
|
||||
impl HealthApi {
|
||||
#[oai(path = "/health", method = "get")]
|
||||
async fn ping(&self) -> HealthResponse {
|
||||
tracing::event!(target: "backend::health", tracing::Level::DEBUG,
|
||||
"Accessing health-check endpoint");
|
||||
HealthResponse::Ok
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Insights**:
|
||||
- Response types are enums with `#[derive(ApiResponse)]`
|
||||
- Each variant maps to HTTP status code via `#[oai(status = N)]`
|
||||
- Handlers use `#[OpenApi(tag = "...")]` for categorization
|
||||
- Type-safe responses at compile time
|
||||
- Tracing at architectural boundaries
|
||||
|
||||
#### 4. JSON Response Pattern with DTOs
|
||||
|
||||
**Location**: `src/route/meta.rs:9-56`
|
||||
|
||||
```rust
|
||||
#[derive(Object, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
struct Meta {
|
||||
version: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
enum MetaResponse {
|
||||
#[oai(status = 200)]
|
||||
Meta(Json<Meta>),
|
||||
#[oai(status = 429)]
|
||||
TooManyRequests,
|
||||
}
|
||||
|
||||
#[OpenApi(tag = "ApiCategory::Meta")]
|
||||
impl MetaApi {
|
||||
#[oai(path = "/meta", method = "get")]
|
||||
async fn meta(&self) -> Result<MetaResponse> {
|
||||
Ok(MetaResponse::Meta(Json(self.into())))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Insights**:
|
||||
- DTOs use `#[derive(Object)]` for OpenAPI schema generation
|
||||
- Response variants can hold `Json<T>` payloads
|
||||
- Handler struct holds state/configuration
|
||||
- Returns `Result<MetaResponse>` for error handling
|
||||
|
||||
#### 5. Middleware Composition Pattern
|
||||
|
||||
**Location**: `src/startup.rs:59-91`
|
||||
|
||||
```rust
|
||||
let app = value
|
||||
.app
|
||||
.with(RateLimit::new(&rate_limit_config))
|
||||
.with(Cors::new())
|
||||
.data(value.settings);
|
||||
```
|
||||
|
||||
**Key Insights**:
|
||||
- Middleware applied via `.with()` method chaining
|
||||
- Order matters: RateLimit → CORS → data injection
|
||||
- Settings injected as shared data via `.data()`
|
||||
- Configuration drives middleware behavior
|
||||
|
||||
#### 6. Configuration Management Pattern
|
||||
|
||||
**Location**: `src/settings.rs:40-62`
|
||||
|
||||
```rust
|
||||
let settings = config::Config::builder()
|
||||
.add_source(config::File::from(settings_directory.join("base.yaml")))
|
||||
.add_source(config::File::from(
|
||||
settings_directory.join(environment_filename),
|
||||
))
|
||||
.add_source(
|
||||
config::Environment::with_prefix("APP")
|
||||
.prefix_separator("__")
|
||||
.separator("__"),
|
||||
)
|
||||
.build()?;
|
||||
```
|
||||
|
||||
**Key Insights**:
|
||||
- Three-tier configuration: base → environment-specific → env vars
|
||||
- Environment detected via `APP_ENVIRONMENT` variable
|
||||
- Environment variables use `APP__` prefix with double underscore separators
|
||||
- Type-safe deserialization
|
||||
|
||||
#### 7. Testing Pattern
|
||||
|
||||
**Location**: `src/route/health.rs:31-38`
|
||||
|
||||
```rust
|
||||
#[tokio::test]
|
||||
async fn health_check_works() {
|
||||
let app = crate::get_test_app();
|
||||
let cli = poem::test::TestClient::new(app);
|
||||
let resp = cli.get("/api/health").send().await;
|
||||
resp.assert_status_is_ok();
|
||||
}
|
||||
```
|
||||
|
||||
**Key Insights**:
|
||||
- Test helper creates full application with random port
|
||||
- `TestClient` provides fluent assertion API
|
||||
- Tests are async with `#[tokio::test]`
|
||||
- Real application used in tests
|
||||
|
||||
### Type System Best Practices
|
||||
|
||||
Current code demonstrates excellent TyDD:
|
||||
- `Environment` enum instead of strings
|
||||
- `RateLimitConfig` newtype instead of raw numbers
|
||||
- `ApiResponse` enums for type-safe HTTP responses
|
||||
|
||||
### Architecture Compliance
|
||||
|
||||
**Current Layers**:
|
||||
1. **Presentation Layer**: `src/route/*` - HTTP adapters
|
||||
2. **Infrastructure Layer**: `src/middleware/*`, `src/startup.rs`, `src/telemetry.rs`
|
||||
|
||||
**Missing Layers** (to be added for Modbus):
|
||||
3. **Domain Layer**: Pure relay logic, no Modbus knowledge
|
||||
4. **Application Layer**: Use cases (get status, toggle)
|
||||
|
||||
---
|
||||
|
||||
## Integration Recommendations
|
||||
|
||||
### Recommended Architecture for Modbus Feature
|
||||
|
||||
Following hexagonal architecture principles from constitution:
|
||||
|
||||
```
|
||||
src/
|
||||
├── domain/
|
||||
│ └── relay/
|
||||
│ ├── mod.rs - Domain types (RelayId, RelayState, Relay)
|
||||
│ ├── relay.rs - Relay entity
|
||||
│ ├── error.rs - Domain errors
|
||||
│ └── repository.rs - RelayRepository trait
|
||||
├── application/
|
||||
│ └── relay/
|
||||
│ ├── mod.rs - Use case exports
|
||||
│ ├── get_status.rs - GetRelayStatus use case
|
||||
│ ├── toggle.rs - ToggleRelay use case
|
||||
│ └── bulk_control.rs - BulkControl use case
|
||||
├── infrastructure/
|
||||
│ └── modbus/
|
||||
│ ├── mod.rs - Modbus exports
|
||||
│ ├── client.rs - ModbusRelayRepository implementation
|
||||
│ ├── config.rs - Modbus configuration
|
||||
│ └── error.rs - Modbus-specific errors
|
||||
└── route/
|
||||
└── relay.rs - HTTP adapter (presentation layer)
|
||||
```
|
||||
|
||||
### Integration Points
|
||||
|
||||
| Component | File | Action |
|
||||
|-----------|------|--------|
|
||||
| **API Category** | `src/route/mod.rs` | Add `Relay` to `ApiCategory` enum |
|
||||
| **API Aggregator** | `src/route/mod.rs` | Add `relay: RelayApi` field to `Api` struct |
|
||||
| **API Tuple** | `src/route/mod.rs` | Add `RelayApi` to `.apis()` return tuple |
|
||||
| **Settings** | `src/settings.rs` | Add `ModbusSettings` struct and `modbus` field |
|
||||
| **Config Files** | `settings/base.yaml` | Add `modbus:` section |
|
||||
| **Shared State** | `src/startup.rs` | Inject `ModbusClient` via `.data()` |
|
||||
| **Dependencies** | `Cargo.toml` | Add `tokio-modbus`, `async-trait`, `mockall` |
|
||||
|
||||
### Example: New Route Handler
|
||||
|
||||
```rust
|
||||
// src/route/relay.rs
|
||||
use poem::Result;
|
||||
use poem_openapi::{ApiResponse, Object, OpenApi, payload::Json, param::Path};
|
||||
use crate::domain::relay::{RelayId, RelayState, Relay};
|
||||
|
||||
#[derive(Object, Serialize, Deserialize)]
|
||||
struct RelayDto {
|
||||
id: u8,
|
||||
state: String, // "on" or "off"
|
||||
label: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
enum RelayResponse {
|
||||
#[oai(status = 200)]
|
||||
Status(Json<RelayDto>),
|
||||
#[oai(status = 400)]
|
||||
BadRequest,
|
||||
#[oai(status = 503)]
|
||||
ServiceUnavailable,
|
||||
}
|
||||
|
||||
#[OpenApi(tag = "ApiCategory::Relay")]
|
||||
impl RelayApi {
|
||||
#[oai(path = "/relays/:id", method = "get")]
|
||||
async fn get_status(&self, id: Path<u8>) -> Result<RelayResponse> {
|
||||
let relay_id = RelayId::new(id.0)
|
||||
.map_err(|_| poem::Error::from_status(StatusCode::BAD_REQUEST))?;
|
||||
|
||||
// Use application layer use case
|
||||
match self.get_status_use_case.execute(relay_id).await {
|
||||
Ok(relay) => Ok(RelayResponse::Status(Json(relay.into()))),
|
||||
Err(_) => Ok(RelayResponse::ServiceUnavailable),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Settings Extension
|
||||
|
||||
```rust
|
||||
// src/settings.rs
|
||||
#[derive(Debug, serde::Deserialize, Clone)]
|
||||
pub struct ModbusSettings {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub slave_id: u8,
|
||||
pub timeout_seconds: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, Clone)]
|
||||
pub struct Settings {
|
||||
pub application: ApplicationSettings,
|
||||
pub debug: bool,
|
||||
pub frontend_url: String,
|
||||
pub rate_limit: RateLimitSettings,
|
||||
pub modbus: ModbusSettings, // New field
|
||||
}
|
||||
```
|
||||
|
||||
```yaml
|
||||
# settings/base.yaml
|
||||
modbus:
|
||||
host: "192.168.1.100"
|
||||
port: 502
|
||||
slave_id: 1
|
||||
timeout_seconds: 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
### Key Takeaways
|
||||
|
||||
1. **tokio-modbus 0.17.0**: Excellent choice, use trait abstraction for testability
|
||||
2. **HTTP Polling**: Maintain spec decision, simpler and adequate for scale
|
||||
3. **Hexagonal Architecture**: Add domain/application layers following existing patterns
|
||||
4. **Type-Driven Development**: Apply newtype pattern (RelayId, RelayState)
|
||||
5. **Testing**: Use mockall with async-trait for >90% coverage without hardware
|
||||
|
||||
### Next Steps
|
||||
|
||||
1. **Clarifying Questions**: Resolve ambiguities in requirements
|
||||
2. **Architecture Design**: Create multiple implementation approaches
|
||||
3. **Final Plan**: Select approach and create detailed implementation plan
|
||||
4. **Implementation**: Follow TDD workflow with types-first design
|
||||
|
||||
---
|
||||
|
||||
**End of Research Document**
|
||||
51
specs/001-modbus-relay-control/spec-checklist.md
Normal file
51
specs/001-modbus-relay-control/spec-checklist.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Specification Quality Checklist: Modbus Relay Control System
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2025-12-28
|
||||
**Feature**: [spec.md](./spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- **Note**: Specification intentionally includes some implementation constraints (Rust, Poem, tokio-modbus) per project constitution requirements (NFR-009, NFR-014, NFR-015). These are architectural constraints, not implementation details of business logic.
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- **Resolution**: FR-023 clarified by user - backend starts successfully even when device unhealthy, frontend displays error as part of Health story
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- **Note**: SC-010 references cargo tarpaulin as measurement tool, which is acceptable for NFR validation
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Quality Assessment
|
||||
|
||||
**Overall Status**: ✅ **READY FOR PLANNING**
|
||||
|
||||
### Strengths
|
||||
- Comprehensive coverage of 5 prioritized, independently testable user stories
|
||||
- 37 functional + 21 non-functional requirements provide clear scope
|
||||
- Edge cases thoroughly documented with specific mitigation strategies
|
||||
- Success criteria are measurable and aligned with user stories
|
||||
- Clear boundaries with explicit "Out of Scope" section
|
||||
- Risk matrix identifies key concerns with mitigation approaches
|
||||
|
||||
### Notes
|
||||
- Specification includes architectural constraints (hexagonal architecture, TDD, TyDD) per project constitution
|
||||
- These constraints are non-negotiable project requirements, not arbitrary implementation details
|
||||
- User clarification resolved FR-023 regarding startup behavior when device is unhealthy
|
||||
- Specification ready for `/sdd:02-plan` stage
|
||||
315
specs/001-modbus-relay-control/spec.md
Normal file
315
specs/001-modbus-relay-control/spec.md
Normal file
@@ -0,0 +1,315 @@
|
||||
# Feature Specification: Modbus Relay Control System
|
||||
|
||||
**Feature Branch**: `001-modbus-relay-control`
|
||||
**Created**: 2025-12-28
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Modbus relay control system: backend reads relay and writes states via Modbus, exposes REST API, frontend displays relay states and allows toggling."
|
||||
|
||||
## Executive Summary
|
||||
|
||||
### Problem Statement
|
||||
|
||||
Users currently require specialized Modbus software (Modbus Poll, SSCOM) to interact with an 8-channel relay device, creating barriers to adoption and limiting remote access capabilities. The lack of a web-based interface prevents non-technical users from controlling relays and limits integration possibilities.
|
||||
|
||||
### Proposed Solution
|
||||
|
||||
A web application consisting of:
|
||||
- **Rust Backend**: Modbus RTU over TCP integration + RESTful HTTP API (deployed on Raspberry Pi)
|
||||
- **Vue.js Frontend**: Real-time relay status display and control interface (deployed on Cloudflare Pages)
|
||||
- **Reverse Proxy**: Traefik with Authelia middleware for authentication and HTTPS termination
|
||||
- **Local Network**: Raspberry Pi on same network as Modbus relay device
|
||||
|
||||
### Value Proposition
|
||||
|
||||
- **Accessibility**: Control relays from any browser without specialized software
|
||||
- **Usability**: Intuitive UI eliminates need for Modbus protocol knowledge
|
||||
- **Foundation**: Enables future automation, scheduling, and integration capabilities
|
||||
- **Deployment**: Self-contained system with no external dependencies
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Monitor Relay Status (Priority: P1)
|
||||
|
||||
As a user, I want to see the current state (on/off) of all 8 relays in real-time so I can verify the physical system state without being physically present.
|
||||
|
||||
**Why this priority**: Foundation capability - all other features depend on accurate state visibility. Delivers immediate value by eliminating need for physical inspection or specialized software.
|
||||
|
||||
**Independent Test**: Can be fully tested by loading the web interface and verifying displayed states match physical relay states (verified with multimeter or visual indicators). Delivers value even without control capabilities.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** all relays are OFF, **When** I load the web interface, **Then** I see 8 relays each displaying "OFF" state
|
||||
2. **Given** relay #3 is ON and others are OFF, **When** I load the interface, **Then** I see relay #3 showing "ON" and others showing "OFF"
|
||||
3. **Given** the interface is loaded, **When** relay state changes externally (via Modbus Poll), **Then** the interface updates within 2 seconds to reflect the new state
|
||||
4. **Given** the Modbus device is unreachable, **When** I load the interface, **Then** I see an error message indicating the device is unavailable
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Toggle Individual Relay (Priority: P1)
|
||||
|
||||
As a user, I want to toggle any relay on or off with a single click so I can control connected devices remotely.
|
||||
|
||||
**Why this priority**: Core use case - enables remote control capability. Combined with Story 1, creates a complete minimal viable product.
|
||||
|
||||
**Independent Test**: Can be tested by clicking any relay toggle button and observing both UI update and physical relay click/LED change. Delivers standalone value for remote control.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** relay #5 is OFF, **When** I click the toggle button for relay #5, **Then** relay #5 turns ON and the UI reflects this within 1 second
|
||||
2. **Given** relay #2 is ON, **When** I click the toggle button for relay #2, **Then** relay #2 turns OFF and the UI reflects this within 1 second
|
||||
3. **Given** the Modbus device is unreachable, **When** I attempt to toggle a relay, **Then** I see an error message and the UI does not change
|
||||
4. **Given** I toggle relay #1, **When** the Modbus command times out, **Then** I see a timeout error and can retry
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Bulk Relay Control (Priority: P2)
|
||||
|
||||
As a user, I want to turn all relays ON or OFF simultaneously so I can quickly reset the entire system or enable/disable all connected devices at once.
|
||||
|
||||
**Why this priority**: Efficiency improvement for common scenarios (system shutdown, initialization). Not critical for MVP but significantly improves user experience.
|
||||
|
||||
**Independent Test**: Can be tested by clicking "All ON" or "All OFF" buttons and verifying all 8 physical relays respond. Delivers value for batch operations without requiring individual story implementations.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** relays have mixed states (some ON, some OFF), **When** I click "All ON", **Then** all 8 relays turn ON within 2 seconds
|
||||
2. **Given** all relays are ON, **When** I click "All OFF", **Then** all 8 relays turn OFF within 2 seconds
|
||||
3. **Given** I click "All ON" and relay #4 fails to respond, **Then** I see an error for relay #4 but other relays still turn ON
|
||||
4. **Given** the Modbus device is unreachable, **When** I click "All ON", **Then** I see an error message and no state changes occur
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 - System Health Monitoring (Priority: P2)
|
||||
|
||||
As a user, I want to see device connectivity status and firmware version so I can diagnose issues and verify device compatibility.
|
||||
|
||||
**Why this priority**: Operational value for troubleshooting. Not required for basic control but critical for production reliability and maintenance.
|
||||
|
||||
**Independent Test**: Can be tested by viewing the health status section, disconnecting the Modbus device, and observing status change. Delivers standalone diagnostic value.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the Modbus device is connected and responsive, **When** I view the health status, **Then** I see "Healthy" status with firmware version displayed
|
||||
2. **Given** the Modbus device is unreachable, **When** the backend starts, **Then** the backend starts successfully and the frontend displays "Unhealthy - Device Unreachable" status
|
||||
3. **Given** the Modbus device becomes unreachable during operation, **When** I view the health status, **Then** I see "Unhealthy - Connection Lost" with timestamp of last successful communication
|
||||
4. **Given** the Modbus device responds but with CRC errors, **When** I view health status, **Then** I see "Degraded - Communication Errors" with error count
|
||||
|
||||
---
|
||||
|
||||
### User Story 5 - Relay Labeling (Priority: P3)
|
||||
|
||||
As a user, I want to assign custom labels to each relay (e.g., "Garage Light", "Water Pump") so I can identify relays by purpose instead of numbers.
|
||||
|
||||
**Why this priority**: Usability enhancement - makes system more intuitive for production use. Not required for MVP but improves long-term user experience.
|
||||
|
||||
**Independent Test**: Can be tested by assigning a label to relay #1, refreshing the page, and verifying the label persists. Delivers value for multi-relay installations without requiring other stories.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** I am viewing relay #3, **When** I click "Edit Label" and enter "Office Fan", **Then** relay #3 displays "Office Fan (Relay 3)"
|
||||
2. **Given** relay #7 has label "Water Pump", **When** I refresh the page, **Then** relay #7 still shows "Water Pump (Relay 7)"
|
||||
3. **Given** I have labeled multiple relays, **When** I toggle a relay by label, **Then** the correct physical relay responds
|
||||
4. **Given** two relays have similar labels, **When** I search for a label, **Then** both matching relays are highlighted
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- **Network Partition**: What happens when the Raspberry Pi loses connectivity to the Modbus device mid-operation?
|
||||
- Backend marks device unhealthy, frontend displays error state, pending operations fail gracefully with clear error messages
|
||||
|
||||
- **Concurrent Control**: How does system handle multiple users toggling the same relay simultaneously?
|
||||
- Last-write-wins semantics, each client receives updated state via polling within 2 seconds
|
||||
|
||||
- **Modbus Timeout**: What happens when a relay command times out?
|
||||
- Backend retries once automatically, if retry fails, returns error to frontend with clear timeout message
|
||||
|
||||
- **Partial Bulk Failure**: What happens when "All ON" command succeeds for 7 relays but relay #4 fails?
|
||||
- Frontend displays partial success with list of failed relays, successful relays remain ON, user can retry failed relays individually
|
||||
|
||||
- **Rapid Toggle Requests**: How does system handle user clicking toggle button repeatedly in quick succession?
|
||||
- Frontend debounces clicks (500ms), backend queues commands serially, prevents command flooding
|
||||
|
||||
- **Device Firmware Mismatch**: What happens if relay device firmware version is incompatible?
|
||||
- Backend logs firmware version, health check displays warning if version is untested, system attempts normal operation with degraded status
|
||||
|
||||
- **State Inconsistency**: What happens if Modbus read shows relay state different from expected state after write?
|
||||
- Backend logs inconsistency, frontend displays actual state (read value), user sees visual indication of unexpected state
|
||||
|
||||
- **Browser Compatibility**: How does frontend handle older browsers without modern JavaScript features?
|
||||
- Vue.js build targets ES2015+, displays graceful error message on IE11 and older, works on all modern browsers (Chrome, Firefox, Safari, Edge)
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
#### Backend - Modbus Integration
|
||||
|
||||
- **FR-001**: System MUST establish Modbus RTU over TCP connection to relay device on configurable IP and port (default: device IP, port 502)
|
||||
- **FR-002**: System MUST use Modbus function code 0x01 (Read Coils) to read all 8 relay states (addresses 0-7)
|
||||
- **FR-003**: System MUST use Modbus function code 0x05 (Write Single Coil) to toggle individual relays
|
||||
- **FR-004**: System MUST use Modbus function code 0x0F (Write Multiple Coils) for bulk operations (All ON/All OFF)
|
||||
- **FR-005**: System MUST validate Modbus CRC16 checksums on all received messages
|
||||
- **FR-006**: System MUST timeout Modbus operations after 3 seconds
|
||||
- **FR-007**: System MUST retry failed Modbus commands exactly once before returning error
|
||||
- **FR-008**: System MUST handle Modbus exception codes (0x01-0x04) and map to user-friendly error messages
|
||||
- **FR-009**: System MUST use tokio-modbus library version 0.17.0 for Modbus protocol implementation
|
||||
- **FR-010**: System MUST support configurable Modbus device address (default: 0x01)
|
||||
|
||||
#### Backend - REST API
|
||||
|
||||
- **FR-011**: System MUST expose `GET /api/relays` endpoint returning array of all relay states (id, state, label)
|
||||
- **FR-012**: System MUST expose `POST /api/relays/{id}/toggle` endpoint to toggle relay {id} (id: 1-8)
|
||||
- **FR-013**: System MUST expose `POST /api/relays/bulk` endpoint accepting `{"operation": "all_on" | "all_off"}`
|
||||
- **FR-014**: System MUST expose `GET /api/health` endpoint returning device status (healthy/unhealthy, firmware version, last_contact timestamp)
|
||||
- **FR-015**: System MUST expose `PUT /api/relays/{id}/label` endpoint to update relay label (max 50 characters)
|
||||
- **FR-016**: System MUST return HTTP 200 for successful operations with JSON response body
|
||||
- **FR-017**: System MUST return HTTP 500 for Modbus communication failures with error details
|
||||
- **FR-018**: System MUST return HTTP 400 for invalid request parameters (e.g., relay id out of range)
|
||||
- **FR-019**: System MUST return HTTP 504 for Modbus timeout errors
|
||||
- **FR-020**: System MUST include OpenAPI 3.0 specification accessible at `/api/specs`
|
||||
- **FR-021**: System MUST apply rate limiting middleware (100 requests/minute per IP)
|
||||
- **FR-022**: System MUST apply CORS middleware allowing all origins (local network deployment)
|
||||
- **FR-023**: System MUST start successfully even if Modbus device is unreachable at startup, marking device as unhealthy
|
||||
- **FR-024**: System MUST persist relay labels to configuration file (YAML) for persistence across restarts
|
||||
|
||||
#### Frontend - User Interface
|
||||
|
||||
- **FR-025**: UI MUST display all 8 relays in a grid layout with clear ON/OFF state indication (color-coded)
|
||||
- **FR-026**: UI MUST provide toggle button for each relay that triggers `POST /api/relays/{id}/toggle`
|
||||
- **FR-027**: UI MUST provide "All ON" and "All OFF" buttons that trigger `POST /api/relays/bulk`
|
||||
- **FR-028**: UI MUST poll `GET /api/relays` every 2 seconds to refresh relay states
|
||||
- **FR-029**: UI MUST display loading indicator while relay operations are in progress
|
||||
- **FR-030**: UI MUST display error messages when API calls fail, with specific error text from backend
|
||||
- **FR-031**: UI MUST display health status section showing device connectivity and firmware version
|
||||
- **FR-032**: UI MUST display "Unhealthy - Device Unreachable" message when backend reports device unreachable
|
||||
- **FR-033**: UI MUST provide inline label editing for each relay (click to edit, save on blur/enter)
|
||||
- **FR-034**: UI MUST be responsive and functional on desktop (>1024px), tablet (768-1024px), and mobile (320-767px)
|
||||
- **FR-035**: UI MUST disable toggle buttons and show error when device is unhealthy
|
||||
- **FR-036**: UI MUST show timestamp of last successful state update
|
||||
- **FR-037**: UI MUST debounce toggle button clicks to 500ms to prevent rapid repeated requests
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
#### Performance
|
||||
|
||||
- **NFR-001**: System MUST respond to `GET /api/relays` within 100ms (excluding Modbus communication time)
|
||||
- **NFR-002**: System MUST complete relay toggle operations within 1 second (including Modbus communication)
|
||||
- **NFR-003**: System MUST handle 10 concurrent users without performance degradation
|
||||
- **NFR-004**: Frontend MUST render initial page load within 2 seconds on 10 Mbps connection
|
||||
|
||||
#### Reliability
|
||||
|
||||
- **NFR-005**: System MUST maintain 95% successful operation rate for Modbus commands
|
||||
- **NFR-006**: System MUST recover automatically from temporary Modbus connection loss within 5 seconds
|
||||
- **NFR-007**: System MUST log all Modbus errors with structured logging (timestamp, error code, relay id)
|
||||
- **NFR-008**: Backend MUST continue serving health and API endpoints even when Modbus device is unreachable
|
||||
|
||||
#### Security
|
||||
|
||||
- **NFR-009**: Backend MUST run on local network with Modbus device (no direct public internet exposure)
|
||||
- **NFR-010**: System MUST NOT implement application-level authentication (handled by Traefik middleware with Authelia)
|
||||
- **NFR-011**: Frontend-to-backend communication MUST use HTTPS via Traefik reverse proxy (backend itself runs HTTP, Traefik handles TLS termination)
|
||||
- **NFR-012**: System MUST validate all API inputs to prevent injection attacks
|
||||
- **NFR-013-SEC**: Backend-to-Modbus communication uses unencrypted Modbus TCP (local network only)
|
||||
|
||||
#### Maintainability
|
||||
|
||||
- **NFR-014**: Code MUST achieve >90% test coverage for domain logic (relay control, Modbus abstraction)
|
||||
- **NFR-015**: System MUST follow hexagonal architecture with trait-based Modbus abstraction for testability
|
||||
- **NFR-016**: System MUST use Type-Driven Development (TyDD) with newtype pattern for RelayId, RelayState, ModbusCommand
|
||||
- **NFR-017**: All public APIs MUST have OpenAPI documentation
|
||||
- **NFR-018-MAINT**: Code MUST pass `cargo clippy` with zero warnings on all, pedantic, and nursery lints
|
||||
|
||||
#### Observability
|
||||
|
||||
- **NFR-019**: System MUST emit structured logs at all architectural boundaries (API, Modbus)
|
||||
- **NFR-020**: System MUST log relay state changes with timestamp, relay id, old state, new state
|
||||
- **NFR-021**: System MUST expose Prometheus metrics endpoint at `/metrics` (request count, error rate, Modbus latency)
|
||||
- **NFR-022**: System MUST log startup configuration (Modbus host/port, relay count) at INFO level
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **Relay**: Represents a single relay channel (1-8) with properties: id (1-8), state (ON/OFF), label (optional, max 50 chars)
|
||||
- **RelayState**: Enum representing ON or OFF state
|
||||
- **RelayId**: Newtype wrapping u8 with validation (1-8 range), implements TyDD pattern
|
||||
- **ModbusCommand**: Enum representing Modbus operations (ReadCoils, WriteSingleCoil, WriteMultipleCoils)
|
||||
- **DeviceHealth**: Struct representing Modbus device status (`healthy: bool`, `firmware_version: Option<String>`, `last_contact: Option<DateTime>`)
|
||||
- **RelayLabel**: Newtype wrapping String with validation (max 50 chars, alphanumeric + spaces)
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Users can view all 8 relay states within 2 seconds of loading the web interface
|
||||
- **SC-002**: Users can toggle any relay with physical relay response within 1 second of button click
|
||||
- **SC-003**: System achieves 95% successful operation rate for relay toggle commands over 24-hour period
|
||||
- **SC-004**: Web interface is accessible and functional on Chrome, Firefox, Safari, and Edge browsers
|
||||
- **SC-005**: Users can successfully use the interface on mobile devices (portrait and landscape)
|
||||
- **SC-006**: Backend starts successfully and serves health endpoint even when Modbus device is disconnected
|
||||
- **SC-007**: Frontend displays clear error message within 2 seconds when Modbus device is unhealthy
|
||||
- **SC-008**: System supports 10 concurrent users performing toggle operations without performance degradation
|
||||
- **SC-009**: All 8 relays turn ON within 2 seconds when "All ON" button is clicked
|
||||
- **SC-010**: Domain logic achieves >90% test coverage as measured by `cargo tarpaulin`
|
||||
|
||||
### User Experience Goals
|
||||
|
||||
- **UX-001**: Non-technical users can control relays without referring to documentation
|
||||
- **UX-002**: Error messages clearly explain problem and suggest remediation (e.g., "Device unreachable - check network connection")
|
||||
- **UX-003**: Relay labels make it intuitive to identify relay purpose without memorizing numbers
|
||||
|
||||
## Dependencies & Assumptions
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **Hardware**: 8-channel Modbus POE ETH Relay device (documented in `docs/Modbus_POE_ETH_Relay.md`)
|
||||
- **Network**: Local network connectivity between Raspberry Pi and relay device
|
||||
- **Libraries**: tokio-modbus 0.17.0, Poem 3.1, poem-openapi 5.1, Tokio 1.48
|
||||
- **Frontend**: Vue.js 3.x, TypeScript, Vite build tool
|
||||
- **Backend Deployment**: Raspberry Pi (or equivalent) running Linux with Docker
|
||||
- **Frontend Deployment**: Cloudflare Pages (or equivalent static hosting)
|
||||
- **Reverse Proxy**: Traefik with Authelia middleware for authentication
|
||||
|
||||
### Assumptions
|
||||
|
||||
- **ASM-001**: Relay device uses Modbus RTU over TCP protocol (per hardware documentation)
|
||||
- **ASM-002**: Relay device supports standard Modbus function codes 0x01, 0x05, 0x0F
|
||||
- **ASM-003**: Local network provides reliable connectivity (>95% uptime)
|
||||
- **ASM-004**: Traefik reverse proxy with Authelia middleware provides adequate authentication
|
||||
- **ASM-005**: Single user will control relays at a time in most scenarios (concurrent control is edge case)
|
||||
- **ASM-006**: Relay device exposes 8 coils at Modbus addresses 0-7
|
||||
- **ASM-007**: Device firmware is compatible with tokio-modbus library
|
||||
- **ASM-008**: Raspberry Pi has sufficient resources (CPU, memory) to run Rust backend
|
||||
- **ASM-009**: Cloudflare Pages or equivalent CDN provides fast frontend delivery
|
||||
- **ASM-010**: Frontend can reach backend via HTTPS through Traefik reverse proxy
|
||||
|
||||
## Out of Scope
|
||||
|
||||
The following capabilities are explicitly excluded from this specification:
|
||||
|
||||
- **Application-Level Authentication**: No user login, role-based access control, or API keys (handled by Traefik/Authelia)
|
||||
- **Historical Data**: No database, state logging, or historical relay state tracking
|
||||
- **Scheduling**: No timer-based relay control or automation rules
|
||||
- **Multiple Devices**: No support for controlling multiple relay devices simultaneously
|
||||
- **Advanced Modbus Features**: No support for flash modes, timing operations, or device reconfiguration
|
||||
- **Mobile Native Apps**: Web interface only, no iOS/Android native applications
|
||||
- **Cloud Backend**: Backend runs on local network (Raspberry Pi), frontend served from Cloudflare Pages
|
||||
- **Real-time Updates**: HTTP polling only (no WebSocket, Server-Sent Events)
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
| Risk | Impact | Probability | Mitigation |
|
||||
|--------------------------------------------|--------|-------------|------------------------------------------------------------------------|
|
||||
| Modbus device firmware incompatibility | High | Low | Test with actual hardware early, document compatible firmware versions |
|
||||
| Network latency exceeds timeout thresholds | Medium | Medium | Make timeouts configurable, implement adaptive retry logic |
|
||||
| Concurrent control causes state conflicts | Low | Medium | Implement last-write-wins with clear state refresh in UI |
|
||||
| Frontend polling overwhelms backend | Low | Low | Rate limit API endpoints, make poll interval configurable |
|
||||
| Raspberry Pi resource exhaustion | Medium | Low | Benchmark with 10 concurrent users, optimize Modbus connection pooling |
|
||||
|
||||
## Revision History
|
||||
|
||||
| Version | Date | Author | Changes |
|
||||
|---------|------|--------|---------|
|
||||
| 1.0 | 2025-12-28 | Business Analyst Agent | Initial specification based on user input |
|
||||
| 1.1 | 2025-12-28 | User Clarification | FR-023 clarified: Backend starts successfully even when device unhealthy, frontend displays error (part of Health story) |
|
||||
| 1.2 | 2025-12-29 | User Clarification | Architecture updated: Frontend on Cloudflare Pages, backend on RPi behind Traefik with Authelia. Updated NFR-009 to NFR-013-SEC to reflect HTTPS via reverse proxy, authentication via Traefik middleware |
|
||||
1153
specs/001-modbus-relay-control/tasks.md
Normal file
1153
specs/001-modbus-relay-control/tasks.md
Normal file
File diff suppressed because it is too large
Load Diff
1035
specs/001-modbus-relay-control/types-design.md
Normal file
1035
specs/001-modbus-relay-control/types-design.md
Normal file
File diff suppressed because it is too large
Load Diff
243
specs/constitution.md
Normal file
243
specs/constitution.md
Normal file
@@ -0,0 +1,243 @@
|
||||
<!--
|
||||
SYNC IMPACT REPORT
|
||||
==================
|
||||
Version Change: 1.0.0 → 1.1.0
|
||||
Amendment Date: 2025-12-27
|
||||
Type: MINOR - New principle added
|
||||
|
||||
Modified Principles:
|
||||
- Principle I: Enhanced with explicit SOLID principles reference
|
||||
|
||||
Added Sections:
|
||||
- New Principle VI: SOLID Principles
|
||||
|
||||
Removed Sections:
|
||||
- None
|
||||
|
||||
Templates Status:
|
||||
✅ plan-template.md - already aligned
|
||||
✅ spec-template.md - already aligned
|
||||
✅ spec-checklist.md - already aligned
|
||||
✅ tasks-template.md - already aligned
|
||||
|
||||
Follow-up TODOs:
|
||||
- None (all changes integrated)
|
||||
|
||||
Rationale:
|
||||
Adding SOLID principles as an explicit constitutional principle reinforces
|
||||
the existing clean architecture and hexagonal architecture principles with
|
||||
concrete design guidelines. This is a MINOR bump because it adds new
|
||||
governance guidance without changing existing principles.
|
||||
-->
|
||||
|
||||
# STA (Smart Temperature & Appliance Control) Constitution
|
||||
|
||||
## Core Principles
|
||||
|
||||
### I. Hexagonal Architecture (Clean Architecture)
|
||||
|
||||
The system MUST follow hexagonal architecture principles with clear separation of concerns:
|
||||
|
||||
- **Domain Layer**: Pure business logic with no external dependencies
|
||||
- **Application Layer**: Use cases and orchestration logic
|
||||
- **Infrastructure Layer**: External concerns (HTTP, Modbus, persistence)
|
||||
- **Presentation Layer**: API contracts and DTOs
|
||||
|
||||
All dependencies MUST point inward. Infrastructure and presentation layers depend on domain/application, never the reverse. This ensures testability, maintainability, and framework independence.
|
||||
|
||||
**SOLID Alignment**: This principle directly enforces Dependency Inversion Principle (DIP) through inward-pointing dependencies and Interface Segregation Principle (ISP) through layer boundaries.
|
||||
|
||||
**Rationale**: Hexagonal architecture enables independent testing of business logic, technology substitution without domain changes, and clear ownership boundaries between layers.
|
||||
|
||||
### II. Domain-Driven Design
|
||||
|
||||
The domain model MUST be rich and expressive:
|
||||
|
||||
- Domain entities encapsulate business rules and invariants
|
||||
- Value objects are immutable and self-validating
|
||||
- Repositories abstract persistence concerns
|
||||
- Services contain domain logic that doesn't belong to entities
|
||||
- Clear ubiquitous language shared between code and specifications
|
||||
|
||||
Domain types MUST NOT leak across architectural boundaries. DTOs and domain entities are distinct.
|
||||
|
||||
**Rationale**: DDD ensures the codebase reflects real-world domain concepts, making it easier to reason about, maintain, and evolve as business requirements change.
|
||||
|
||||
### III. Test-First Development (NON-NEGOTIABLE)
|
||||
|
||||
TDD is mandatory for all feature development:
|
||||
|
||||
1. Write failing tests first
|
||||
2. User reviews and approves test scenarios
|
||||
3. Implement minimal code to pass tests
|
||||
4. Refactor while keeping tests green
|
||||
|
||||
Test coverage MUST include:
|
||||
- Unit tests for domain logic (isolated, fast)
|
||||
- Integration tests for infrastructure adapters (Modbus, HTTP)
|
||||
- Contract tests for API endpoints
|
||||
- Mock-based tests to avoid hardware dependencies during CI
|
||||
|
||||
**Rationale**: Test-first ensures specifications are validated before implementation, prevents regression, and serves as executable documentation. The red-green-refactor cycle enforces disciplined development.
|
||||
|
||||
### IV. API-First Design
|
||||
|
||||
All functionality MUST be exposed through well-defined API contracts:
|
||||
|
||||
- RESTful HTTP API for web interface (Poem + OpenAPI)
|
||||
- Modbus TCP protocol for relay hardware communication
|
||||
- Clear separation between public API contracts and internal implementation
|
||||
- OpenAPI specifications generated and maintained automatically
|
||||
- API versioning strategy for backward compatibility
|
||||
|
||||
Backend (Rust) and frontend (TypeScript/Vue) communicate exclusively through documented API contracts.
|
||||
|
||||
**Rationale**: API-first design enables parallel frontend/backend development, clear integration points, and prevents tight coupling between presentation and business logic.
|
||||
|
||||
### V. Observability & Monitoring
|
||||
|
||||
Production systems MUST be observable:
|
||||
|
||||
- Structured logging at all architectural boundaries (tracing crate)
|
||||
- Request/response logging for HTTP and Modbus communication
|
||||
- Health check endpoints for system status
|
||||
- Error context preserved across layer boundaries (thiserror)
|
||||
- JSON log format for production environments
|
||||
- Human-readable format for development
|
||||
|
||||
Debugging MUST be possible without modifying code.
|
||||
|
||||
**Rationale**: Observability enables rapid diagnosis of production issues, performance analysis, and understanding system behavior under real-world conditions.
|
||||
|
||||
### VI. SOLID Principles
|
||||
|
||||
All code MUST adhere to SOLID design principles:
|
||||
|
||||
**Single Responsibility Principle (SRP)**:
|
||||
- Each module, class, or function has ONE reason to change
|
||||
- Domain entities focus on business rules only
|
||||
- Infrastructure adapters focus on external integration only
|
||||
- Controllers/handlers focus on request orchestration only
|
||||
|
||||
**Open/Closed Principle (OCP)**:
|
||||
- Entities open for extension via traits/interfaces
|
||||
- Closed for modification through trait implementations
|
||||
- New behavior added through new implementations, not modifications
|
||||
|
||||
**Liskov Substitution Principle (LSP)**:
|
||||
- Trait implementations MUST be substitutable without breaking behavior
|
||||
- Mock implementations MUST honor trait contracts
|
||||
- Repository implementations MUST preserve domain semantics
|
||||
|
||||
**Interface Segregation Principle (ISP)**:
|
||||
- Traits MUST be focused and minimal
|
||||
- Clients depend only on methods they use
|
||||
- Large interfaces split into role-specific traits
|
||||
|
||||
**Dependency Inversion Principle (DIP)**:
|
||||
- High-level domain logic depends on abstractions (traits)
|
||||
- Low-level infrastructure implements abstractions
|
||||
- Dependencies injected through constructors/builders
|
||||
- No direct instantiation of concrete infrastructure types in domain
|
||||
|
||||
**Rationale**: SOLID principles ensure code remains maintainable, testable, and extensible as the system evolves. They provide concrete design rules that support the broader clean architecture goals.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Backend (Rust)
|
||||
|
||||
- **Web Framework**: Poem with OpenAPI support (poem-openapi)
|
||||
- **Async Runtime**: Tokio
|
||||
- **Modbus Protocol**: tokio-modbus 0.17.0
|
||||
- **Configuration**: config crate with YAML support
|
||||
- **Logging**: tracing + tracing-subscriber
|
||||
- **Error Handling**: thiserror
|
||||
- **Testing**: Built-in Rust test framework + mockall
|
||||
|
||||
### Frontend (TypeScript/Vue)
|
||||
|
||||
- **Framework**: Vue 3 with TypeScript
|
||||
- **HTTP Client**: Type-safe API client generated from OpenAPI specs
|
||||
- **Build Tool**: Vite
|
||||
- **State Management**: Pinia (if needed for complex state)
|
||||
|
||||
### Architecture Patterns
|
||||
|
||||
- Clean/Hexagonal architecture with explicit layer boundaries
|
||||
- Repository pattern for persistence abstraction
|
||||
- Trait-based dependency injection
|
||||
- Mock-based testing to avoid hardware dependencies
|
||||
- SOLID principles applied to all design decisions
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Feature Development Process
|
||||
|
||||
1. **Specification Phase**:
|
||||
- Write feature specification in `specs/<feature>/spec.md`
|
||||
- Define data model in `specs/<feature>/data-model.md`
|
||||
- Create implementation plan in `specs/<feature>/plan.md`
|
||||
- Document API contracts in `specs/<feature>/contracts/`
|
||||
|
||||
2. **Test-First Implementation**:
|
||||
- Write failing tests for domain logic
|
||||
- Write failing tests for infrastructure adapters
|
||||
- Write failing contract tests for API endpoints
|
||||
- Get user approval on test scenarios
|
||||
- Implement code to pass tests
|
||||
- Refactor while maintaining green tests
|
||||
|
||||
3. **Integration & Validation**:
|
||||
- Integration tests with mocked hardware
|
||||
- Real hardware testing (when available)
|
||||
- OpenAPI documentation validation
|
||||
- Code review focusing on architectural compliance
|
||||
|
||||
### Code Review Requirements
|
||||
|
||||
All code changes MUST be reviewed for:
|
||||
- Compliance with hexagonal architecture principles
|
||||
- SOLID principles adherence
|
||||
- Domain model clarity and expressiveness
|
||||
- Test coverage and quality
|
||||
- API contract adherence
|
||||
- Observability (logging, error context)
|
||||
- No domain logic leaking into infrastructure layer
|
||||
|
||||
### Quality Gates
|
||||
|
||||
Code MUST NOT be merged unless:
|
||||
- All tests pass (unit + integration)
|
||||
- Test coverage meets minimum thresholds
|
||||
- Architecture review confirms layer separation
|
||||
- SOLID principles validated (no SRP/DIP violations)
|
||||
- OpenAPI specs are up-to-date
|
||||
- Logging captures key operational events
|
||||
|
||||
## Governance
|
||||
|
||||
This constitution supersedes all other development practices and guidelines. All architectural decisions MUST align with the principles defined herein.
|
||||
|
||||
### Amendment Process
|
||||
|
||||
Amendments to this constitution require:
|
||||
1. Documented rationale for the change
|
||||
2. Impact analysis on existing codebase
|
||||
3. Migration plan if breaking existing patterns
|
||||
4. Approval before implementation
|
||||
|
||||
### Compliance Verification
|
||||
|
||||
- All pull requests MUST verify constitutional compliance
|
||||
- Architecture decisions MUST be justified against these principles
|
||||
- Complexity introduced MUST be necessary to uphold principles
|
||||
- Violations MUST be addressed before merge
|
||||
|
||||
### Version Control
|
||||
|
||||
This constitution uses semantic versioning:
|
||||
- **MAJOR**: Breaking changes to core principles
|
||||
- **MINOR**: New principles or significant clarifications
|
||||
- **PATCH**: Typo fixes, wording improvements
|
||||
|
||||
**Version**: 1.1.0 | **Ratified**: 2025-12-27 | **Last Amended**: 2025-12-27
|
||||
104
specs/templates/plan-template.md
Normal file
104
specs/templates/plan-template.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Implementation Plan: [FEATURE]
|
||||
|
||||
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
|
||||
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||
|
||||
## Summary
|
||||
|
||||
[Extract from feature spec: primary requirement + technical approach from research]
|
||||
|
||||
## Technical Context
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: Replace the content in this section with the technical details
|
||||
for the project. The structure here is presented in advisory capacity to guide
|
||||
the iteration process.
|
||||
-->
|
||||
|
||||
**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION]
|
||||
**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION]
|
||||
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
|
||||
**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]
|
||||
**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
|
||||
**Project Type**: [single/web/mobile - determines source structure]
|
||||
**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]
|
||||
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
|
||||
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
[Gates determined based on constitution file]
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/[###-feature]/
|
||||
├── plan.md # This file (/speckit.plan command output)
|
||||
├── research.md # Phase 0 output (/speckit.plan command)
|
||||
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
<!--
|
||||
ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
|
||||
for this feature. Delete unused options and expand the chosen structure with
|
||||
real paths (e.g., apps/admin, packages/something). The delivered plan must
|
||||
not include Option labels.
|
||||
-->
|
||||
|
||||
```text
|
||||
# [REMOVE IF UNUSED] Option 1: Single project (DEFAULT)
|
||||
src/
|
||||
├── models/
|
||||
├── services/
|
||||
├── cli/
|
||||
└── lib/
|
||||
|
||||
tests/
|
||||
├── contract/
|
||||
├── integration/
|
||||
└── unit/
|
||||
|
||||
# [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected)
|
||||
backend/
|
||||
├── src/
|
||||
│ ├── models/
|
||||
│ ├── services/
|
||||
│ └── api/
|
||||
└── tests/
|
||||
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ ├── pages/
|
||||
│ └── services/
|
||||
└── tests/
|
||||
|
||||
# [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected)
|
||||
api/
|
||||
└── [same as backend above]
|
||||
|
||||
ios/ or android/
|
||||
└── [platform-specific structure: feature modules, UI flows, platform tests]
|
||||
```
|
||||
|
||||
**Structure Decision**: [Document the selected structure and reference the real
|
||||
directories captured above]
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
|
||||
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
|
||||
34
specs/templates/spec-checklist.md
Normal file
34
specs/templates/spec-checklist.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Specification Quality Checklist: [FEATURE NAME]
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: [DATE]
|
||||
**Feature**: [Link to spec.md]
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [ ] No implementation details (languages, frameworks, APIs)
|
||||
- [ ] Focused on user value and business needs
|
||||
- [ ] Written for non-technical stakeholders
|
||||
- [ ] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [ ] No [NEEDS CLARIFICATION] markers remain
|
||||
- [ ] Requirements are testable and unambiguous
|
||||
- [ ] Success criteria are measurable
|
||||
- [ ] Success criteria are technology-agnostic (no implementation details)
|
||||
- [ ] All acceptance scenarios are defined
|
||||
- [ ] Edge cases are identified
|
||||
- [ ] Scope is clearly bounded
|
||||
- [ ] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [ ] All functional requirements have clear acceptance criteria
|
||||
- [ ] User scenarios cover primary flows
|
||||
- [ ] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [ ] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Items marked incomplete require spec updates before `/sdd:01-specify` or `/sdd:01-plan`
|
||||
115
specs/templates/spec-template.md
Normal file
115
specs/templates/spec-template.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Feature Specification: [FEATURE NAME]
|
||||
|
||||
**Feature Branch**: `[###-feature-name]`
|
||||
**Created**: [DATE]
|
||||
**Status**: Draft
|
||||
**Input**: User description: "$ARGUMENTS"
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
<!--
|
||||
IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
|
||||
Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
|
||||
you should still have a viable MVP (Minimum Viable Product) that delivers value.
|
||||
|
||||
Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
|
||||
Think of each story as a standalone slice of functionality that can be:
|
||||
- Developed independently
|
||||
- Tested independently
|
||||
- Deployed independently
|
||||
- Demonstrated to users independently
|
||||
-->
|
||||
|
||||
### User Story 1 - [Brief Title] (Priority: P1)
|
||||
|
||||
[Describe this user journey in plain language]
|
||||
|
||||
**Why this priority**: [Explain the value and why it has this priority level]
|
||||
|
||||
**Independent Test**: [Describe how this can be tested independently - e.g., "Can be fully tested by [specific action] and delivers [specific value]"]
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||
2. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - [Brief Title] (Priority: P2)
|
||||
|
||||
[Describe this user journey in plain language]
|
||||
|
||||
**Why this priority**: [Explain the value and why it has this priority level]
|
||||
|
||||
**Independent Test**: [Describe how this can be tested independently]
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - [Brief Title] (Priority: P3)
|
||||
|
||||
[Describe this user journey in plain language]
|
||||
|
||||
**Why this priority**: [Explain the value and why it has this priority level]
|
||||
|
||||
**Independent Test**: [Describe how this can be tested independently]
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
||||
|
||||
---
|
||||
|
||||
[Add more user stories as needed, each with an assigned priority]
|
||||
|
||||
### Edge Cases
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: The content in this section represents placeholders.
|
||||
Fill them out with the right edge cases.
|
||||
-->
|
||||
|
||||
- What happens when [boundary condition]?
|
||||
- How does system handle [error scenario]?
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: The content in this section represents placeholders.
|
||||
Fill them out with the right functional requirements.
|
||||
-->
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"]
|
||||
- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"]
|
||||
- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"]
|
||||
- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"]
|
||||
- **FR-005**: System MUST [behavior, e.g., "log all security events"]
|
||||
|
||||
*Example of marking unclear requirements:*
|
||||
|
||||
- **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?]
|
||||
- **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified]
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **[Entity 1]**: [What it represents, key attributes without implementation]
|
||||
- **[Entity 2]**: [What it represents, relationships to other entities]
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: Define measurable success criteria.
|
||||
These must be technology-agnostic and measurable.
|
||||
-->
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: [Measurable metric, e.g., "Users can complete account creation in under 2 minutes"]
|
||||
- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"]
|
||||
- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"]
|
||||
- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"]
|
||||
251
specs/templates/tasks-template.md
Normal file
251
specs/templates/tasks-template.md
Normal file
@@ -0,0 +1,251 @@
|
||||
---
|
||||
|
||||
description: "Task list template for feature implementation"
|
||||
---
|
||||
|
||||
# Tasks: [FEATURE NAME]
|
||||
|
||||
**Input**: Design documents from `/specs/[###-feature-name]/`
|
||||
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
|
||||
|
||||
**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification.
|
||||
|
||||
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
||||
- Include exact file paths in descriptions
|
||||
|
||||
## Path Conventions
|
||||
|
||||
- **Single project**: `src/`, `tests/` at repository root
|
||||
- **Web app**: `backend/src/`, `frontend/src/`
|
||||
- **Mobile**: `api/src/`, `ios/src/` or `android/src/`
|
||||
- Paths shown below assume single project - adjust based on plan.md structure
|
||||
|
||||
<!--
|
||||
============================================================================
|
||||
IMPORTANT: The tasks below are SAMPLE TASKS for illustration purposes only.
|
||||
|
||||
The /speckit.tasks command MUST replace these with actual tasks based on:
|
||||
- User stories from spec.md (with their priorities P1, P2, P3...)
|
||||
- Feature requirements from plan.md
|
||||
- Entities from data-model.md
|
||||
- Endpoints from contracts/
|
||||
|
||||
Tasks MUST be organized by user story so each story can be:
|
||||
- Implemented independently
|
||||
- Tested independently
|
||||
- Delivered as an MVP increment
|
||||
|
||||
DO NOT keep these sample tasks in the generated tasks.md file.
|
||||
============================================================================
|
||||
-->
|
||||
|
||||
## Phase 1: Setup (Shared Infrastructure)
|
||||
|
||||
**Purpose**: Project initialization and basic structure
|
||||
|
||||
- [ ] T001 Create project structure per implementation plan
|
||||
- [ ] T002 Initialize [language] project with [framework] dependencies
|
||||
- [ ] T003 [P] Configure linting and formatting tools
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
|
||||
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
|
||||
|
||||
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
|
||||
|
||||
Examples of foundational tasks (adjust based on your project):
|
||||
|
||||
- [ ] T004 Setup database schema and migrations framework
|
||||
- [ ] T005 [P] Implement authentication/authorization framework
|
||||
- [ ] T006 [P] Setup API routing and middleware structure
|
||||
- [ ] T007 Create base models/entities that all stories depend on
|
||||
- [ ] T008 Configure error handling and logging infrastructure
|
||||
- [ ] T009 Setup environment configuration management
|
||||
|
||||
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 - [Title] (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: [Brief description of what this story delivers]
|
||||
|
||||
**Independent Test**: [How to verify this story works on its own]
|
||||
|
||||
### Tests for User Story 1 (OPTIONAL - only if tests requested) ⚠️
|
||||
|
||||
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
|
||||
|
||||
- [ ] T010 [P] [US1] Contract test for [endpoint] in tests/contract/test_[name].py
|
||||
- [ ] T011 [P] [US1] Integration test for [user journey] in tests/integration/test_[name].py
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [ ] T012 [P] [US1] Create [Entity1] model in src/models/[entity1].py
|
||||
- [ ] T013 [P] [US1] Create [Entity2] model in src/models/[entity2].py
|
||||
- [ ] T014 [US1] Implement [Service] in src/services/[service].py (depends on T012, T013)
|
||||
- [ ] T015 [US1] Implement [endpoint/feature] in src/[location]/[file].py
|
||||
- [ ] T016 [US1] Add validation and error handling
|
||||
- [ ] T017 [US1] Add logging for user story 1 operations
|
||||
|
||||
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 - [Title] (Priority: P2)
|
||||
|
||||
**Goal**: [Brief description of what this story delivers]
|
||||
|
||||
**Independent Test**: [How to verify this story works on its own]
|
||||
|
||||
### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️
|
||||
|
||||
- [ ] T018 [P] [US2] Contract test for [endpoint] in tests/contract/test_[name].py
|
||||
- [ ] T019 [P] [US2] Integration test for [user journey] in tests/integration/test_[name].py
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [ ] T020 [P] [US2] Create [Entity] model in src/models/[entity].py
|
||||
- [ ] T021 [US2] Implement [Service] in src/services/[service].py
|
||||
- [ ] T022 [US2] Implement [endpoint/feature] in src/[location]/[file].py
|
||||
- [ ] T023 [US2] Integrate with User Story 1 components (if needed)
|
||||
|
||||
**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 - [Title] (Priority: P3)
|
||||
|
||||
**Goal**: [Brief description of what this story delivers]
|
||||
|
||||
**Independent Test**: [How to verify this story works on its own]
|
||||
|
||||
### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️
|
||||
|
||||
- [ ] T024 [P] [US3] Contract test for [endpoint] in tests/contract/test_[name].py
|
||||
- [ ] T025 [P] [US3] Integration test for [user journey] in tests/integration/test_[name].py
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [ ] T026 [P] [US3] Create [Entity] model in src/models/[entity].py
|
||||
- [ ] T027 [US3] Implement [Service] in src/services/[service].py
|
||||
- [ ] T028 [US3] Implement [endpoint/feature] in src/[location]/[file].py
|
||||
|
||||
**Checkpoint**: All user stories should now be independently functional
|
||||
|
||||
---
|
||||
|
||||
[Add more user story phases as needed, following the same pattern]
|
||||
|
||||
---
|
||||
|
||||
## Phase N: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Improvements that affect multiple user stories
|
||||
|
||||
- [ ] TXXX [P] Documentation updates in docs/
|
||||
- [ ] TXXX Code cleanup and refactoring
|
||||
- [ ] TXXX Performance optimization across all stories
|
||||
- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/
|
||||
- [ ] TXXX Security hardening
|
||||
- [ ] TXXX Run quickstart.md validation
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
- **Setup (Phase 1)**: No dependencies - can start immediately
|
||||
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
|
||||
- **User Stories (Phase 3+)**: All depend on Foundational phase completion
|
||||
- User stories can then proceed in parallel (if staffed)
|
||||
- Or sequentially in priority order (P1 → P2 → P3)
|
||||
- **Polish (Final Phase)**: Depends on all desired user stories being complete
|
||||
|
||||
### User Story Dependencies
|
||||
|
||||
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
|
||||
- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable
|
||||
- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable
|
||||
|
||||
### Within Each User Story
|
||||
|
||||
- Tests (if included) MUST be written and FAIL before implementation
|
||||
- Models before services
|
||||
- Services before endpoints
|
||||
- Core implementation before integration
|
||||
- Story complete before moving to next priority
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
- All Setup tasks marked [P] can run in parallel
|
||||
- All Foundational tasks marked [P] can run in parallel (within Phase 2)
|
||||
- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows)
|
||||
- All tests for a user story marked [P] can run in parallel
|
||||
- Models within a story marked [P] can run in parallel
|
||||
- Different user stories can be worked on in parallel by different team members
|
||||
|
||||
---
|
||||
|
||||
## Parallel Example: User Story 1
|
||||
|
||||
```bash
|
||||
# Launch all tests for User Story 1 together (if tests requested):
|
||||
Task: "Contract test for [endpoint] in tests/contract/test_[name].py"
|
||||
Task: "Integration test for [user journey] in tests/integration/test_[name].py"
|
||||
|
||||
# Launch all models for User Story 1 together:
|
||||
Task: "Create [Entity1] model in src/models/[entity1].py"
|
||||
Task: "Create [Entity2] model in src/models/[entity2].py"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First (User Story 1 Only)
|
||||
|
||||
1. Complete Phase 1: Setup
|
||||
2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
|
||||
3. Complete Phase 3: User Story 1
|
||||
4. **STOP and VALIDATE**: Test User Story 1 independently
|
||||
5. Deploy/demo if ready
|
||||
|
||||
### Incremental Delivery
|
||||
|
||||
1. Complete Setup + Foundational → Foundation ready
|
||||
2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
|
||||
3. Add User Story 2 → Test independently → Deploy/Demo
|
||||
4. Add User Story 3 → Test independently → Deploy/Demo
|
||||
5. Each story adds value without breaking previous stories
|
||||
|
||||
### Parallel Team Strategy
|
||||
|
||||
With multiple developers:
|
||||
|
||||
1. Team completes Setup + Foundational together
|
||||
2. Once Foundational is done:
|
||||
- Developer A: User Story 1
|
||||
- Developer B: User Story 2
|
||||
- Developer C: User Story 3
|
||||
3. Stories complete and integrate independently
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- [P] tasks = different files, no dependencies
|
||||
- [Story] label maps task to specific user story for traceability
|
||||
- Each user story should be independently completable and testable
|
||||
- Verify tests fail before implementing
|
||||
- Commit after each task or logical group
|
||||
- Stop at any checkpoint to validate story independently
|
||||
- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence
|
||||
Reference in New Issue
Block a user