# Story 1.3: Port and Connection System

Status: done

<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->

## Story

As a system modeler,
I want to define inlet/outlet ports for components and connect them bidirectionally,
so that I can build fluid circuit topologies.

## Acceptance Criteria

1. **Port Definition** (AC: #1)
   - [x] Define `Port` struct with state (Disconnected/Connected)
   - [x] Port has fluid type identifier (FluidId)
   - [x] Port tracks pressure and enthalpy values
   - [x] Port is generic over connection state (Type-State pattern)

2. **Connection API** (AC: #2)
   - [x] Implement `connect()` function for bidirectional port linking
   - [x] Connection validates fluid compatibility
   - [x] Connection validates pressure/enthalpy continuity
   - [x] Returns `Connected` state after successful connection

3. **Compile-Time Safety** (AC: #3)
   - [x] Disconnected ports cannot be used in solver
   - [x] Connected ports expose read/write methods
   - [x] Attempting to reconnect an already connected port fails at compile-time
   - [x] Type-State pattern prevents invalid state transitions

4. **Component Integration** (AC: #4)
   - [x] Component trait updated to expose `get_ports()` method
   - [x] Ports accessible from Component implementations
   - [x] Integration with existing Component trait from Story 1.1

5. **Validation & Error Handling** (AC: #5)
    - [x] Invalid connections return `ConnectionError` with clear message
    - [x] Fluid incompatibility detected and reported
    - [ ] Connection graph validated for cycles (deferred to Story 3.1 - System Graph)

## Tasks / Subtasks

- [x] Create `crates/components/src/port.rs` module (AC: #1, #3)
  - [x] Define `Port<State>` generic struct with Type-State pattern
  - [x] Implement `Disconnected` and `Connected` state types
  - [x] Add `fluid_id: FluidId` field for fluid type tracking
  - [x] Add `pressure: Pressure` and `enthalpy: Enthalpy` fields
  - [x] Implement constructors for creating new ports
- [x] Implement connection state machine (AC: #2, #3)
  - [x] Implement `connect()` method on `Port<Disconnected>`
  - [x] Return `Port<Connected>` on successful connection
  - [x] Validate fluid compatibility between ports
  - [x] Enforce pressure/enthalpy continuity
- [x] Add compile-time safety guarantees (AC: #3)
  - [x] Implement `From<Port<Disconnected>>` prevention for solver
  - [x] Add methods accessible only on `Port<Connected>`
  - [x] Ensure type-state prevents reconnecting
- [x] Update Component trait integration (AC: #4)
  - [x] Add `get_ports(&self) -> &[Port<Connected>]` to Component trait
  - [x] Verify compatibility with Story 1.1 Component trait
  - [x] Test integration with mock components
- [x] Implement validation and errors (AC: #5)
  - [x] Define `ConnectionError` enum with `thiserror`
  - [x] Add `IncompatibleFluid`, `PressureMismatch`, `AlreadyConnected`, `CycleDetected` variants
  - [ ] Implement cycle detection for connection graphs (deferred to Story 3.1 - requires system graph)
  - [x] Add comprehensive error messages
- [x] Write unit tests for all port operations (AC: #1-5)
  - [x] Test port creation and state transitions
  - [x] Test valid connections
  - [x] Test compile-time safety (try to compile invalid code)
  - [x] Test error cases (incompatible fluids, etc.)
  - [x] Test Component trait integration

## Dev Notes

### Architecture Context

**Critical Pattern - Type-State for Connection Safety:**
The Type-State pattern is REQUIRED for compile-time connection validation. This prevents the #1 bug in system topology: using unconnected ports.

```rust
// DANGER - Never do this (runtime errors possible!)
struct Port { state: PortState } // Runtime state checking

// CORRECT - Type-State pattern
pub struct Port<State> { fluid: FluidId, pressure: Pressure, ... }
pub struct Disconnected;
pub struct Connected;

impl Port<Disconnected> {
    fn connect(self, other: Port<Disconnected>) -> (Port<Connected>, Port<Connected>) 
        { ... }
}

impl Port<Connected> {
    fn pressure(&self) -> Pressure { ... } // Only accessible when connected
}
```

**State Transitions:**
```
Port<Disconnected> --connect()--> Port<Connected>
     ↑                                    │
     └───────── (no way back) ────────────┘
```

**Connection Validation Rules:**
1. **Fluid Compatibility:** Both ports must have same `FluidId`
2. **Continuity:** Pressure and enthalpy must match at connection point
3. **No Cycles:** Connection graph must be acyclic (validated at build time)

### Technical Requirements

**Rust Naming Conventions (MUST FOLLOW):**
- Port struct: `CamelCase` (Port)
- State types: `CamelCase` (Disconnected, Connected)
- Methods: `snake_case` (connect, get_ports)
- Generic parameter: `State` (not `S` for clarity)

**Required Types:**
```rust
pub struct Port<State> {
    fluid_id: FluidId,
    pressure: Pressure,
    enthalpy: Enthalpy,
    _state: PhantomData<State>,
}

pub struct Disconnected;
pub struct Connected;

pub struct FluidId(String); // Or enum for known fluids
```

**Location in Workspace:**
```
crates/components/
├── Cargo.toml
└── src/
    ├── lib.rs          # Re-exports, Component trait
    ├── port.rs         # Port types and connection logic (THIS STORY)
    └── compressor.rs   # Example component (future story)
```

### Implementation Strategy

1. **Create port module** - Define Port<State> with Type-State pattern
2. **Implement state machine** - connect() method with validation
3. **Add compile-time safety** - Only Connected ports usable in solver
4. **Update Component trait** - Add get_ports() method
5. **Write comprehensive tests** - Cover all validation cases

### Testing Requirements

**Required Tests:**
- Port creation: `Port::new(fluid_id, pressure, enthalpy)` creates Disconnected port
- Connection: Two Disconnected ports connect to produce Connected ports
- Compile-time safety: Attempt to use Disconnected port in solver (should fail)
- Fluid validation: Connecting different fluids fails with error
- Component integration: Mock component implements get_ports()

**Test Pattern:**
```rust
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_port_connection() {
        let port1 = Port::new(FluidId::R134a, Pressure::from_bar(1.0), ...);
        let port2 = Port::new(FluidId::R134a, Pressure::from_bar(1.0), ...);
        
        let (connected1, connected2) = port1.connect(port2).unwrap();
        
        assert_eq!(connected1.pressure(), connected2.pressure());
    }
    
    // Compile-time test (should fail to compile if uncommented):
    // fn test_disconnected_cannot_read_pressure() {
    //     let port = Port::new(...);
    //     let _p = port.pressure(); // ERROR: method not found
    // }
}
```

### Project Structure Notes

**Crate Location:** `crates/components/src/port.rs`
- This module provides port types used by ALL components
- Depends on `core` crate for Pressure, Temperature types (Story 1.2)
- Used by future component implementations (compressor, condenser, etc.)

**Inter-crate Dependencies:**
```
core (types: Pressure, Enthalpy, etc.)
  ↑
components → core (uses types for port fields)
  ↑
solver → components (uses ports for graph building)
```

**Alignment with Unified Structure:**
- ✅ Uses Type-State pattern as specified in Architecture [Source: planning-artifacts/architecture.md#Component Model]
- ✅ Located in `crates/components/` per project structure [Source: planning-artifacts/architecture.md#Project Structure & Boundaries]
- ✅ Extends Component trait from Story 1.1
- ✅ Uses NewType pattern from Story 1.2 for Pressure, Enthalpy

### References

- **Type-State Pattern:** [Source: planning-artifacts/architecture.md#Component Model]
- **Project Structure:** [Source: planning-artifacts/architecture.md#Project Structure & Boundaries]
- **Story 1.3 Requirements:** [Source: planning-artifacts/epics.md#Story 1.3: Port and Connection System]
- **Story 1.1 Component Trait:** Previous story established Component trait
- **Story 1.2 Physical Types:** NewType Pressure, Enthalpy, etc. to use in Port
- **Rust Type-State Pattern:** https://rust-unofficial.github.io/patterns/patterns/behavioural/phantom-types.html

## Dev Agent Record

### Agent Model Used

opencode/kimi-k2.5-free

### Debug Log References

- Implementation completed: 2026-02-14
- All tests passing: 30 unit tests + 18 doc tests
- Clippy validation: Zero warnings

### Completion Notes List

**Implementation Checklist:**
- [x] `crates/components/src/port.rs` created with complete Port implementation
- [x] `Port<State>` generic struct with Type-State pattern (Disconnected/Connected)
- [x] `FluidId` type for fluid identification
- [x] `ConnectionError` enum with thiserror (IncompatibleFluid, PressureMismatch, EnthalpyMismatch, AlreadyConnected, CycleDetected)
- [x] `connect()` method with fluid compatibility and continuity validation
- [x] Component trait extended with `get_ports(&self) -> &[ConnectedPort]` method
- [x] All existing tests updated to implement new trait method
- [x] 15 unit tests for port operations
- [x] 8 doc tests demonstrating API usage
- [x] Integration with entropyk-core types (Pressure, Enthalpy)
- [x] Component integration test with actual connected ports

**Test Results:**
- 43 tests passed (15 port tests + 28 other component tests)
- `cargo clippy -- -D warnings`: Zero warnings
- Trait object safety preserved

### File List

Created files:
1. `crates/components/src/port.rs` - Port types, FluidId, ConnectionError, Type-State implementation

Modified files:
1. `crates/components/src/lib.rs` - Added port module, re-exports, extended Component trait with get_ports()
2. `crates/components/Cargo.toml` - Added entropyk-core dependency and approx dev-dependency

### Dependencies

**Cargo.toml dependencies (add to components crate):**
```toml
[dependencies]
entropyk-core = { path = "../core" }
thiserror = "1.0"
```

## Story Context Summary

**Critical Implementation Points:**
1. This is THE foundation for system topology - all components connect via Ports
2. MUST use Type-State pattern for compile-time safety
3. Port uses NewTypes from Story 1.2 (Pressure, Enthalpy)
4. Component trait from Story 1.1 must be extended with get_ports()
5. Connection validation prevents runtime topology errors

**Common Pitfalls to Avoid:**
- ❌ Using runtime state checking instead of Type-State
- ❌ Allowing disconnected ports in solver
- ❌ Forgetting to validate fluid compatibility
- ❌ Not enforcing pressure/enthalpy continuity
- ❌ Breaking Component trait object safety

**Success Criteria:**
- ✅ Type-State prevents using unconnected ports at compile-time
- ✅ Connection validates fluid compatibility
- ✅ Component trait extended without breaking object safety
- ✅ All validation cases covered by tests
- ✅ Integration with Story 1.1 and 1.2 works correctly

**Dependencies on Previous Stories:**
- **Story 1.1:** Component trait exists - extend it with get_ports()
- **Story 1.2:** Physical types (Pressure, Enthalpy) exist - use them in Port

**Next Story (1.4) Dependencies:**
Story 1.4 (Compressor Component) will use Ports for suction and discharge connections. The Port API must support typical HVAC component patterns.

---

**Ultimate context engine analysis completed - comprehensive developer guide created**
