Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions .planning/REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,20 @@ Requirements for initial release: OpenClaw on single Ubuntu host.
- [x] **INST-03**: Installation streams progress in real-time
- [x] **INST-04**: Installation fails fast with clear error messages

### Secrets Management
### Secrets Management (Global - Phase 5)

- [x] **SEC-01**: User can set secrets (`clm secret set`)
- [x] **SEC-02**: User can list secret keys (`clm secret list`)
- [x] **SEC-03**: Secrets stored with mode 600, never displayed

### Per-Instance Secrets (Phase 6)

- [x] **PSEC-01**: Secrets are scoped to installed claw instances, not global
- [x] **PSEC-02**: User sets secrets per claw (`clm secret set <clawname> KEY`)
- [x] **PSEC-03**: User lists secrets grouped by claw with missing required secrets shown
- [x] **PSEC-04**: Status shows degraded state for claws with missing required secrets
- [x] **PSEC-05**: Secrets only settable for installed/initialized claws

### Fleet Status

- [x] **STAT-01**: User can view fleet status (`clm status`)
Expand Down Expand Up @@ -107,12 +115,17 @@ Which phases cover which requirements. Updated during roadmap creation.
| SEC-01 | Phase 5 | Complete |
| SEC-02 | Phase 5 | Complete |
| SEC-03 | Phase 5 | Complete |
| PSEC-01 | Phase 6 | Planned |
| PSEC-02 | Phase 6 | Planned |
| PSEC-03 | Phase 6 | Planned |
| PSEC-04 | Phase 6 | Planned |
| PSEC-05 | Phase 6 | Planned |

**Coverage:**
- v1 requirements: 17 total
- Mapped to phases: 17
- Unmapped: 0
- v1 requirements: 22 total
- Mapped to phases: 22
- Unmapped: 0

---
*Requirements defined: 2026-03-20*
*Last updated: 2026-03-21 after phase 4 planning*
*Last updated: 2026-03-22 after phase 6 planning*
27 changes: 23 additions & 4 deletions .planning/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Decimal phases appear between their surrounding integers in numeric order.
- [x] **Phase 2: Host Management** - Add, list, remove hosts with hardware capability detection (completed 2026-03-21)
- [x] **Phase 3: Registry & Compatibility** - Load claw manifests and validate hardware compatibility (completed 2026-03-21)
- [x] **Phase 4: Installation & Fleet Status** - Install OpenClaw instances and view fleet status (completed 2026-03-22)
- [ ] **Phase 5: Secrets Management** - Secure storage and retrieval of API keys and credentials
- [x] **Phase 5: Secrets Management** - Secure storage and retrieval of API keys and credentials (completed 2026-03-22)
- [ ] **Phase 6: Per-Instance Secrets** - Refactor secrets from global scope to per-claw-instance scope

## Phase Details

Expand Down Expand Up @@ -100,15 +101,33 @@ Plans:
- [x] 05-01-PLAN.md — Core secrets storage module and manifest schema extension (SEC-01, SEC-03)
- [x] 05-02-PLAN.md — CLI secret commands (set, list, remove) (SEC-01, SEC-02, SEC-03)

### Phase 6: Per-Instance Secrets
**Goal**: Each installed claw has its own set of secrets; secrets are no longer global
**Depends on**: Phase 5
**Requirements**: PSEC-01, PSEC-02, PSEC-03, PSEC-04, PSEC-05
**Success Criteria** (what must be TRUE):
1. User runs `clm secret set <clawname> KEY` and secret is stored for that specific claw instance
2. User runs `clm secret list` and sees secrets grouped by claw with missing required secrets per claw
3. Same secret key can have different values per claw instance (e.g., different OPENAI_API_KEY per claw)
4. Secrets can only be set for installed claws (validation enforced)
5. `clm status` shows degraded state for claws with missing required secrets
**Plans**: 3 plans

Plans:
- [x] 06-01-PLAN.md — Refactor core secrets module for per-instance scoping (PSEC-01, PSEC-05)
- [x] 06-02-PLAN.md — Update CLI commands for per-claw secrets (PSEC-02, PSEC-03)
- [x] 06-03-PLAN.md — Update status display for degraded state (PSEC-04)

