# Implementation Roadmap

## The Iron Vault - Path to Production

Prioritized improvements for making FSAL production-ready and integration-ready.

---

## Phase 1: Critical Security Fixes (1-2 days)

### 1.1 Directory fsync After Rename
**File:** `src/aperion_fsal/core/safeio.py`
**Effort:** 30 min
```python
# After os.replace(tmp_path, p):
dir_fd = os.open(p.parent, os.O_DIRECTORY)
try:
    os.fsync(dir_fd)
finally:
    os.close(dir_fd)
```

### 1.2 Constant-Time Token Comparison
**File:** `src/aperion_fsal/service/auth.py`
**Effort:** 15 min
```python
import hmac

if not hmac.compare_digest(token.encode('utf-8'), fsal_token.encode('utf-8')):
    raise HTTPException(status_code=403, ...)
```

### 1.3 Request Size Limits
**File:** `src/aperion_fsal/service/models.py`
**Effort:** 15 min
```python
class WriteIn(PathIn):
    content_b64: str = Field(..., max_length=134_217_728)  # 128MB base64 = ~96MB file
```

### 1.4 Rate Limiting
**Files:** `pyproject.toml`, `src/aperion_fsal/service/app.py`
**Effort:** 1 hour
```toml
# pyproject.toml
dependencies = [..., "slowapi>=0.1.9"]
```
```python
# app.py
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter

@app.post("/v1/fs/write")
@limiter.limit("60/minute")
def fs_write(...):
```

### 1.5 Protect API Documentation in Production
**File:** `src/aperion_fsal/service/app.py`
**Effort:** 15 min
```python
is_dev = os.environ.get("APERION_ENV", "").lower() in ("dev", "development", "test")

app = FastAPI(
    ...,
    docs_url="/docs" if is_dev else None,
    redoc_url="/redoc" if is_dev else None,
)
```

---

## Phase 2: Production Hardening (2-3 days)

### 2.1 Add Security Headers Middleware
**File:** `src/aperion_fsal/service/middleware.py` (new)
**Effort:** 1 hour
```python
from starlette.middleware.base import BaseHTTPMiddleware

class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        response = await call_next(request)
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        response.headers["Cache-Control"] = "no-store"
        if request.url.scheme == "https":
            response.headers["Strict-Transport-Security"] = "max-age=31536000"
        return response
```

### 2.2 Switch to Gunicorn in Docker
**Files:** `Dockerfile`, `pyproject.toml`
**Effort:** 1 hour
```dockerfile
# Dockerfile
CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", \
     "aperion_fsal.service.app:app", "--bind", "0.0.0.0:4848", \
     "--timeout", "30", "--graceful-timeout", "10"]
```

### 2.3 Structured Logging with structlog
**Files:** `pyproject.toml`, `src/aperion_fsal/logging.py` (new)
**Effort:** 2 hours
```toml
dependencies = [..., "structlog>=24.0.0"]
```
```python
# logging.py
import structlog

def configure_logging():
    structlog.configure(
        processors=[
            structlog.stdlib.add_log_level,
            structlog.processors.TimeStamper(fmt="iso"),
            structlog.processors.JSONRenderer(),
        ],
    )
```

### 2.4 Writable Health Check
**File:** `src/aperion_fsal/service/app.py`
**Effort:** 30 min
```python
@app.get("/healthz")
def healthz():
    sandbox_config = validate_sandbox_config()
    
    # Test actual writability
    writable = False
    if sandbox_config.get("configured"):
        try:
            test_file = get_sandbox_root() / ".healthcheck"
            test_file.write_bytes(b"ok")
            test_file.unlink()
            writable = True
        except Exception:
            pass
    
    return {
        "ok": sandbox_config.get("configured", False) and writable,
        "writable": writable,
        ...
    }
```

### 2.5 Request Timeout Middleware
**File:** `src/aperion_fsal/service/middleware.py`
**Effort:** 1 hour
```python
import anyio
from starlette.middleware.base import BaseHTTPMiddleware

class TimeoutMiddleware(BaseHTTPMiddleware):
    def __init__(self, app, timeout: float = 30.0):
        super().__init__(app)
        self.timeout = timeout
    
    async def dispatch(self, request, call_next):
        try:
            with anyio.fail_after(self.timeout):
                return await call_next(request)
        except TimeoutError:
            return JSONResponse(
                status_code=504,
                content={"detail": "Request timeout"}
            )
```

---

## Phase 3: Observability (1-2 days)

