blue/.blue/docs/rfcs/0034-comprehensive-config-architecture.accepted.md
Eric Garcia 02901dfec7 chore: batch commit - ADRs, RFCs, dialogues, spikes, and code updates
ADRs:
- Update 0008-honor, 0009-courage, 0013-overflow, 0015-plausibility
- Add 0017-hosted-coding-assistant-architecture

RFCs:
- 0032: per-repo AWS profile configuration (draft)
- 0033: round-scoped dialogue files (impl + plan)
- 0034: comprehensive config architecture (accepted)
- 0036: expert output discipline (impl)
- 0037: single source protocol authority (draft)
- 0038: SDLC workflow discipline (draft)
- 0039: ADR architecture greenfield clarifications (impl)
- 0040: divorce financial analysis (draft)
- 0042: alignment dialogue defensive publication (draft)

Spikes:
- Read tool token limit on assembled dialogues
- RFC ID collision root cause
- Expert agent output too long
- Judge writes expert outputs
- Blue MCP server on superviber infrastructure
- Playwright MCP multiple window isolation

Dialogues: 16 alignment dialogue records

Code:
- blue-core: forge module enhancements
- blue-mcp: env handlers and server updates
- alignment-expert agent improvements
- alignment-play skill refinements
- install.sh script

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 16:28:31 -05:00

359 lines
11 KiB
Markdown