## Progress

**Execution Order:**
Phases execute in numeric order: 1 → 2 → 3 → 4 → 5
Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6

| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Foundation Setup | 2/2 | Complete | 2026-03-21 |
| 1. Foundation Setup | 2/2 | Complete | 2026-03-21 |
| 2. Host Management | 4/4 | Complete | 2026-03-21 |
| 3. Registry & Compatibility | 4/4 | Complete | 2026-03-21 |
| 4. Installation & Fleet Status | 4/4 | Complete | 2026-03-22 |
| 5. Secrets Management | 0/2 | Planning complete | - |
| 5. Secrets Management | 2/2 | Complete | 2026-03-22 |
| 6. Per-Instance Secrets | 1/3 | In Progress| |
31 changes: 22 additions & 9 deletions .planning/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
status: unknown
stopped_at: Completed 05-02-PLAN.md
last_updated: "2026-03-22T21:26:17.610Z"
stopped_at: Completed 06-02-PLAN.md
last_updated: "2026-03-22T22:44:02.664Z"
last_activity: 2026-03-22
progress:
total_phases: 5
completed_phases: 5
total_plans: 16
completed_plans: 16
total_phases: 6
completed_phases: 6
total_plans: 19
completed_plans: 19
---

# Project State
Expand All @@ -20,11 +20,11 @@ progress:
See: .planning/PROJECT.md (updated 2026-03-20)

**Core value:** Users can manage all their AI assistants from one place with consistent configuration and security practices.
**Current focus:** Phase 05 — secrets-management
**Current focus:** Phase 06make-secrets-specific-to-installed-claws-not-global

## Current Position

Phase: 05
Phase: 06
Plan: Not started

## Performance Metrics
Expand Down Expand Up @@ -57,6 +57,9 @@ Plan: Not started
| Phase 04-installation-fleet-status P04 | 128 | 1 tasks | 3 files |
| Phase 05 P01 | 189 | 2 tasks | 4 files |
| Phase 05 P02 | 209 | 2 tasks | 3 files |
| Phase 06-make-secrets-specific-to-installed-claws-not-global P01 | 427 | 1 tasks | 4 files |
| Phase 06 P03 | 336 | 2 tasks | 4 files |
| Phase 06 P02 | 411 | 2 tasks | 3 files |

## Accumulated Context

Expand Down Expand Up @@ -94,6 +97,12 @@ Recent decisions affecting current work:
- [Phase 05]: Use same locking and atomic write patterns as hosts.py for consistency
- [Phase 05]: Secrets stored as dict[str, SecretEntry] for O(1) key lookup
- [Phase 05]: Named CLI functions set_cmd/list_cmd/remove_cmd to avoid shadowing Python builtins
- [Phase 06-make-secrets-specific-to-installed-claws-not-global]: Nested JSON structure for per-instance secrets: {instance_key: {secret_key: SecretEntry}}
- [Phase 06-make-secrets-specific-to-installed-claws-not-global]: Instance key format: host:claw_type:claw_name (colon-separated)
- [Phase 06-make-secrets-specific-to-installed-claws-not-global]: Legacy __global__ namespace for backward compatibility with Phase 05 CLI commands
- [Phase 06-03]: DEGRADED status added to ClawStatus enum for running claws with missing required secrets
- [Phase 06-03]: Degraded state displayed in yellow to indicate warning (not critical failure)
- [Phase 06-02]: CLI commands now require claw_name as first positional argument for per-instance scoping

### Pending Todos

Expand All @@ -112,8 +121,12 @@ None yet.
| 260321-jld | Fix hardware detection ansible-runner issue #3 | 2026-03-21 | fe4561d | [260321-jld-fix-hardware-detection-ansible-runner-ne](./quick/260321-jld-fix-hardware-detection-ansible-runner-ne/) |
| 260321-jux | ATX review fixes: SSH key validation, test coverage, GPU detection | 2026-03-21 | 397640b | [260321-jux-atx-review-fixes-ssh-key-validation-test](./quick/260321-jux-atx-review-fixes-ssh-key-validation-test/) |

### Roadmap Evolution

