Skip to content

Commit 2dce168

Browse files
docs(governance): thin-proxy CLAUDE.md refactor (refs #75)
Replace 1833-line CLAUDE.md monolith with 75-line thin proxy that @imports governance contracts from cybermonkey/prompts submodule. Changes: - Add prompts/ git submodule (ssh://git@vhost7:9001/cybermonkey/prompts.git) - CLAUDE.md: 1833 lines → 75 lines (within 200-line budget) - .claude/rules/go-patterns.md: architecture, constants, logging, idempotency - .claude/rules/cli-patterns.md: cmd/pkg separation, flag validation, human-centric input - .claude/rules/secrets-vault.md: Vault/Consul patterns, token auth hierarchy - .claude/rules/debugging.md: diagnostic logging, evidence collection Path-scoped rules load only when touching relevant files, preventing context saturation that caused agents to ignore most of the old CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5b54809 commit 2dce168

7 files changed

Lines changed: 572 additions & 1810 deletions

File tree

.claude/rules/cli-patterns.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
---
2+
description: Eos CLI patterns — cobra commands, flag validation, human-centric input handling
3+
paths:
4+
- "cmd/**/*.go"
5+
- "pkg/interaction/**"
6+
- "pkg/verify/**"
7+
---
8+
9+
# Eos CLI Patterns
10+
11+
## Command Structure
12+
13+
Verb-first with flag-based operations:
14+
```
15+
eos [verb] [noun] --[operation] [target] [--flags...]
16+
17+
eos update hecate --add bionicgpt --dns example.com
18+
eos update vault --fix --dry-run
19+
eos delete env production --force
20+
```
21+
22+
Exception: standard CRUD verbs use positional args:
23+
```
24+
eos update services start nginx # 'start' is a verb, not an operation flag
25+
```
26+
27+
## Human-Centric Flag Handling (P0 — Breaking)
28+
29+
If a required flag is missing, NEVER fail immediately. ALWAYS offer interactive fallback with informed consent.
30+
31+
**Violation**: `if flag == "" { return fmt.Errorf("--token is required") }`
32+
33+
**Correct pattern** — use the full fallback chain:
34+
1. CLI flag (if explicitly set via `cmd.Flags().Changed()`)
35+
2. Environment variable (if configured)
36+
3. Interactive prompt (if TTY available, with help text explaining WHY and HOW)
37+
4. Default value (if `AllowEmpty` is true)
38+
5. Error with clear remediation steps (non-interactive mode only)
39+
40+
```go
41+
// CORRECT: Human-centric with fallback chain
42+
tokenFlag, _ := cmd.Flags().GetString("token")
43+
tokenWasSet := cmd.Flags().Changed("token")
44+
45+
result, err := interaction.GetRequiredString(rc, tokenFlag, tokenWasSet, &interaction.RequiredFlagConfig{
46+
FlagName: "token",
47+
EnvVarName: "VAULT_TOKEN",
48+
PromptMessage: "Enter Vault root token: ",
49+
HelpText: "Required for cluster operations. Get via: vault token create",
50+
IsSecret: true,
51+
})
52+
if err != nil {
53+
return fmt.Errorf("failed to get vault token: %w", err)
54+
}
55+
logger.Info("Using Vault token", zap.String("source", string(result.Source)))
56+
```
57+
58+
Required elements:
59+
- **Help text**: WHY is this needed? HOW to get the value?
60+
- **Source logging**: always log which fallback was used (CLI/env/prompt/default)
61+
- **Validation**: validate input, retry with clear guidance (max 3 attempts)
62+
- **Security**: `IsSecret: true` for passwords/tokens (no terminal echo)
63+
64+
## Missing Dependencies (P0 — Breaking)
65+
66+
NEVER error out immediately when a dependency is missing. ALWAYS offer informed consent to install:
67+
```go
68+
interaction.CheckDependencyWithPrompt(rc, interaction.DependencyConfig{
69+
Name: "docker",
70+
Description: "Container runtime required for service deployment",
71+
InstallCmd: "curl -fsSL https://get.docker.com | sh",
72+
AskConsent: true,
73+
})
74+
```
75+
76+
## Flag Bypass Vulnerability Prevention (P0 — Breaking)
77+
78+
Cobra's `--` separator stops flag parsing and passes everything as positional args. This bypasses safety flags.
79+
80+
**Vulnerable pattern** (user types `eos delete env prod -- --force`):
81+
- Cobra sees args: `["prod", "--force"]` — flags are NOT set
82+
- `--force` check passes silently — production deleted without confirmation
83+
84+
**MANDATORY MITIGATION**: ALL commands accepting positional arguments MUST call `verify.ValidateNoFlagLikeArgs` as the first line of `RunE`:
85+
86+
```go
87+
RunE: eos.Wrap(func(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []string) error {
88+
logger := otelzap.Ctx(rc.Ctx)
89+
90+
// CRITICAL: Detect flag-like args passed as positional (-- bypass)
91+
if err := verify.ValidateNoFlagLikeArgs(args); err != nil {
92+
return err // clear user-facing error with remediation
93+
}
94+
95+
// rest of command logic...
96+
})
97+
```
98+
99+
Affected command types (any using `cobra.ExactArgs`, `cobra.MaximumNArgs`, `cobra.MinimumNArgs`):
100+
- Safety-critical: `cmd/delete/`, `cmd/promote/` — production deletion, approval overrides
101+
- All others: `cmd/backup/`, `cmd/create/`, `cmd/update/`
102+
103+
See `pkg/verify/validators.go:ValidateNoFlagLikeArgs` for implementation.
104+
105+
## Drift Correction Pattern
106+
107+
Services that drift from canonical state (wrong permissions, config values):
108+
```
109+
eos update <service> --fix # detect and correct drift
110+
eos update <service> --fix --dry-run # preview corrections without applying
111+
```
112+
113+
NEVER create separate `eos fix <service>` commands — use `--fix` flag on existing `eos update` commands.
114+
115+
## Configuration Drift Decision
116+
117+
```
118+
Service has drifted?
119+
├─ Use: eos update <service> --fix
120+
├─ Compares: Current state vs. canonical state from eos create
121+
├─ Corrects: Permissions, ownership, config values
122+
└─ Verifies: Post-fix state matches canonical
123+
124+
Want to check only?
125+
└─ Use: eos update <service> --fix --dry-run
126+
127+
DEPRECATED: eos fix vault → use eos update vault --fix
128+
```

.claude/rules/debugging.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
description: Eos debugging patterns — diagnostic logging, debug commands, evidence collection
3+
paths:
4+
- "cmd/debug/**"
5+
- "pkg/**/*.go"
6+
---
7+
8+
# Debugging Patterns
9+
10+
## Diagnostic Logging Strategy
11+
12+
In `cmd/debug/` handlers, use two distinct output modes:
13+
14+
| Phase | Output method | Purpose |
15+
|---|---|---|
16+
| Diagnostic checks (health, config validation) | `logger.Info/Warn/Error(...)` | Structured — captured by telemetry |
17+
| Progress indicators | `logger.Debug(...)` or `logger.Info(...)` | Visible to user in real-time |
18+
| Issue detection | `logger.Warn/Error(...)` with zap fields | Structured error data |
19+
| **Final report rendering** | `fmt.Print(report.Render())` ONLY | Terminal-formatted output AFTER telemetry |
20+
21+
```go
22+
// CORRECT: cmd/debug handler pattern
23+
func runVaultDiagnostic(rc *eos_io.RuntimeContext) error {
24+
logger := otelzap.Ctx(rc.Ctx)
25+
26+
// Phase 1: diagnostics via structured logger (telemetry captured)
27+
logger.Info("Checking Vault seal status")
28+
sealed, err := vault.CheckSealStatus(rc)
29+
if err != nil {
30+
logger.Error("Failed to check seal status", zap.Error(err))
31+
}
32+
logger.Info("Vault seal status", zap.Bool("sealed", sealed))
33+
34+
// Phase 2: terminal-formatted report ONLY after all diagnostics done
35+
report := buildVaultReport(sealed, ...)
36+
fmt.Print(report.Render()) // OK here — final output only
37+
return nil
38+
}
39+
```
40+
41+
## Evidence Collection
42+
43+
When collecting diagnostic evidence, capture:
44+
1. **State**: current configuration, running services, connectivity
45+
2. **Timestamps**: when check was performed, service start times
46+
3. **Context**: environment variables (redacted secrets), config file hashes
47+
4. **Errors**: full error chains including root cause
48+
49+
```go
50+
// Evidence struct pattern
51+
type DiagnosticEvidence struct {
52+
Timestamp time.Time `json:"timestamp"`
53+
ServiceName string `json:"service_name"`
54+
Checks []CheckResult `json:"checks"`
55+
Errors []string `json:"errors"`
56+
Config map[string]string `json:"config"` // no secret values
57+
}
58+
```
59+
60+
## Debug Command Structure
61+
62+
Debug commands live in `cmd/debug/` and follow this pattern:
63+
64+
```
65+
eos debug [service] # full diagnostic check
66+
eos debug [service] --fix # diagnose and attempt auto-remediation
67+
eos debug [service] --json # machine-readable output for CI/automation
68+
```
69+
70+
Output format:
71+
- Human mode (default): coloured terminal report with summary + details
72+
- JSON mode (`--json`): structured JSON for parsing by other tools
73+
74+
## Automatic Debug Output Capture
75+
76+
For commands that call external tools (`vault`, `consul`, `docker`):
77+
```go
78+
// Capture stdout+stderr for evidence
79+
cmd := exec.CommandContext(rc.Ctx, "vault", "status")
80+
out, err := cmd.CombinedOutput()
81+
if err != nil {
82+
logger.Error("vault status failed",
83+
zap.Error(err),
84+
zap.String("output", string(out)), // attach full output
85+
)
86+
}
87+
```
88+
89+
## Anti-Patterns
90+
91+
| Anti-pattern | Why it's wrong | Do this instead |
92+
|---|---|---|
93+
| `fmt.Println("checking vault...")` in diagnostic phase | Bypasses telemetry, no structured fields | `logger.Info("checking vault status")` |
94+
| `fmt.Print(...)` in pkg/ functions | pkg/ functions have no terminal context | Return structured data, let cmd/ render |
95+
| Swallowing errors in diagnostics | Hidden failures give false-positive health | Log and continue: `logger.Warn("...", zap.Error(err))` |
96+
| `log.Fatal(...)` in pkg/ | Kills process without cleanup | Return error, let cmd/ handle exit |

.claude/rules/go-patterns.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
description: Eos Go patterns — architecture, constants, logging, idempotency, retry logic
3+
paths:
4+
- "**/*.go"
5+
- "pkg/**/*.go"
6+
---
7+
8+
# Eos Go Patterns
9+
10+
## Architecture: cmd/ vs pkg/ (P0 — Breaking)
11+
12+
**cmd/**: Orchestration ONLY.
13+
- Define `cobra.Command` with flags
14+
- Parse flags into config struct
15+
- Call `pkg/[feature]/Function(rc, config)`
16+
- Return result — NO business logic
17+
- **If cmd/ file exceeds 100 lines → move logic to pkg/**
18+
19+
**pkg/**: ALL business logic.
20+
- Pattern: **ASSESS → INTERVENE → EVALUATE**
21+
1. ASSESS: Check current state
22+
2. INTERVENE: Apply changes if needed
23+
3. EVALUATE: Verify and report results
24+
- Always use `*eos_io.RuntimeContext` for all operations
25+
26+
```go
27+
// Good cmd/ file (thin orchestration)
28+
RunE: eos.Wrap(func(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []string) error {
29+
cfg := &vault.ClusterConfig{Token: tokenFlag}
30+
return vault.UpdateCluster(rc, cfg) // all logic in pkg/
31+
})
32+
33+
// Bad cmd/ file (business logic in cmd/)
34+
RunE: func(cmd *cobra.Command, args []string) error {
35+
client := api.NewClient(...) // WRONG — this belongs in pkg/
36+
resp, err := client.Do(...) // WRONG
37+
return err
38+
}
39+
```
40+
41+
## Logging (P0 — Breaking)
42+
43+
**ALWAYS** use `otelzap.Ctx(rc.Ctx)` — structured logging goes to terminal AND telemetry.
44+
45+
**NEVER** use `fmt.Print*` / `fmt.Println` in pkg/ or cmd/ (except one exception below).
46+
47+
**Exception — cmd/debug/ final report rendering ONLY:**
48+
```go
49+
// CORRECT: diagnostics via logger, final output via fmt
50+
logger.Info("Checking Vault config") // diagnostic — telemetry captured
51+
logger.Warn("Seal status: sealed") // diagnostic
52+
fmt.Print(report.Render()) // ONLY at end, after all telemetry
53+
```
54+
55+
## Constants — Single Source of Truth (P0 — Breaking)
56+
57+
NEVER hardcode literal values. Every value must be a named constant defined in EXACTLY ONE place.
58+
59+
| Value type | Location |
60+
|------------|----------|
61+
| Port numbers | `pkg/shared/ports.go` |
62+
| Common paths | `pkg/shared/paths.go` |
63+
| Vault paths/URLs | `pkg/vault/constants.go` |
64+
| Consul paths | `pkg/consul/constants.go` |
65+
| Service-specific | `pkg/[service]/constants.go` |
66+
67+
**FORBIDDEN hardcoded values:**
68+
```go
69+
// WRONG — hardcoded everywhere
70+
os.MkdirAll("/etc/vault.d", 0755)
71+
net.Listen("tcp", "localhost:8200")
72+
exec.Command("systemctl", "start", "vault.service")
73+
74+
// CORRECT — named constants
75+
os.MkdirAll(vault.VaultConfigDir, vault.VaultDirPerm)
76+
net.Listen("tcp", fmt.Sprintf("%s:%d", shared.LocalhostIP, shared.PortVault))
77+
exec.Command("systemctl", "start", vault.VaultServiceName)
78+
```
79+
80+
**Circular import exception**: Document with `// NOTE: Duplicates B.ConstName to avoid circular import`
81+
82+
**File permissions** must have security rationale in the constant definition:
83+
```go
84+
// VaultTLSKeyPerm restricts private key access to vault user only.
85+
// RATIONALE: Private keys must not be world-readable.
86+
// SECURITY: Prevents credential theft via filesystem access.
87+
// THREAT MODEL: Mitigates insider threat and container escape attacks.
88+
const VaultTLSKeyPerm = 0600
89+
```
90+
91+
## Idempotency (P1)
92+
93+
All pkg/ operations MUST be safe to run multiple times:
94+
- Check before creating: verify state before applying changes
95+
- Use `os.MkdirAll` not `os.Mkdir` (no error if exists)
96+
- Use upsert patterns for config writes
97+
- Compare current state to desired state before modifying
98+
99+
## Retry Logic (P1)
100+
101+
**Transient failures → retry with backoff:**
102+
- Network timeouts, connection refused (service starting)
103+
- Lock contention, resource temporarily unavailable
104+
- HTTP 429/503 (rate limiting, service overloaded)
105+
106+
**Deterministic failures → fail fast, no retry:**
107+
- Config/validation errors, missing required files
108+
- Authentication failures (wrong credentials)
109+
- Permission denied
110+
111+
```go
112+
// Transient: retry
113+
err := retry.Do(func() error {
114+
return vault.CheckHealth(rc)
115+
}, retry.Attempts(5), retry.Delay(2*time.Second))
116+
117+
// Deterministic: fail fast
118+
if cfg.Token == "" {
119+
return fmt.Errorf("vault token required: %w", ErrMissingConfig)
120+
}
121+
```
122+
123+
## Error Context (P1)
124+
125+
Wrap errors with context at EVERY layer:
126+
```go
127+
// WRONG — no context
128+
return err
129+
130+
// CORRECT — context at each layer
131+
return fmt.Errorf("failed to initialize vault cluster: %w", err)
132+
```
133+
134+
User-facing errors use typed error wrappers:
135+
```go
136+
return eos_err.NewUserError("vault token expired — run: vault token renew")
137+
return eos_err.NewSystemError("vault unsealing failed", err)
138+
```
139+
140+
Capture command output in errors:
141+
```go
142+
out, err := cmd.CombinedOutput()
143+
if err != nil {
144+
return fmt.Errorf("command failed: %w\noutput: %s", err, out)
145+
}
146+
```
147+
148+
## Code Integration (P0)
149+
150+
**Before writing new code**, search for existing functionality:
151+
- `grep -r "FunctionName" pkg/` to find existing implementations
152+
- ALWAYS enhance existing functions rather than creating duplicates
153+
- NEVER create a second HTTP client for the same service — add methods to the existing one
154+
- Only deprecate functions if absolutely necessary — prefer evolution over replacement
155+
- Verify integration points: ensure new code is wired into existing callers
156+
157+
## Common Anti-Patterns
158+
159+
| Anti-pattern | Correct approach |
160+
|---|---|
161+
| `fmt.Println("done")` in pkg/ | `logger.Info("operation complete", zap.String("op", "done"))` |
162+
| New HTTP client for existing service | Add method to existing client in `pkg/[service]/client.go` |
163+
| Hardcoded `"/etc/vault.d"` | Use `vault.VaultConfigDir` constant |
164+
| `os.MkdirAll(dir, 0755)` | Use `vault.VaultDirPerm` or `consul.ConsulDirPerm` |
165+
| Business logic in `cmd/*.go` | Move to `pkg/[feature]/*.go` |
166+
| `_ = someFunc()` (discarding errors) | `if err != nil { return fmt.Errorf(...): %w", err) }` |
167+
| Standalone `*.md` docs (except ROADMAP.md, README.md) | Put in inline comments or update ROADMAP.md |

0 commit comments

Comments
 (0)