Updates RFC 0001 to use the correct conceptual model: - Domain = coordination context (edge/relationship between repos) - Repo = git repository (node) - Realm = groups related domains - Index = local registry of realms Key changes: - Sessions register by repo name, track active domains - Worktrees created across repos participating in a domain - Contracts belong to domains, repos provide/consume them - Tool params changed: --domains for context, --repos for participants - Implementation code updated to reflect correct model Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
39 KiB
RFC 0001: Cross-Repo Coordination with Realms
| Status | Draft |
| Created | 2026-01-24 |
| Source | Spike: cross-repo-coordination |
| Dialogue | cross-repo-realms.dialogue.md |
Problem
We have repositories under different ownership that depend on each other:
aperture(training-tools webapp) needs S3 access to data in another AWS accountfungal-image-analysisgrants that access via IAM policies
When aperture adds a new S3 path, fungal's IAM policy must update. Currently:
- No awareness - Blue in aperture doesn't know fungal exists
- No coordination - Changes require manual cross-repo communication
- No tracking - No record of cross-repo dependencies
Goals
- Awareness - Blue sessions know about related repos and their dependencies
- Coordination - Changes in one repo trigger notifications in dependent repos
- Trust boundaries - Different orgs can coordinate without shared write access
- Auditability - All cross-repo coordination is version-controlled
Non-Goals
- Automatic code changes across repos (manual review required)
- Public realm discovery (future scope)
- Monorepo support (for MVP, each repo is a distinct participant)
Proposal
Hierarchy
Index (~/.blue/index.yaml)
└── Realm (git repo)
└── Domain (coordination context)
├── Repo A (participant)
└── Repo B (participant)
| Level | Purpose | Storage |
|---|---|---|
| Index | List of realms user participates in | ~/.blue/index.yaml |
| Realm | Groups related coordination domains | Git repository |
| Domain | Coordination context between repos | Directory in realm repo |
| Repo | Actual code repository (can participate in multiple domains) | .blue/ directory |
Key insight: A domain is the relationship (edge), not the thing (node). Repos are nodes; domains are edges connecting them.
┌─────────────────────────────────────────┐
│ Realm: letemcook │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Domain: s3-access │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ aperture │◄─►│ fungal │ │ │
│ │ │ (export) │ │ (import) │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ │ │ │
│ └─────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Domain: training-pipeline │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ fungal │◄─►│ ml-infra │ │ │
│ │ │ (export) │ │ (import) │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ │ │ │
│ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────┘
Note: fungal participates in both domains with different roles.
Realm Structure
A realm is a git repository:
realm-letemcook/
├── realm.yaml # Realm metadata and governance
├── repos/ # Registry of all participating repos
│ ├── aperture.yaml # Repo metadata (path, maintainers)
│ └── fungal-image-analysis.yaml
├── domains/
│ ├── s3-access/ # A coordination context
│ │ ├── domain.yaml # What is being coordinated
│ │ ├── contracts/
│ │ │ └── s3-permissions.yaml # The contract schema + value
│ │ └── bindings/
│ │ ├── aperture.yaml # aperture exports this contract
│ │ └── fungal.yaml # fungal imports this contract
│ │
│ └── training-pipeline/ # Another coordination context
│ ├── domain.yaml
│ ├── contracts/
│ │ └── job-schema.yaml
│ └── bindings/
│ ├── fungal.yaml # fungal exports here
│ └── ml-infra.yaml # ml-infra imports here
└── .github/
└── CODEOWNERS
realm.yaml
name: letemcook
version: "1.0.0"
created_at: 2026-01-24T10:00:00Z
governance:
admission: approval # open | approval | invite-only
approvers:
- eric@example.com
breaking_changes:
require_approval: true
grace_period_days: 14
repos/{repo}.yaml
Each participating repo is registered:
# repos/aperture.yaml
name: aperture
org: cultivarium # Optional org prefix
path: /Users/ericg/letemcook/aperture
# Or remote:
# url: git@github.com:cultivarium/aperture.git
maintainers:
- eric@example.com
joined_at: 2026-01-24T10:00:00Z
# repos/fungal-image-analysis.yaml
name: fungal-image-analysis
org: fungal-org
path: /Users/ericg/letemcook/fungal-image-analysis
maintainers:
- fungal-maintainer@example.com
joined_at: 2026-01-24T10:00:00Z
domains/{domain}/domain.yaml
Describes what is being coordinated:
# domains/s3-access/domain.yaml
name: s3-access
description: Coordinates S3 bucket access between aperture and fungal
created_at: 2026-01-24T10:00:00Z
# Which repos participate in this coordination
members:
- aperture
- fungal-image-analysis
domains/{domain}/contracts/{contract}.yaml
The actual contract being coordinated:
# domains/s3-access/contracts/s3-permissions.yaml
name: s3-permissions
version: "1.3.0"
description: S3 paths that need cross-account access
schema:
type: object
properties:
read:
type: array
items: { type: string }
write:
type: array
items: { type: string }
value:
read:
- "jobs/*/masks/*"
- "jobs/*/*/config.json"
- "jobs/*/*/manifest.json"
- "training-runs/*"
- "training-metrics/*"
write:
- "jobs/*/*/manifest.json"
- "training-metrics/*"
changelog:
- version: "1.3.0"
date: 2026-01-24
changes:
- "Added training-metrics/* for experiment tracking"
domains/{domain}/bindings/{repo}.yaml
How each repo relates to the contracts in this domain:
# domains/s3-access/bindings/aperture.yaml
repo: aperture
role: provider # This repo defines/exports the contract
exports:
- contract: s3-permissions
source_files:
- models/training/s3_paths.py
- models/shared/data/parquet_export.py
# domains/s3-access/bindings/fungal.yaml
repo: fungal-image-analysis
role: consumer # This repo implements/imports the contract
imports:
- contract: s3-permissions
version: ">=1.0.0"
binding: cdk/training_tools_access_stack.py
status: current
resolved_version: "1.3.0"
resolved_at: 2026-01-24T12:00:00Z
Local Configuration
Each repo stores its realm participation:
# aperture/.blue/config.yaml
realm:
name: letemcook
path: ../realm-letemcook
repo: aperture
# Domains this repo participates in (populated by realm join/sync)
domains:
- name: s3-access
role: provider
contracts:
- s3-permissions
Index File
# ~/.blue/index.yaml
realms:
- name: letemcook
path: /Users/ericg/letemcook/realm-letemcook
- name: ml-infra
url: git@github.com:org/realm-ml-infra.git
local_path: /Users/ericg/.blue/realms/ml-infra
Session Coordination (IPC/Socket)
Blue MCP servers communicate directly for real-time session awareness.
Architecture:
┌─────────────────────┐ IPC Socket ┌─────────────────────┐
│ Blue MCP Server │◄──────────────────►│ Blue MCP Server │
│ (aperture) │ │ (fungal) │
│ │ │ │
│ Socket: /tmp/blue/ │ │ Socket: /tmp/blue/ │
│ aperture.sock │ │ fungal.sock │
└─────────────────────┘ └─────────────────────┘
│ │
└──────────────┬────────────────────────────┘
│
▼
┌───────────────────────┐
│ Realm Session Index │
│ /tmp/blue/letemcook/ │
│ sessions.json │
└───────────────────────┘
Session Registration:
// /tmp/blue/letemcook/sessions.json
{
"sessions": [
{
"repo": "aperture",
"socket": "/tmp/blue/aperture.sock",
"pid": 12345,
"started_at": "2026-01-24T10:00:00Z",
"active_rfc": "training-metrics-v2",
"active_domains": ["s3-access"],
"exports_modified": ["s3-permissions"]
},
{
"repo": "fungal-image-analysis",
"socket": "/tmp/blue/fungal.sock",
"pid": 12346,
"started_at": "2026-01-24T10:05:00Z",
"active_rfc": null,
"active_domains": ["s3-access"],
"imports_watching": ["s3-permissions"]
}
]
}
Real-time Notifications:
When aperture modifies an export, it broadcasts to all sessions watching that export:
// Session broadcast
fn notify_export_change(export_name: &str, changes: &ExportChanges) {
let sessions = load_realm_sessions();
for session in sessions.watching(export_name) {
send_ipc_message(&session.socket, Message::ExportChanged {
from_repo: self.repo.clone(),
contract: export_name.to_string(),
changes: changes.clone(),
});
}
}
Receiving Notifications:
# In fungal-image-analysis terminal (real-time)
$ blue status
📊 fungal-image-analysis (in realm: letemcook)
🔔 Live notification from repo 'aperture':
Contract 's3-permissions' in domain 's3-access' changed:
+ training-metrics/experiments/*
Your import is now stale.
Run 'blue realm worktree --domain s3-access' to start coordinated changes.
Unified Worktrees (Cross-Repo Branches)
When changes span multiple repos, create worktrees in all affected repos simultaneously with a shared branch name.
Workflow:
# In aperture: start cross-repo change
$ blue realm worktree --rfc training-metrics-v2
Creating unified worktree 'feat/training-metrics-v2':
✓ aperture: .worktrees/feat-training-metrics-v2/
✓ fungal-image-analysis: .worktrees/feat-training-metrics-v2/
Branch 'feat/training-metrics-v2' created in both repos.
You are now in aperture worktree.
Related worktree:
cd /Users/ericg/letemcook/fungal-image-analysis/.worktrees/feat-training-metrics-v2
Realm Worktree Tracking:
# realm-letemcook/worktrees/feat-training-metrics-v2.yaml
name: feat/training-metrics-v2
created_at: 2026-01-24T10:00:00Z
source_rfc: aperture:training-metrics-v2
domain: s3-access # The coordination context this change affects
repos:
- name: aperture
path: /Users/ericg/letemcook/aperture/.worktrees/feat-training-metrics-v2
status: active
commits: 3
- name: fungal-image-analysis
path: /Users/ericg/letemcook/fungal-image-analysis/.worktrees/feat-training-metrics-v2
status: active
commits: 1
coordination:
# Commits are linked across repos
commits:
- aperture: abc1234
fungal-image-analysis: null
message: "feat: add experiment metrics export"
- aperture: def5678
fungal-image-analysis: ghi9012
message: "feat: update IAM for experiment metrics"
linked: true # These commits are coordinated
Coordinated Commits:
# After making changes in both worktrees
$ blue realm commit -m "feat: add experiment metrics with IAM support"
Coordinated commit across repos in domain 's3-access':
aperture:
✓ Committed: abc1234
Files: models/training/metrics_exporter.py
fungal-image-analysis:
✓ Committed: def5678
Files: cdk/training_tools_access_stack.py
Commits linked in realm worktree tracking.
$ blue realm push
Pushing coordinated branches:
✓ aperture: feat/training-metrics-v2 → origin
✓ fungal-image-analysis: feat/training-metrics-v2 → origin
Ready for coordinated PRs. Run 'blue realm pr' to create.
Coordinated PRs:
$ blue realm pr
Creating coordinated pull requests:
aperture:
✓ PR #45: "feat: add experiment metrics export"
Base: main
Links to: fungal-image-analysis#23
fungal-image-analysis:
✓ PR #23: "feat: IAM policy for experiment metrics"
Base: main
Links to: aperture#45
Blocked by: aperture#45 (merge aperture first)
PRs are linked. Merge order: aperture#45 → fungal-image-analysis#23
Workflow
Initial Setup
# 1. Create realm (one-time)
$ mkdir realm-letemcook && cd realm-letemcook
$ blue realm init --name letemcook
✓ Created realm.yaml
✓ Initialized git repository
# 2. Register aperture in realm
$ cd ../aperture
$ blue realm join ../realm-letemcook
✓ Created repos/aperture.yaml
✓ Auto-detected exports: s3-permissions
✓ Updated .blue/config.yaml
# 3. Create the s3-access domain (coordination context)
$ blue realm domain create s3-access --repos aperture,fungal-image-analysis
✓ Created domains/s3-access/domain.yaml
✓ Created domains/s3-access/bindings/aperture.yaml (provider)
✓ Created domains/s3-access/bindings/fungal-image-analysis.yaml (consumer)
# 4. Register fungal in realm
$ cd ../fungal-image-analysis
$ blue realm join ../realm-letemcook
✓ Created repos/fungal-image-analysis.yaml
✓ Detected import: s3-permissions in domain s3-access
✓ Updated .blue/config.yaml
Daily Development
# Developer in aperture adds new S3 path
$ cd aperture
$ vim models/training/metrics_exporter.py
# Added: s3://bucket/training-metrics/experiments/*
# Blue status shows cross-realm impact
$ blue status
📊 aperture (in realm: letemcook)
RFCs:
training-metrics-v2 [in-progress] 3/5 tasks
⚠️ Cross-repo change detected:
Domain 's3-access' contract 's3-permissions' has local changes:
+ training-metrics/experiments/*
Affected repos:
- fungal-image-analysis (imports >=1.0.0 of s3-permissions)
Run 'blue realm sync' to update realm
# Sync changes to realm
$ blue realm sync
📤 Syncing with realm 'letemcook'...
Contracts updated in domain 's3-access':
s3-permissions: 1.3.0 → 1.4.0
+ training-metrics/experiments/*
Notifying affected repos:
fungal-image-analysis:
✓ Created GitHub issue #42
"Update IAM policy: new S3 path training-metrics/experiments/*"
✓ Realm synced (commit abc1234)
Consumer Response
# Maintainer in fungal sees notification
$ cd fungal-image-analysis
$ blue status
📊 fungal-image-analysis (in realm: letemcook)
⚠️ Stale imports in domain 's3-access':
s3-permissions: 1.3.0 → 1.4.0 available
Binding: cdk/training_tools_access_stack.py
Changes:
+ training-metrics/experiments/* (read/write)
# Update the IAM policy
$ vim cdk/training_tools_access_stack.py
# Add new path to policy
# Mark import as resolved
$ blue realm sync
📤 Syncing with realm 'letemcook'...
Imports resolved in domain 's3-access':
s3-permissions: now at 1.4.0
✓ Realm synced (commit def5678)
New Tools
blue_realm_init
Create a new realm.
blue_realm_init
--name: Realm name (kebab-case)
--path: Where to create realm repo (default: current directory)
blue_realm_join
Register a repository in a realm.
blue_realm_join
realm_path: Path to realm repo
--name: Repo name in realm (default: repo directory name)
--detect-exports: Auto-detect exports (default: true)
blue_realm_domain
Create or manage a coordination domain.
blue_realm_domain
create: Create a new domain
--name: Domain name (kebab-case)
--repos: Comma-separated list of participating repos
--contract: Initial contract name
blue_realm_leave
Remove a repository from a realm.
blue_realm_leave
--force: Leave even if other repos depend on our exports
blue_realm_export
Declare or update an export.
blue_realm_export
--name: Export name
--version: Semantic version
--value: JSON value (or --file for YAML file)
--detect: Auto-detect from code patterns
blue_realm_import
Declare an import dependency on a contract.
blue_realm_import
--contract: Contract name
--domain: Domain containing the contract
--version: Version requirement (semver)
--binding: Local file that uses this import
blue_realm_sync
Synchronize local state with realm.
blue_realm_sync
--dry-run: Show what would change without committing
--notify: Create GitHub issues for affected consumers (default: true)
blue_realm_check
Check realm status without syncing.
blue_realm_check
--exports: Check if local exports have changed
--imports: Check if any imports are stale
blue_realm_graph
Display the dependency graph.
blue_realm_graph
--format: text | mermaid | dot
blue_realm_sessions
List active Blue sessions in the realm.
blue_realm_sessions
--watch: Continuously update (real-time)
blue_realm_worktree
Create unified worktrees across affected repos.
blue_realm_worktree
--rfc: RFC title that drives the change
--branch: Branch name (default: feat/{rfc-title})
--domain: Domain to coordinate (default: auto-detect from exports)
--repos: Specific repos to include (default: all in domain)
blue_realm_commit
Create coordinated commits across realm worktrees.
blue_realm_commit
-m: Commit message (applied to all repos)
--repos: Which repos to commit (default: all with changes)
--link: Link commits in realm tracking (default: true)
blue_realm_push
Push coordinated branches to remotes.
blue_realm_push
--repos: Which repos to push (default: all in worktree)
blue_realm_pr
Create linked pull requests across repos.
blue_realm_pr
--title: PR title (applied to all)
--body: PR body template
--draft: Create as draft PRs
Implementation
Phase Overview
| Phase | Scope | Duration |
|---|---|---|
| 0 | Data model in blue-core | 1 week |
| 1 | Realm init | 1 week |
| 2 | Domain join | 1 week |
| 3 | Status integration | 1 week |
| 4 | Sync & notifications | 2 weeks |
| 5 | Session coordination (IPC) | 2 weeks |
| 6 | Unified worktrees | 2 weeks |
| 7 | Coordinated commits & PRs | 2 weeks |
| 8 | Polish & docs | 1 week |
Total: 13 weeks (Phases 0-4 for basic functionality, 5-7 for full coordination)
Phase 0: Data Model (Week 1)
Add to blue-core/src/:
// realm.rs
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealmConfig {
pub name: String,
pub version: String,
pub governance: Governance,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Governance {
pub admission: AdmissionPolicy,
pub approvers: Vec<String>,
pub breaking_changes: BreakingChangePolicy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AdmissionPolicy {
Open,
Approval,
InviteOnly,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BreakingChangePolicy {
pub require_approval: bool,
pub grace_period_days: u32,
}
/// A registered repository in the realm
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealmRepo {
pub name: String,
pub org: Option<String>,
pub path: Option<PathBuf>,
pub url: Option<String>,
pub maintainers: Vec<String>,
pub joined_at: String,
}
/// A coordination context (edge between repos)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Domain {
pub name: String,
pub description: Option<String>,
pub members: Vec<String>, // repo names
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Export {
pub name: String,
pub version: String,
pub description: Option<String>,
pub schema: Option<serde_json::Value>,
pub value: serde_json::Value,
pub changelog: Vec<ChangelogEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangelogEntry {
pub version: String,
pub date: String,
pub changes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Import {
pub contract: String,
pub domain: String, // which domain contains this contract
pub version: String, // semver requirement
pub binding: String, // local file that uses this
pub status: ImportStatus,
pub resolved_version: Option<String>,
pub resolved_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ImportStatus {
Current,
Stale,
Broken,
}
Phase 1: Realm Init (Week 2)
// handlers/realm.rs
pub fn handle_init(args: &Value) -> Result<Value, ServerError> {
let name = args.get("name").and_then(|v| v.as_str())
.ok_or(ServerError::InvalidParams)?;
let path = args.get("path")
.and_then(|v| v.as_str())
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("."));
// Create realm directory
let realm_path = path.join(format!("realm-{}", name));
fs::create_dir_all(&realm_path)?;
// Create realm.yaml
let config = RealmConfig {
name: name.to_string(),
version: "1.0.0".to_string(),
governance: Governance {
admission: AdmissionPolicy::Approval,
approvers: vec![],
breaking_changes: BreakingChangePolicy {
require_approval: true,
grace_period_days: 14,
},
},
};
let yaml = serde_yaml::to_string(&config)?;
fs::write(realm_path.join("realm.yaml"), yaml)?;
// Create directories
fs::create_dir_all(realm_path.join("domains"))?;
fs::create_dir_all(realm_path.join("contracts"))?;
// Initialize git
Command::new("git")
.args(["init"])
.current_dir(&realm_path)
.output()?;
Ok(json!({
"status": "success",
"message": format!("Created realm '{}' at {}", name, realm_path.display()),
"path": realm_path.display().to_string()
}))
}
Phase 2: Repo Registration (Week 3)
pub fn handle_join(args: &Value, repo_path: &Path) -> Result<Value, ServerError> {
let realm_path = args.get("realm_path")
.and_then(|v| v.as_str())
.map(PathBuf::from)
.ok_or(ServerError::InvalidParams)?;
let repo_name = args.get("name")
.and_then(|v| v.as_str())
.map(String::from)
.unwrap_or_else(|| {
repo_path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_string()
});
// Validate realm exists
let realm_yaml = realm_path.join("realm.yaml");
if !realm_yaml.exists() {
return Err(ServerError::NotFound("Realm not found".into()));
}
// Create repos directory if needed
let repos_dir = realm_path.join("repos");
fs::create_dir_all(&repos_dir)?;
// Register repo in realm
let repo = RealmRepo {
name: repo_name.clone(),
org: None,
path: Some(repo_path.to_path_buf()),
url: None,
maintainers: vec![],
joined_at: chrono::Utc::now().to_rfc3339(),
};
fs::write(
repos_dir.join(format!("{}.yaml", repo_name)),
serde_yaml::to_string(&repo)?
)?;
// Auto-detect exports (stored temporarily until domain is created)
let exports = detect_exports(repo_path)?;
// Update repo's .blue/config.yaml
let blue_dir = repo_path.join(".blue");
fs::create_dir_all(&blue_dir)?;
let config = json!({
"realm": {
"name": realm_name,
"path": realm_path.display().to_string(),
},
"repo": repo_name,
"detected_exports": exports,
});
fs::write(blue_dir.join("config.yaml"), serde_yaml::to_string(&config)?)?;
// Commit to realm
Command::new("git")
.args(["add", "."])
.current_dir(&realm_path)
.output()?;
Command::new("git")
.args(["commit", "-m", &format!("Register repo: {}", repo_name)])
.current_dir(&realm_path)
.output()?;
Ok(json!({
"status": "success",
"message": format!("Registered '{}' in realm", repo_name),
"exports_detected": exports.len(),
"next_step": "Run 'blue realm domain create' to create a coordination domain"
}))
}
Phase 3: Status Integration (Week 4)
Modify handle_status to include realm information:
fn get_realm_status(state: &ProjectState) -> Option<Value> {
let config_path = state.repo_path.join(".blue/config.yaml");
let config: Value = serde_yaml::from_str(
&fs::read_to_string(&config_path).ok()?
).ok()?;
let realm_path = config["realm"]["path"].as_str()?;
let repo_name = config["repo"].as_str()?;
// Find domains this repo participates in
let domains = find_repo_domains(realm_path, repo_name).ok()?;
// Check for local export changes in each domain
let local_exports = detect_exports(&state.repo_path).ok()?;
let mut export_changes = vec![];
let mut stale_imports = vec![];
for domain in &domains {
let declared = load_domain_exports(realm_path, domain, repo_name).ok()?;
export_changes.extend(diff_exports(&local_exports, &declared, domain));
let imports = load_domain_imports(realm_path, domain, repo_name).ok()?;
stale_imports.extend(check_import_staleness(realm_path, domain, &imports));
}
Some(json!({
"realm": config["realm"]["name"],
"repo": repo_name,
"domains": domains,
"export_changes": export_changes,
"stale_imports": stale_imports
}))
}
Phase 4: Sync (Weeks 5-6)
pub fn handle_sync(args: &Value, state: &ProjectState) -> Result<Value, ServerError> {
let dry_run = args.get("dry_run").and_then(|v| v.as_bool()).unwrap_or(false);
let notify = args.get("notify").and_then(|v| v.as_bool()).unwrap_or(true);
let config = load_realm_config(&state.repo_path)?;
let realm_path = PathBuf::from(&config.realm_path);
let repo_name = &config.repo;
// Pull latest realm state
git_pull(&realm_path)?;
// Find domains this repo participates in
let domains = find_repo_domains(&realm_path, repo_name)?;
// Detect export changes across all domains
let local_exports = detect_exports(&state.repo_path)?;
let mut all_changes = vec![];
for domain in &domains {
let declared = load_domain_exports(&realm_path, domain, repo_name)?;
let changes = diff_exports(&local_exports, &declared);
if !changes.is_empty() {
all_changes.push((domain.clone(), changes));
}
}
if all_changes.is_empty() {
return Ok(json!({
"status": "success",
"message": "No changes to sync"
}));
}
if dry_run {
return Ok(json!({
"status": "dry_run",
"changes": all_changes
}));
}
// Update exports in each affected domain
let mut notifications = vec![];
for (domain, changes) in &all_changes {
let contract = &changes[0].contract;
let new_version = bump_version(&changes[0].old_version, changes);
save_domain_export(&realm_path, domain, repo_name, contract, &new_version)?;
// Find affected repos in this domain
let affected_repos = find_domain_consumers(&realm_path, domain, contract)?;
if notify {
for affected_repo in &affected_repos {
let issue = create_github_issue(affected_repo, domain, changes)?;
notifications.push(issue);
}
}
}
// Commit and push
git_commit(&realm_path, &format!(
"{}: sync exports from {}",
all_changes.iter().map(|(d, _)| d.as_str()).collect::<Vec<_>>().join(", "),
repo_name
))?;
git_push(&realm_path)?;
Ok(json!({
"status": "success",
"message": format!("Synced changes in {} domain(s)", all_changes.len()),
"notifications": notifications
}))
}
Phase 5: Session Coordination (Weeks 8-9)
IPC Socket Infrastructure:
// blue-core/src/ipc.rs
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::PathBuf;
pub struct BlueIpcServer {
socket_path: PathBuf,
listener: UnixListener,
realm: String,
repo: String,
}
impl BlueIpcServer {
pub fn start(realm: &str, repo: &str) -> Result<Self, IpcError> {
let socket_path = PathBuf::from(format!("/tmp/blue/{}.sock", repo));
std::fs::create_dir_all("/tmp/blue")?;
let listener = UnixListener::bind(&socket_path)?;
// Register in realm session index
register_session(realm, repo, &socket_path)?;
Ok(Self { socket_path, listener, realm: realm.to_string(), repo: repo.to_string() })
}
pub fn broadcast_contract_change(&self, domain: &str, contract: &str, changes: &ContractChanges) {
let sessions = load_realm_sessions(&self.realm);
for session in sessions.watching_contract(domain, contract) {
if let Ok(mut stream) = UnixStream::connect(&session.socket) {
let msg = IpcMessage::ContractChanged {
from_repo: self.repo.clone(),
domain: domain.to_string(),
contract: contract.to_string(),
changes: changes.clone(),
};
serde_json::to_writer(&mut stream, &msg).ok();
}
}
}
}
#[derive(Serialize, Deserialize)]
pub enum IpcMessage {
ContractChanged { from_repo: String, domain: String, contract: String, changes: ContractChanges },
SessionStarted { repo: String, rfc: Option<String> },
SessionEnded { repo: String },
Ping,
Pong,
}
Session Index:
// blue-core/src/realm_sessions.rs
pub fn register_session(realm: &str, repo: &str, socket: &Path) -> Result<(), Error> {
let index_path = PathBuf::from(format!("/tmp/blue/{}/sessions.json", realm));
std::fs::create_dir_all(index_path.parent().unwrap())?;
let mut sessions = load_sessions(&index_path)?;
sessions.push(Session {
repo: repo.to_string(),
socket: socket.to_path_buf(),
pid: std::process::id(),
started_at: chrono::Utc::now(),
active_rfc: None,
active_domains: vec![],
exports_modified: vec![],
imports_watching: vec![],
});
save_sessions(&index_path, &sessions)
}
pub fn unregister_session(realm: &str, repo: &str) -> Result<(), Error> {
let index_path = PathBuf::from(format!("/tmp/blue/{}/sessions.json", realm));
let mut sessions = load_sessions(&index_path)?;
sessions.retain(|s| s.repo != repo);
save_sessions(&index_path, &sessions)
}
Phase 6: Unified Worktrees (Weeks 10-11)
// handlers/realm_worktree.rs
pub fn handle_realm_worktree(args: &Value, state: &ProjectState) -> Result<Value, ServerError> {
let rfc_title = args.get("rfc").and_then(|v| v.as_str())
.ok_or(ServerError::InvalidParams)?;
let branch = args.get("branch").and_then(|v| v.as_str())
.map(String::from)
.unwrap_or_else(|| format!("feat/{}", rfc_title));
let domain_filter = args.get("domain").and_then(|v| v.as_str());
let config = load_realm_config(&state.repo_path)?;
let realm_path = PathBuf::from(&config.realm_path);
let repo_name = &config.repo;
// Find domains this repo participates in (or use filter)
let domains = match domain_filter {
Some(d) => vec![d.to_string()],
None => find_repo_domains(&realm_path, repo_name)?,
};
// Find all repos affected by changes in these domains
let mut affected_repos = HashSet::new();
affected_repos.insert(repo_name.clone());
for domain in &domains {
let domain_repos = get_domain_members(&realm_path, domain)?;
affected_repos.extend(domain_repos);
}
// Create worktree in our repo
let our_worktree = create_worktree(&state.repo_path, &branch)?;
// Create worktrees in affected repos
let mut worktrees = vec![WorktreeInfo {
repo: repo_name.clone(),
path: our_worktree.clone(),
status: "active".to_string(),
}];
for repo in &affected_repos {
if repo == repo_name { continue; } // Skip ourselves
let repo_config = load_realm_repo(&realm_path, repo)?;
if let Some(repo_path) = &repo_config.path {
let wt = create_worktree(repo_path, &branch)?;
worktrees.push(WorktreeInfo {
repo: repo.clone(),
path: wt,
status: "active".to_string(),
});
}
}
// Track in realm
let tracking = RealmWorktree {
name: branch.clone(),
created_at: chrono::Utc::now().to_rfc3339(),
source_rfc: format!("{}:{}", repo_name, rfc_title),
domains: domains.clone(),
repos: worktrees.clone(),
commits: vec![],
};
save_realm_worktree(&realm_path, &tracking)?;
Ok(json!({
"status": "success",
"message": format!("Created unified worktree '{}'", branch),
"branch": branch,
"domains": domains,
"worktrees": worktrees
}))
}
Phase 7: Coordinated Commits & PRs (Weeks 12-13)
// handlers/realm_commit.rs
pub fn handle_realm_commit(args: &Value, state: &ProjectState) -> Result<Value, ServerError> {
let message = args.get("m").and_then(|v| v.as_str())
.ok_or(ServerError::InvalidParams)?;
let config = load_realm_config(&state.repo_path)?;
let realm_path = PathBuf::from(&config.realm_path);
// Find current worktree tracking
let branch = get_current_branch(&state.repo_path)?;
let tracking = load_realm_worktree(&realm_path, &branch)?;
let mut commits = vec![];
// Commit in each repo that has changes
for wt in &tracking.repos {
if has_uncommitted_changes(&wt.path)? {
let commit = git_commit(&wt.path, message)?;
commits.push(LinkedCommit {
repo: wt.repo.clone(),
sha: commit,
message: message.to_string(),
});
}
}
// Update realm tracking
let mut tracking = tracking;
tracking.commits.push(CoordinatedCommit {
commits: commits.clone(),
timestamp: chrono::Utc::now().to_rfc3339(),
});
save_realm_worktree(&realm_path, &tracking)?;
Ok(json!({
"status": "success",
"message": format!("Committed in {} repo(s)", commits.len()),
"commits": commits
}))
}
// handlers/realm_pr.rs
pub fn handle_realm_pr(args: &Value, state: &ProjectState) -> Result<Value, ServerError> {
let config = load_realm_config(&state.repo_path)?;
let realm_path = PathBuf::from(&config.realm_path);
let branch = get_current_branch(&state.repo_path)?;
let tracking = load_realm_worktree(&realm_path, &branch)?;
let title = args.get("title").and_then(|v| v.as_str())
.unwrap_or(&branch);
let mut prs = vec![];
// Determine merge order (repos that export should merge before repos that import)
let ordered_repos = topological_sort_repos(&realm_path, &tracking.domains, &tracking.repos)?;
for (i, wt) in ordered_repos.iter().enumerate() {
let repo_config = load_realm_repo(&realm_path, &wt.repo)?;
if let Some(repo_path) = &repo_config.path {
// Create PR with links to other PRs
let body = generate_pr_body(&tracking, &prs, i)?;
let pr = create_github_pr(repo_path, &branch, title, &body)?;
prs.push(LinkedPR {
repo: wt.repo.clone(),
number: pr.number,
url: pr.url,
blocked_by: if i > 0 { Some(prs[i-1].clone()) } else { None },
});
}
}
Ok(json!({
"status": "success",
"message": format!("Created {} linked PR(s)", prs.len()),
"prs": prs,
"merge_order": ordered_repos.iter().map(|r| &r.repo).collect::<Vec<_>>()
}))
}
Phase 8: Polish (Week 14)
- Error handling for all edge cases
- User-friendly messages in Blue's voice
- Documentation
- Tests
- Socket cleanup on process exit
Test Plan
Basic Realm Operations
blue realm initcreates valid realm structureblue realm joinregisters repo correctly in repos/ directoryblue realm joinauto-detects potential exportsblue realm domain createcreates domain with contracts and bindingsblue statusshows realm state and participating domainsblue statusdetects local contract changesblue statusshows stale imports across domainsblue realm syncupdates contracts in affected domainsblue realm synccreates GitHub issues for affected reposblue realm sync --dry-runshows changes without committing- Multiple repos can coordinate through a domain
- Breaking changes are flagged appropriately
- CODEOWNERS prevents cross-repo writes to domain bindings
Session Coordination
- IPC socket created on Blue start
- Session registered in realm index with repo name
- Contract changes broadcast to watching sessions
- Sessions cleaned up on process exit
blue realm sessionslists active sessions by repo
Unified Worktrees
blue realm worktreecreates worktrees in all affected repos in domain- Worktree tracked in realm repo with domain reference
blue realm commitcommits in all repos with changes- Commits linked in realm tracking
blue realm pushpushes all branchesblue realm prcreates linked PRs with correct merge order (exporters first)
Future Work
- Signature verification - Domains sign their exports
- Multiple realms - One repo participates in multiple realms
- Cross-realm imports - Import from domain in different realm
- Public registry - Discover realms and contracts
- Infrastructure verification - Check actual AWS state matches exports