- Phase 6 added: make secrets specific to installed claws, not global

## Session Continuity

Last activity: 2026-03-22
Stopped at: Completed 05-02-PLAN.md
Stopped at: Completed 06-02-PLAN.md
Resume file: None
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
---
phase: 06-make-secrets-specific-to-installed-claws-not-global
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/clawrium/core/secrets.py
- tests/test_secrets.py
autonomous: true
requirements:
- PSEC-01
- PSEC-05

must_haves:
truths:
- "Secrets are stored per claw instance, not globally"
- "Instance identity is host:claw_type:claw_name format"
- "Same secret key can exist with different values per instance"
- "Secrets can only be set for installed claws"
artifacts:
- path: "src/clawrium/core/secrets.py"
provides: "Per-instance secret storage with nested JSON structure"
exports: ["load_secrets", "save_secrets", "get_instance_secrets", "set_instance_secret", "remove_instance_secret", "list_instances_with_secrets", "get_instance_key"]
- path: "tests/test_secrets.py"
provides: "Tests for per-instance secret operations"
contains: "test_set_instance_secret"
key_links:
- from: "src/clawrium/core/secrets.py"
to: "secrets.json"
via: "nested dict with instance keys"
pattern: "host:claw_type:claw_name"
---

<objective>
Refactor secrets module from global to per-claw-instance scoping.

Purpose: Each installed claw instance needs its own secrets (e.g., different OPENAI_API_KEY per claw), enabling fleet-wide secret management where the same key can have different values per instance.

Output: Updated core/secrets.py with nested JSON storage structure and instance-scoped operations.
</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

<interfaces>
<!-- Key types and contracts from existing codebase -->

From src/clawrium/core/secrets.py (current global implementation to refactor):
```python
class SecretEntry(TypedDict):
key: str
value: str
created_at: str # ISO 8601 timestamp
updated_at: str # ISO 8601 timestamp
description: str

def load_secrets() -> dict[str, SecretEntry]: ...
def save_secrets(secrets: dict[str, SecretEntry]) -> None: ...
def get_secret(key: str) -> SecretEntry | None: ...
def set_secret(key: str, value: str, description: str = "", *, strict: bool = False) -> bool: ...
def remove_secret(key: str) -> bool: ...
def list_secrets() -> list[str]: ...
def validate_secret_key(key: str) -> str: ...
```

From src/clawrium/core/hosts.py (for getting installed claws):
```python
def load_hosts() -> list[dict]: ...
def get_host(identifier: str) -> dict | None: ...
# Host dict has: {"hostname": str, "claws": {"openclaw": {"status": "installed", "user": str, ...}}, ...}
```