### 3.1 Prometheus Metrics
**Files:** `pyproject.toml`, `src/aperion_fsal/service/metrics.py` (new)
**Effort:** 2 hours
```toml
dependencies = [..., "prometheus-client>=0.19.0"]
```
```python
from prometheus_client import Counter, Histogram, generate_latest

REQUEST_COUNT = Counter(
    'fsal_requests_total', 'Total requests',
    ['method', 'endpoint', 'status']
)
REQUEST_LATENCY = Histogram(
    'fsal_request_duration_seconds', 'Request latency',
    ['endpoint']
)
BYTES_READ = Counter('fsal_bytes_read_total', 'Bytes read')
BYTES_WRITTEN = Counter('fsal_bytes_written_total', 'Bytes written')

@app.get("/metrics")
def metrics():
    return Response(generate_latest(), media_type="text/plain")
```

### 3.2 Correlation ID Propagation
**File:** `src/aperion_fsal/service/middleware.py`
**Effort:** 1 hour
```python
import uuid
from starlette.middleware.base import BaseHTTPMiddleware

class CorrelationIDMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        correlation_id = request.headers.get("X-Correlation-ID", str(uuid.uuid4()))
        # Attach to request state for logging
        request.state.correlation_id = correlation_id
        response = await call_next(request)
        response.headers["X-Correlation-ID"] = correlation_id
        return response
```

### 3.3 Audit Log Events
**File:** `src/aperion_fsal/service/audit.py` (new)
**Effort:** 2 hours
```python
import json
from pathlib import Path
from datetime import datetime

AUDIT_LOG = Path("/var/log/fsal/audit.jsonl")

def audit_event(event_type: str, data: dict, request=None):
    event = {
        "timestamp": datetime.utcnow().isoformat(),
        "event": event_type,
        "data": data,
    }
    if request:
        event["client_ip"] = request.client.host
        event["correlation_id"] = getattr(request.state, "correlation_id", None)
    
    with AUDIT_LOG.open("a") as f:
        f.write(json.dumps(event) + "\n")
```

---

## Phase 4: Robustness (2-3 days)

### 4.1 EAFP Pattern for TOCTOU Mitigation
**File:** `src/aperion_fsal/core/safeio.py`
**Effort:** 2 hours

Refactor check-then-use patterns to try-except patterns.

### 4.2 File Locking
**File:** `src/aperion_fsal/core/locking.py` (new)
**Effort:** 2 hours
```python
import fcntl
from contextlib import contextmanager

@contextmanager
def file_lock(path: Path, exclusive: bool = True):
    lock_path = path.with_suffix(path.suffix + ".lock")
    with open(lock_path, "w") as lock_file:
        fcntl.flock(lock_file, fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH)
        try:
            yield
        finally:
            fcntl.flock(lock_file, fcntl.LOCK_UN)
```

### 4.3 Disk Space Checking
**File:** `src/aperion_fsal/core/safeio.py`
**Effort:** 1 hour
```python
import shutil

def check_disk_space(path: Path, required: int) -> bool:
    usage = shutil.disk_usage(path)
    return usage.free > required * 1.2  # 20% buffer
```

### 4.4 Configurable Limits
**File:** `src/aperion_fsal/config.py` (new)
**Effort:** 1 hour
```python
import os

class Config:
    MAX_FILE_SIZE = int(os.environ.get("FSAL_MAX_FILE_SIZE", 100 * 1024 * 1024))  # 100MB
    MAX_ENTRIES = int(os.environ.get("FSAL_MAX_ENTRIES", 10000))
    REQUEST_TIMEOUT = float(os.environ.get("FSAL_TIMEOUT", 30.0))
    RATE_LIMIT = os.environ.get("FSAL_RATE_LIMIT", "100/minute")
```

---

## Phase 5: Enhancements (Future)

### 5.1 Async File Operations with aiofiles
### 5.2 Streaming Support for Large Files
### 5.3 OpenTelemetry Tracing
### 5.4 Recursive Directory Listing
### 5.5 Content-Type Detection

---

## Testing Checkpoints

After each phase, run:

```bash
# Unit & security tests
pytest -v

# Coverage (should stay >60%)
pytest --cov=aperion_fsal --cov-report=term-missing

# Type checking
mypy src/

# Linting
ruff check src/
```

---

## Integration Testing Milestones

### Milestone 1: Basic Integration
- [ ] FSAL starts in Docker
- [ ] Can read/write via HTTP
- [ ] Health check passes

### Milestone 2: Aperion CLI Integration
- [ ] CLI commands work with standalone FSAL
- [ ] TUI displays FSAL entries

### Milestone 3: Full Production
- [ ] Metrics visible in Prometheus
- [ ] Logs in structured format
- [ ] Rate limiting active
- [ ] All security fixes applied

---

*Last updated: 2026-02-08*