# RFC 0034: Comprehensive Config Architecture
| | |
|---|---|
| **Status** | Accepted |
| **Date** | 2026-01-26 |
| **Supersedes** | RFC 0032 |
| **Dialogue** | [blue-config-architecture.dialogue.recorded.md](../dialogues/2026-01-26T1829Z-blue-config-architecture.dialogue.recorded.md) |
---
## Summary
This RFC defines a comprehensive `.blue/config.yaml` architecture that serves as the single source of truth for repo-level Blue configuration. It addresses four requirements: (1) worktree initialization, (2) release constraints, (3) AWS profile per-repo, and (4) forge configuration—all unified under a versioned schema with clear separation between configuration and runtime state.
## Problem
Blue currently lacks a cohesive configuration architecture:
1. **No schema versioning** — Config changes can break existing tooling silently
2. **Worktree initialization undefined** — No spec for what happens when creating isolated environments
3. **Release constraints scattered** — Branch policies hardcoded in Rust, not configurable per-repo
4. **State/config conflation** — Unclear what belongs in config vs runtime state
RFC 0032 proposed adding AWS profile configuration but didn't address these broader architectural concerns.
## Design
### Core Principles
1. **Config declares intent** — Branch roles, dependencies, requirements
2. **Blue validates** — Pre-flight checks, warnings, guidance
3. **Forge enforces** — Branch protection, required reviews, CI gates
4. **State separation** — config.yaml (pure declarations), blue.db (runtime state)
5. **Single source** — One versioned file with semantic sections
### Schema (Version 1)
```yaml
version: 1
forge:
type: github # github | forgejo | gitlab | bitbucket
host: github.com # or self-hosted (e.g., git.example.com)
owner: superviber
repo: blue
aws:
profile: cultivarium # AWS profile from ~/.aws/config
release:
develop_branch: develop # where active development happens
main_branch: main # protected release branch
worktree:
env:
# Additional environment variables injected into .env.isolated
# LOG_LEVEL: debug
# RUST_BACKTRACE: 1
```
### Section Definitions
#### `version` (required)
Schema version for migration support. Blue validates this field and rejects unknown versions with actionable error messages.
```yaml
version: 1
```
#### `forge` (required)
Forge connection details. Blue uses these for PR creation, issue tracking, and API operations.
```yaml
forge:
type: github # github | forgejo | gitlab | bitbucket
host: github.com # API host (self-hosted URLs supported)
owner: superviber # Repository owner/organization
repo: blue # Repository name
```
**Forgejo/Gitea Note**: Forgejo uses a GitHub-compatible API for most operations. Set `type: forgejo` and `host` to your instance URL (e.g., `git.example.com`).
#### `aws` (optional)
AWS profile configuration. Inherits RFC 0032's precedence rules.
```yaml
aws:
profile: cultivarium # Profile name from ~/.aws/config
```
**Precedence**: Shell `AWS_PROFILE` > config > Blue defaults
#### `release` (optional)
Branch topology for release workflows. Blue **observes** these for intelligent operations; **Forge enforces** protection rules.
```yaml
release:
develop_branch: develop # Default PR target, RFC work branch
main_branch: main # Release target, protected branch
```
**Blue's role**: Validate PR targets, warn on policy violations, generate correct release notes.
**Forge's role**: Enforce branch protection, required reviews, CI gates.
#### `worktree` (optional)
Worktree initialization configuration. Defines environment variables injected into `.env.isolated` during `blue_env_mock`.
```yaml
worktree:
env:
AWS_PROFILE: "${aws.profile}" # Reference other config values
LOG_LEVEL: debug
RUST_BACKTRACE: 1
```
### Worktree Initialization
"Worktree initialization" means: **the operations Blue performs when creating an isolated work environment**.
1. **Read config** — Load `.blue/config.yaml`
2. **Generate `.env.isolated`** — Write environment variables from `aws.profile` and `worktree.env`
3. **Validate state** — Check branches exist, forge is reachable (warn, don't block)
This happens in `blue_env_mock` and `ensure_state()`.
### Observe vs Enforce
Blue operates in the **knowledge layer**—it knows branch policies exist, validates conformance, warns on deviations. The forge operates in the **enforcement layer**—it blocks merges, requires reviews, enforces protection rules.
| Aspect | Blue's Role | Forge's Role |
|--------|-------------|--------------|
| Branch topology | Observes, validates | Enforces protection |
| PR targets | Suggests correct branch | Rejects invalid targets |
| Direct commits to main | Warns user | Blocks if protected |
| Release workflow | Generates docs from correct branch | Enforces CI gates |
**Why not enforce in Blue?**
- Blue cannot prevent `git push --force main`—only Forge can
- Enforcement creates brittle duplication of forge logic
- Sync problems between local config and remote reality
- False sense of security
### State Separation
| Category | Location | Example |
|----------|----------|---------|
| **Declarations** | `.blue/config.yaml` | AWS profile, branch names |
| **Runtime state** | `blue.db` | Active RFC, session data |
| **Generated artifacts** | `.env.isolated` | Environment for worktree |
| **Ephemeral state** | `.blue/state/` | Lock files, cache |
Config is **read-only after load**. Rust's ownership model enforces this—pass `&BlueConfig` references, never `&mut`.
### Rust Implementation
#### 1. Config Struct
File: `crates/blue-core/src/config.rs`
```rust
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlueConfig {
pub version: u32,
pub forge: ForgeConfig,
#[serde(default)]
pub aws: Option<AwsConfig>,
#[serde(default)]
pub release: Option<ReleaseConfig>,
#[serde(default)]
pub worktree: Option<WorktreeConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForgeConfig {
#[serde(rename = "type")]
pub forge_type: String,
pub host: String,
pub owner: String,
pub repo: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AwsConfig {
pub profile: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReleaseConfig {
#[serde(default = "default_develop")]
pub develop_branch: String,
#[serde(default = "default_main")]
pub main_branch: String,
}
fn default_develop() -> String { "develop".to_string() }
fn default_main() -> String { "main".to_string() }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorktreeConfig {
#[serde(default)]
pub env: std::collections::HashMap<String, String>,
}
```
#### 2. Schema Validation
File: `crates/blue-core/src/config.rs`
```rust
impl BlueConfig {
pub fn validate(&self) -> Result<(), ConfigError> {
// Version check
if self.version != 1 {
return Err(ConfigError::UnsupportedVersion {
found: self.version,
supported: vec![1],
});
}
// Forge validation
if !["github", "forgejo", "gitlab", "bitbucket"].contains(&self.forge.forge_type.as_str()) {
return Err(ConfigError::InvalidForgeType(self.forge.forge_type.clone()));
}
Ok(())
}
}
```
#### 3. Environment Injection
File: `crates/blue-mcp/src/server.rs` in `ensure_state()`
```rust
// Inject AWS_PROFILE from config (RFC 0032 precedence: shell wins)
if let Some(aws) = &state.config.aws {
if std::env::var("AWS_PROFILE").is_err() {
std::env::set_var("AWS_PROFILE", &aws.profile);
tracing::info!(profile = %aws.profile, "AWS profile set from config");
}
}
```
#### 4. Worktree Environment Generation
File: `crates/blue-mcp/src/handlers/env.rs`
```rust
pub fn generate_env_isolated(config: &BlueConfig) -> Vec<String> {
let mut lines = vec![
"# Generated by Blue - do not edit manually".to_string(),
format!("# Source: .blue/config.yaml (version {})", config.version),
];
// AWS profile
if let Some(aws) = &config.aws {
lines.push(format!("AWS_PROFILE={}", aws.profile));
}
// Custom worktree env vars
if let Some(worktree) = &config.worktree {
for (key, value) in &worktree.env {
lines.push(format!("{}={}", key, value));
}
}
lines
}
```
### Migration Path
#### From RFC 0032 (proposed but not implemented)
No migration needed—RFC 0032 was never implemented.
#### From existing config.yaml
Existing configs without `version` field:
```yaml
# Before (version-less)
forge:
type: github
host: github.com
owner: superviber
repo: blue
```
Blue CLI provides migration command:
```bash
blue config migrate # Adds version: 1, preserves existing fields
blue config validate # Checks config-to-reality alignment
```
### Validation Timing
Blue validates config at multiple points:
| Timing | Validation | Action |
|--------|------------|--------|
| `ensure_state()` | Schema version, required fields | Error if invalid |
| `blue_env_mock` | AWS profile exists | Warn if missing |
| `blue_worktree_create` | Branches exist | Warn if missing |
| `blue_pr_create` | PR target matches policy | Warn if mismatch |
Warnings are surfaced; operations proceed. Enforcement is Forge's responsibility.
## Test Plan
- [ ] Schema v1 loads and validates correctly
- [ ] Unknown version rejected with actionable error
- [ ] `forge` section required, others optional
- [ ] AWS profile injected into environment (shell precedence honored)
- [ ] Release branch defaults applied when section missing
- [ ] `.env.isolated` generated with all worktree.env vars
- [ ] Config validation warns on missing AWS profile
- [ ] PR creation warns when target mismatches release.develop_branch
- [ ] `blue config migrate` adds version to legacy configs
- [ ] `blue config validate` checks forge reachability
## Alternatives Considered
### 1. Layered config files (`.blue/config.yaml` + `.blue/policies.yaml`)
**Rejected**: Adds operational complexity. Where do I look when things break? Single file with semantic sections provides the same separation without filesystem fragmentation.
### 2. Blue enforces branch policies
**Rejected**: Blue cannot prevent `git push --force main`. Attempting enforcement creates false security and brittle duplication of forge logic. Blue's role is validation and guidance; Forge owns enforcement.
### 3. Config contains runtime state
**Rejected**: Violates config-as-contract semantics. Git doesn't put "is working tree clean?" in `.git/config`. Config declares identity; state tracks operations.
### 4. Per-worktree config overrides
**Deferred**: Edge case raised in dialogue. Could add `.blue/worktrees/{name}/config.yaml` layering later if real need emerges. YAGNI for now.
## References
- [Alignment Dialogue: blue-config-architecture](../dialogues/2026-01-26T1829Z-blue-config-architecture.dialogue.recorded.md) — 12-expert deliberation, 100% convergence
- RFC 0032 (superseded) — Original AWS profile proposal
- `.blue/context.manifest.yaml` — Precedent for versioned YAML schemas in Blue
---
*"Configuration declares reality; validation observes consistency; tooling enforces policy at appropriate boundaries."*
— Blue