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
11 changes: 6 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · Versioning:
## [Unreleased]

### Changed
- **Renamed host product references from hawk to rho.** Host config paths
(`~/.rho/`), the `rho_build`/`rho_build_concise` tool namespaces, the
`rho_response` schema name, and the `EXPORT_RHO_FIXTURE` env var now use the
rho identity. Retired ecosystem repos (`shrike`, `harrier`, `kestrel`,
`merlin`) are no longer referenced.
- **Renamed host product references to rho.** Host config paths (`~/.rho/`),
the `rho_build`/`rho_build_concise` tool namespaces, the `rho_response`
schema name, and the `EXPORT_RHO_FIXTURE` env var now use the rho identity.
References to retired ecosystem repos were removed.
- **Credential env-file migration falls back to the pre-rename host config
directory** so installs created before the rename still migrate.

## [0.0.1] — 2026-09-15

Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ and two Go AST tests in `rho/internal/testaudit/`.

- do not import `rho/internal/*`
- do not import the removed legacy path `rho/shared/types`
- do not import other engines (`swift`) — engines are peers, not dependencies

## Quick Start

Expand Down Expand Up @@ -320,7 +319,6 @@ flux is part of the graycode-eco:
|---|---|---|
| **rho** | [GrayCodeAI/rho](https://github.com/GrayCodeAI/rho) | AI coding agent |
| **flux** | This repo | LLM provider runtime |
| **swift** | [GrayCodeAI/swift](https://github.com/GrayCodeAI/swift) | Session capture |

## Development

Expand Down
19 changes: 17 additions & 2 deletions credentials/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ func markEnvFileMigrationDone() {
}

// MigrateEnvFileCredentials imports API keys from plaintext credential files
// (~/.rho/env, ~/.rho/.env) into the OS secret store and removes them.
// (~/.rho/env, ~/.rho/.env) into the OS secret store and removes them. It also
// checks the pre-rename host config directory (~/.hawk/env, ~/.hawk/.env) so
// installs created before the rename still migrate.
func MigrateEnvFileCredentials(ctx context.Context) (int, error) {
if ctx == nil {
ctx = context.Background()
Expand All @@ -34,7 +36,7 @@ func MigrateEnvFileCredentials(ctx context.Context) (int, error) {
return 0, nil
}
total := 0
for _, path := range []string{rhoEnvPath(), rhoDotEnvPath()} {
for _, path := range []string{rhoEnvPath(), rhoDotEnvPath(), legacyEnvPath(), legacyDotEnvPath()} {
n, err := migrateEnvFileAt(ctx, path)
if err != nil && !os.IsNotExist(err) {
return total, err
Expand Down Expand Up @@ -93,6 +95,19 @@ func rhoDotEnvPath() string {
return filepath.Join(home, ".rho", ".env")
}

// legacyEnvPath is the pre-rename host config file path, kept so installs
// created before the rename still migrate.
func legacyEnvPath() string {
home, _ := os.UserHomeDir()
return filepath.Join(home, ".hawk", "env")
}

// legacyDotEnvPath is the pre-rename host dotenv file path.
func legacyDotEnvPath() string {
home, _ := os.UserHomeDir()
return filepath.Join(home, ".hawk", ".env")
}

func readEnvFile(path string) (map[string]string, error) {
data, err := os.ReadFile(path) // #nosec G304 -- path is built from os.UserHomeDir() in rhoDotEnvPath, not untrusted input
if err != nil {
Expand Down
31 changes: 31 additions & 0 deletions credentials/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,37 @@ func TestMigrateEnvFileCredentials_BothPaths(t *testing.T) {
}
}

func TestMigrateEnvFileCredentials_LegacyPaths(t *testing.T) {
ms := &MapStore{}
cs := &CombinedStore{Keychain: ms}
SetDefaultStore(cs)
t.Cleanup(func() { SetDefaultStore(nil) })

dir := t.TempDir()
t.Setenv("HOME", dir)

legacyDir := filepath.Join(dir, ".hawk")
if err := os.MkdirAll(legacyDir, 0o700); err != nil {
t.Fatal(err)
}
envPath := filepath.Join(legacyDir, "env")
if err := os.WriteFile(envPath, []byte("ANTHROPIC_API_KEY=sk-legacy\n"), 0o600); err != nil {
t.Fatal(err)
}

ctx := context.Background()
n, err := MigrateEnvFileCredentials(ctx)
if err != nil {
t.Fatalf("error: %v", err)
}
if n != 1 {
t.Fatalf("expected 1 migrated from legacy path, got %d", n)
}
if _, err := os.Stat(envPath); !os.IsNotExist(err) {
t.Error("legacy ~/.hawk/env should be removed after migration")
}
}

func TestMigrateEnvFileCredentialsAt_NilKeychain(t *testing.T) {
// When DefaultStore is a CombinedStore with nil Keychain, migration should fail.
cs := &CombinedStore{Keychain: nil}
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/CREDENTIAL-SETUP-FLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Setup is **gateway-first**: pick the gateway on the Gateways tab, paste any non-
→ Pick model → ListModels (auto) when credentials exist
```

## Host API (rho uses `internal/fluxclient` only)
## Host API (rho integrates through `flux/engine`)

- `ResolveCredentialForHost` / `SaveCredentialForHost`
- `ApplyFluxCredentials`
Expand Down
2 changes: 1 addition & 1 deletion internal/observability/genai_semconv.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// These exported constants are the canonical, ecosystem-wide attribute keys for
// describing LLM / AI agent operations. They follow the OpenTelemetry GenAI
// semantic conventions (gen_ai.*) and are shared as the reference set that the
// other graycode-eco repos (rho, swift) should mirror when emitting
// other graycode-eco repos should mirror when emitting
// spans, so dashboards and exporters can correlate cost/usage/identity across
// the whole ecosystem.
//
Expand Down
21 changes: 2 additions & 19 deletions scripts/check-ecosystem-boundaries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,19 @@ cd "$ROOT_DIR"
# Shared ecosystem vocabulary lives in rho/internal/contracts, which
# hosts vendor rather than import from here.
FORBIDDEN_RHO='github\.com/GrayCodeAI/rho(/|")'
FORBIDDEN_ENGINES='github\.com/GrayCodeAI/swift(/|")'

exit_code=0

if command -v rg >/dev/null 2>&1; then
violations="$(rg -n "$FORBIDDEN_RHO" --glob '*.go' . || true)"
engine_violations="$(rg -n "$FORBIDDEN_ENGINES" --glob '*.go' . || true)"
else
violations="$(grep -rn --include='*.go' -E "$FORBIDDEN_RHO" . || true)"
engine_violations="$(grep -rn --include='*.go' -E "$FORBIDDEN_ENGINES" . || true)"
fi

if [[ -n "${violations}" ]]; then
echo "forbidden Rho host imports found:"
echo "${violations}"
echo
echo "flux must use local contracts, never the Rho product module"
exit_code=1
fi

if [[ -n "${engine_violations}" ]]; then
echo "forbidden cross-engine imports found:"
echo "${engine_violations}"
echo
echo "support engines must not import other engines directly — they are peers, not dependencies"
exit_code=1
fi

if [[ $exit_code -ne 0 ]]; then
exit $exit_code
exit 1
fi

echo "ecosystem boundary guard passed"
echo "ecosystem boundary guard passed"
10 changes: 5 additions & 5 deletions tools/versioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func BehaviorPresetFrom(s string) (BehaviorPreset, error) {
}

// FinalizeErrorCode classifies a finalize warning or violation, mirroring the
// FINALIZE_ERROR_CODE enum in proto/rho/contracts/v1/tool.proto.
// FINALIZE_ERROR_CODE enum in the host tool contract.
type FinalizeErrorCode int

const (
Expand Down Expand Up @@ -90,7 +90,7 @@ func (r FinalizeResult) Ok() bool {
}

// ToolMeta is the canonical identity envelope attached to tool-call events,
// mirroring the ToolMeta message in proto/rho/contracts/v1/tool.proto.
// mirroring the ToolMeta message in the host tool contract.
// version is an additive-only bump: new additive fields do NOT change it.
type ToolMeta struct {
Version string `json:"version"`
Expand All @@ -102,11 +102,11 @@ type ToolMeta struct {
}

// ToolNamespace is a CLOSED enum identifying the harness that owns a tool,
// mirroring the ToolNamespace enum in proto/rho/contracts/v1/tool.proto. A new
// mirroring the ToolNamespace enum in the host tool contract. A new
// unknown namespace is a wire-breaking change that intentionally fails
// ToolNamespaceFrom (forward-safety): a deploy that rolls the contract forward
// before the consumer code cannot silently mis-route a tool it doesn't
// understand. ToolNamespaceAcp is reserved for the forthcoming rho-acp repo.
// understand. ToolNamespaceAcp is reserved for a forthcoming ACP harness.
type ToolNamespace string

const (
Expand All @@ -116,7 +116,7 @@ const (
ToolNamespaceCodex ToolNamespace = "codex" // codex harness
ToolNamespaceOpencode ToolNamespace = "opencode" // opencode harness
ToolNamespaceMcp ToolNamespace = "mcp" // MCP servers (falcon)
ToolNamespaceAcp ToolNamespace = "acp" // reserved: rho-acp
ToolNamespaceAcp ToolNamespace = "acp" // reserved: ACP harness
)

// ToolNamespaceFrom parses a namespace string into a ToolNamespace. The set is
Expand Down
Loading