---
phase: 06-make-secrets-specific-to-installed-claws-not-global
plan: 03
type: execute
wave: 2
depends_on:
  - 06-01
files_modified:
  - src/clawrium/cli/status.py
  - src/clawrium/core/health.py
  - tests/test_cli_status.py
  - tests/test_health.py
autonomous: true
requirements:
  - PSEC-04

must_haves:
  truths:
    - "Status shows degraded state for claws with missing required secrets"
    - "Degraded message lists specific missing secret keys"
    - "Running claws with all secrets show as running, not degraded"
  artifacts:
    - path: "src/clawrium/cli/status.py"
      provides: "Fleet status with degraded state display"
      contains: "degraded"
    - path: "src/clawrium/core/health.py"
      provides: "Health check with secrets validation"
      exports: ["ClawStatus"]
    - path: "tests/test_cli_status.py"
      provides: "Tests for degraded state display"
      contains: "test_status_degraded"
  key_links:
    - from: "src/clawrium/cli/status.py"
      to: "src/clawrium/core/secrets.py"
      via: "get_instance_secrets for missing secrets check"
      pattern: "get_instance_secrets\\("
    - from: "src/clawrium/cli/status.py"
      to: "src/clawrium/core/registry.py"
      via: "get_required_secrets for manifest lookup"
      pattern: "get_required_secrets\\("
---

<objective>
Add degraded state to fleet status for claws with missing required secrets.

Purpose: Users need to see at a glance which claws are missing required secrets so they know to run `clm secret set`. Per D-15, claws with missing secrets show as 'degraded' with a message listing the missing keys.

Output: Updated status.py and health.py with degraded state handling.
</objective>

<execution_context>
@/home/devashish/workspace/ric03uec/clawrium/.claude/get-shit-done/workflows/execute-plan.md
@/home/devashish/workspace/ric03uec/clawrium/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/06-make-secrets-specific-to-installed-claws-not-global/06-CONTEXT.md
@.planning/phases/06-make-secrets-specific-to-installed-claws-not-global/06-01-SUMMARY.md

<interfaces>
<!-- From 06-01-PLAN and existing code -->

From src/clawrium/core/secrets.py (after 06-01):
```python
def get_instance_key(host: str, claw_type: str, claw_name: str) -> str: ...
def get_instance_secrets(instance_key: str) -> dict[str, SecretEntry]: ...
```

From src/clawrium/core/registry.py:
```python
def get_required_secrets(claw_name: str) -> list[SecretDefinition]:
    """Returns [{"key": "OPENAI_API_KEY", "description": "..."}]"""
```

From src/clawrium/core/health.py:
```python
class ClawStatus(str, Enum):
    RUNNING = "running"
    STOPPED = "stopped"
    UNKNOWN = "unknown"
    NOT_INSTALLED = "not_installed"
    # Need to add: DEGRADED = "degraded"

class HealthResult(TypedDict):
    claw: str
    host: str
    status: ClawStatus
    user: str | None
    error: str | None
    # Need to add: missing_secrets: list[str] | None
```

From src/clawrium/cli/status.py:
```python
def status(host: Optional[str] = ...) -> None:
    # Displays table with Host, Version, User, Status, Installed columns
    # Need to add degraded state display with missing secrets
```

Degraded state per D-15:
- If claw is running but missing required secrets -> show "degraded (missing: KEY1, KEY2)"
- Color: [yellow] for degraded
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Add DEGRADED status and missing_secrets to health module</name>
  <files>src/clawrium/core/health.py, tests/test_health.py</files>
  <read_first>
    - src/clawrium/core/health.py (current health check implementation)
    - src/clawrium/core/secrets.py (after 06-01 for instance functions)
    - src/clawrium/core/registry.py (get_required_secrets)
    - tests/test_health.py (existing test patterns)
  </read_first>
  <behavior>
    - Test: ClawStatus.DEGRADED exists as enum value "degraded"
    - Test: HealthResult has missing_secrets field
    - Test: check_claw_health returns DEGRADED when claw running but missing required secrets
    - Test: check_claw_health returns RUNNING when claw running with all required secrets
    - Test: missing_secrets contains list of missing key names
  </behavior>
  <action>
    1. Add DEGRADED to ClawStatus enum:
       ```python
       class ClawStatus(str, Enum):
           RUNNING = "running"
           STOPPED = "stopped"
           UNKNOWN = "unknown"
           NOT_INSTALLED = "not_installed"
           DEGRADED = "degraded"  # Running but missing required secrets
       ```

    2. Update HealthResult TypedDict:
       ```python
       class HealthResult(TypedDict):
           claw: str
           host: str
           status: ClawStatus
           user: str | None
           error: str | None
           missing_secrets: list[str] | None  # List of missing required secret keys
       ```

    3. Add imports to health.py:
       ```python
       from clawrium.core.secrets import get_instance_key, get_instance_secrets
       from clawrium.core.registry import get_required_secrets
       ```

    4. Add helper function `get_missing_secrets(claw_type: str, host: dict, claw_record: dict) -> list[str]`:
       ```python
       def get_missing_secrets(claw_type: str, host: dict, claw_record: dict) -> list[str]:
           """Check which required secrets are missing for a claw instance."""
           # Get claw name from user field (e.g., "opc-work" -> "work")
           claw_user = claw_record.get("user", "")
           # Extract name part after prefix (opc-, zc-, nc-)
           claw_name = claw_user.split("-", 1)[1] if "-" in claw_user else claw_user

           instance_key = get_instance_key(host["hostname"], claw_type, claw_name)
           instance_secrets = get_instance_secrets(instance_key)

           required = get_required_secrets(claw_type)
           missing = [s["key"] for s in required if s["key"] not in instance_secrets]

           return missing
       ```

    5. Modify check_claw_health to check secrets after process check:
       - After determining RUNNING status, call get_missing_secrets()
       - If missing list is non-empty:
         - Set status to DEGRADED
         - Set missing_secrets to the list
       - If no missing secrets:
         - Keep status as RUNNING
         - Set missing_secrets to None
       - For other statuses (STOPPED, UNKNOWN, NOT_INSTALLED):
         - Set missing_secrets to None (not relevant)

    6. Update all return statements in check_claw_health to include missing_secrets field

    7. Update tests/test_health.py:
       - Add test_claw_status_degraded_exists
       - Add test_check_claw_health_degraded_when_missing_secrets (mock secrets to be empty)
       - Add test_check_claw_health_running_when_all_secrets_present
       - Add test_missing_secrets_field_populated
  </action>
  <verify>
    <automated>cd /home/devashish/workspace/ric03uec/clawrium && python -m pytest tests/test_health.py -v</automated>
  </verify>
  <acceptance_criteria>
    - src/clawrium/core/health.py contains `DEGRADED = "degraded"`
    - src/clawrium/core/health.py contains `missing_secrets: list[str] | None`
    - src/clawrium/core/health.py contains `def get_missing_secrets(`
    - src/clawrium/core/health.py contains `get_instance_secrets(`
    - tests/test_health.py contains `test_claw_status_degraded`
    - tests/test_health.py contains `test_check_claw_health_degraded`
    - `pytest tests/test_health.py` exits with code 0
  </acceptance_criteria>
  <done>
    - DEGRADED status added to ClawStatus enum
    - HealthResult includes missing_secrets field
    - check_claw_health returns DEGRADED when missing required secrets
    - Health tests pass
  </done>