Storage format per D-07, D-08, D-09:
```json
{
"wolf:openclaw:work": {
"OPENAI_API_KEY": {"key": "OPENAI_API_KEY", "value": "...", "created_at": "...", "updated_at": "...", "description": "..."}
},
"wolf:openclaw:personal": {
"OPENAI_API_KEY": {"key": "OPENAI_API_KEY", "value": "...", ...}
}
}
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
<name>Task 1: Refactor secrets module for per-instance storage</name>
<files>src/clawrium/core/secrets.py, tests/test_secrets.py</files>
<read_first>
- src/clawrium/core/secrets.py (current implementation to refactor)
- src/clawrium/core/hosts.py (for get_host, load_hosts patterns)
- tests/test_secrets.py (existing test patterns to update)
</read_first>
<behavior>
- Test: get_instance_key("wolf", "openclaw", "work") returns "wolf:openclaw:work"
- Test: set_instance_secret("wolf:openclaw:work", "OPENAI_API_KEY", "sk-123") creates nested entry
- Test: get_instance_secrets("wolf:openclaw:work") returns dict of SecretEntry for that instance
- Test: set same key on different instances stores different values
- Test: remove_instance_secret removes only from specified instance
- Test: list_instances_with_secrets returns list of instance keys that have secrets
- Test: get_installed_claw raises error if claw not installed (validates against hosts.json)
- Test: file permissions remain 0o600
</behavior>
<action>
1. Add new function `get_instance_key(host: str, claw_type: str, claw_name: str) -> str`:
- Returns f"{host}:{claw_type}:{claw_name}" per D-08

2. Add new function `get_installed_claw(claw_name: str) -> tuple[str, str, str]`:
- Search all hosts in load_hosts() for a claw with matching name (from claws dict)
- Claw name is in host["claws"][claw_type]["user"] which follows pattern prefix-clawname
- Actually per D-13, lookup by claw_name which is unique across fleet
- For each host, check each claw_type in host.get("claws", {})
- The claw "name" is stored in the "user" field minus the prefix (e.g., "opc-work" -> "work")
- Raise ClawNotFoundError if no installed claw matches
- Return (hostname, claw_type, claw_name)

3. Add class `ClawNotFoundError(Exception)` for when claw name not found

4. Modify `load_secrets() -> dict[str, dict[str, SecretEntry]]`:
- Change return type from dict[str, SecretEntry] to dict[str, dict[str, SecretEntry]]
- Top-level keys are instance keys (host:claw_type:claw_name)
- Second-level keys are secret keys (OPENAI_API_KEY, etc.)
- Delete old secrets.json on first access per D-06 (clean break)

5. Keep `save_secrets(secrets: dict[str, dict[str, SecretEntry]])`:
- Same atomic write pattern with fcntl locking
- File permissions 0o600

6. Add `get_instance_secrets(instance_key: str) -> dict[str, SecretEntry]`:
- Load all secrets, return secrets.get(instance_key, {})

7. Add `set_instance_secret(instance_key: str, key: str, value: str, description: str = "") -> bool`:
- Validate key with validate_secret_key()
- Load secrets, create instance dict if not exists
- Set or update SecretEntry at secrets[instance_key][key]
- Return True if created, False if updated

8. Add `remove_instance_secret(instance_key: str, key: str) -> bool`:
- Validate key with validate_secret_key()
- Load secrets, remove key from instance dict if exists
- Return True if removed, False if not found

9. Add `list_instances_with_secrets() -> list[str]`:
- Return sorted list of instance keys that have at least one secret

10. Keep old functions but deprecate (mark for removal):
- Keep get_secret, set_secret, remove_secret, list_secrets as stubs that raise DeprecatedError
- This ensures any lingering references fail fast

11. Update __all__ to export new functions:
- Add: get_instance_key, get_installed_claw, get_instance_secrets, set_instance_secret, remove_instance_secret, list_instances_with_secrets, ClawNotFoundError
- Keep: load_secrets, save_secrets, validate_secret_key, SECRETS_FILE, SecretEntry, SecretsFileCorruptedError, InvalidSecretKeyError

12. Update tests/test_secrets.py:
- Update existing tests to use new nested structure
- Add tests for get_instance_key
- Add tests for instance-scoped set/get/remove/list
- Add test for same key different values per instance
- Add test for ClawNotFoundError when claw not installed
</action>
<verify>
<automated>cd /home/devashish/workspace/ric03uec/clawrium && make test</automated>
</verify>
<acceptance_criteria>
- src/clawrium/core/secrets.py contains `def get_instance_key(`
- src/clawrium/core/secrets.py contains `def get_instance_secrets(`
- src/clawrium/core/secrets.py contains `def set_instance_secret(`
- src/clawrium/core/secrets.py contains `def remove_instance_secret(`
- src/clawrium/core/secrets.py contains `def list_instances_with_secrets(`
- src/clawrium/core/secrets.py contains `class ClawNotFoundError`
- tests/test_secrets.py contains `test_get_instance_key`
- tests/test_secrets.py contains `test_set_instance_secret`
- tests/test_secrets.py contains `test_same_key_different_instances`
- `make test` exits with code 0
</acceptance_criteria>
<done>
- Per-instance secret storage functions implemented
- Instance key format is host:claw_type:claw_name
- Same secret key stores different values per instance
- Old global functions deprecated
- All tests pass
</done>
</task>

</tasks>

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

<success_criteria>
- Per-instance secret storage implemented with nested JSON structure
- Instance key format: host:claw_type:claw_name
- Same key can have different values per instance
- File permissions remain 0o600
- All tests pass
- Code passes linting
</success_criteria>

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