</task>

<task type="auto">
  <name>Task 2: Update status CLI to display degraded state</name>
  <files>src/clawrium/cli/status.py, tests/test_cli_status.py</files>
  <read_first>
    - src/clawrium/cli/status.py (current status display)
    - src/clawrium/core/health.py (after Task 1 with DEGRADED)
    - tests/test_cli_status.py (existing test patterns)
  </read_first>
  <action>
    1. Update status display in status() function to handle DEGRADED:
       - After line `elif live_status == ClawStatus.STOPPED:` add:
       ```python
       elif live_status == ClawStatus.DEGRADED:
           # Get missing secrets from health result
           missing = health_results.get((claw_name, h["hostname"]), {}).get("missing_secrets", [])
           if missing:
               missing_str = ", ".join(missing[:3])  # Show first 3
               if len(missing) > 3:
                   missing_str += f" +{len(missing) - 3} more"
               status_display = f"[yellow]degraded (missing: {missing_str})[/yellow]"
           else:
               status_display = "[yellow]degraded[/yellow]"
       ```

    2. Actually, the health_results dict stores ClawStatus, not full HealthResult.
       Need to change the dict to store full HealthResult:
       ```python
       # Change from:
       health_results: dict[tuple[str, str], ClawStatus] = {}
       # To:
       health_results: dict[tuple[str, str], HealthResult] = {}

       # And update the loop:
       result = check_claw_health(claw_name, h)
       health_results[(claw_name, h["hostname"])] = result  # Store full result
       ```

    3. Update status display logic to use result["status"] and result.get("missing_secrets"):
       ```python
       result = health_results.get((claw_name, h["hostname"]))
       if result:
           live_status = result["status"]
           missing_secrets = result.get("missing_secrets", [])
       else:
           live_status = ClawStatus.UNKNOWN
           missing_secrets = []

       if live_status == ClawStatus.RUNNING:
           status_display = "[green]running[/green]"
       elif live_status == ClawStatus.DEGRADED:
           if missing_secrets:
               missing_str = ", ".join(missing_secrets[:3])
               if len(missing_secrets) > 3:
                   missing_str += f" +{len(missing_secrets) - 3} more"
               status_display = f"[yellow]degraded (missing: {missing_str})[/yellow]"
           else:
               status_display = "[yellow]degraded[/yellow]"
       elif live_status == ClawStatus.STOPPED:
           status_display = "[red]stopped[/red]"
       # ... rest of conditions
       ```

    4. Import HealthResult from health module if not already imported

    5. Update tests/test_cli_status.py:
       - Add test_status_degraded_shows_missing_secrets
       - Mock check_claw_health to return DEGRADED with missing_secrets
       - Verify output contains "degraded" and missing key names
  </action>
  <verify>
    <automated>cd /home/devashish/workspace/ric03uec/clawrium && python -m pytest tests/test_cli_status.py -v</automated>
  </verify>
  <acceptance_criteria>
    - src/clawrium/cli/status.py contains `ClawStatus.DEGRADED`
    - src/clawrium/cli/status.py contains `missing_secrets`
    - src/clawrium/cli/status.py contains `degraded (missing:`
    - tests/test_cli_status.py contains `test_status_degraded`
    - `pytest tests/test_cli_status.py` exits with code 0
  </acceptance_criteria>
  <done>
    - Status displays degraded state with missing secret keys
    - Running claws with all secrets show as running
    - Status tests pass
  </done>
</task>

</tasks>

<verification>
```bash
cd /home/devashish/workspace/ric03uec/clawrium
make test
make lint
```
</verification>

<success_criteria>
- ClawStatus.DEGRADED enum value exists
- HealthResult includes missing_secrets field
- check_claw_health returns DEGRADED when missing required secrets
- `clm status` shows degraded state with missing secret keys
- All tests pass
- Code passes linting
</success_criteria>

<output>
After completion, create `.planning/phases/06-make-secrets-specific-to-installed-claws-not-global/06-03-SUMMARY.md`
</output>
