Skip to content

refactor(hosts): host authorities and owner resolution - #27176

Merged
OrcaWin merged 2 commits into
mainfrom
OrcaWin/d4-d4-f1
Oct 10, 2026
Merged

OrcaWin merged 2 commits into
mainfrom
OrcaWin/d4-d4-f1

Conversation

@OrcaWin

@OrcaWin OrcaWin commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

ELI5

Every project, workspace and terminal in Orca lives in exactly one place: this computer, an SSH host, or a paired Orca server (which can have SSH hosts of its own). Today the app's code has no single way to write down "SSH host t, reached through server E", and many places fall back to the "Active Server" setting (whatever server you last focused) when they cannot tell. This PR adds that missing vocabulary and one function that answers "who owns this workspace?" from the workspace's own rows, without ever looking at the Active Server setting. Nothing uses it yet; later PRs move features onto it one at a time.

What Changed

Users see no change. This is a foundation PR.

  • HostAuthority = { endpoint, at } (src/shared/host-authority.ts). endpoint is this app or a paired server; at is that endpoint's own machine or one of its SSH targets. authorityKey writes it with the host ids we already use (local, ssh:t, runtime:E). Only the nested case (SSH target behind a server) gets a new spelling, nested:E/t. That spelling exists only in memory, and every existing host-id parser rejects it, so a nested key that leaks somewhere fails closed instead of turning into runtime:E. The unresolved-owner placeholder can never become an authority. HostRef is the "resource plus its owner" pair that later adapters will take.
  • HostRoute = { target, at } (runtime-client-target.ts). at sits next to RuntimeClientTarget, not inside it. Fences and caches use the target as a key, and putting at inside would merge (E, ssh:t) into runtime:E. runtimeTargetForExecutionHostId is extended instead of copied: it gives the same answers for every existing host id and now also routes a nested key to its server.
  • resolveOwner(state, { workspaceId, hostId? }) (lib/resolve-owner.ts) returns resolved | missing | ambiguous (the existing ExecutionHostOwnerMatch). It reuses the existing route resolvers (resolveWorktreeOperationRouteResult and the strict host-qualified resolver) and runs them with the focus setting blanked out. Focus is the only setting those resolvers read, so the result cannot depend on the Active Server.
    Two corrections on top of those resolvers. First, when the workspace is the active one, its selected host goes through the strict resolver, so two servers that both proxy the same SSH target give "ambiguous" and never a directly dialed SSH host. Second, when the route names only a paired server, the owning folder, project group or repo row can still name the SSH target behind that server (its connectionId). In that case the result keeps the target, using the same rule as resolveWorktreeExecutionHost.
  • resolveOwnerWithLegacyFocus is resolveOwner with today's fallback to the focused server added back, and counts each time focus changed the result. A later slice can delete its fallback once that count stays at zero.

Why

The D4 owner-routing plan moves every renderer call that routes by focus over to routing by the resource's own owner. Those later slices need one owner type and one resolver first. Otherwise each slice would invent its own, or move its call sites twice.

Alternatives considered:

  • Putting at on RuntimeClientTarget. Rejected, because fences and caches key on the target (see above).
  • Writing a new resolver from scratch. Rejected. The existing route resolvers already handle ambiguity, folder workspaces, detected worktrees and the paired-server transport. Blanking the focus setting gives a focus-free answer, and the code proves it rather than promising it.
  • Counting fallbacks inside the resolvers. Rejected, because it would mean editing two focus branches (the worktree one and the folder one). Comparing against the focus-free answer measures the thing we care about, "focus changed the answer", in one place. It runs only when a focus value is set.

Prerequisite note: the design lists HBI A0a as a prerequisite. The only part this PR needs is the shared ambiguity-aware match type (ExecutionHostOwnerMatch), and it is already on main in src/shared/worktree-execution-host-resolution.ts. A0a's remaining work (moving the first-wins worktree lookups onto the index) is separate and stays in its own stack.

Linked Issue

None. This is a refactor from the D4 owner-routing design and closes no issue.

Visual Proof

N/A. No caller uses the new code yet, so nothing visible or interactive changes.

Testing

  • resolve-owner.test.ts is a characterization table. Each row runs with Active Server set to {local, another server, a deleted server}. Rows: local (stamped and unstamped), direct SSH, a managed server on an SSH host, a paired server, an SSH target behind a server (with and without a named host, and known only from its repo row), the same ssh:t behind two servers (ambiguous with a named host, without one, and when it is the active workspace), a recipe-VM SSH target, the floating workspace, folders (local, paired server, an SSH target behind a paired server on the folder or on its group, unstamped) and an unknown id. Every row asserts that resolveOwner gives the same answer under all three settings. It also pins today's answer, which reads focus, and checks the fallback counter. With the focus blanking removed, 4 cases fail.

  • host-authority.test.ts checks that keys round-trip, that a nested key is rejected by the host-id and host-identity parsers, and that the unresolved placeholder and contradictory routes are refused.

  • runtime-client-target.test.ts checks that plain host ids give the same answers as before and that a nested key routes to its server.

  • pnpm tc, oxlint, check:code-quality:changed, and the focused tests for the existing callers of runtimeTargetForExecutionHostId.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • SSH: an SSH target behind a server stays distinct from a directly dialed one. When two servers proxy the same target, the result is ambiguous and the code never guesses.
  • Folder workspaces: covered, including the paired-server folder case.
  • Windows: no paths or platform logic. Compatibility: no runtime RPC, IPC or persisted key changes.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

Adds HostAuthority {endpoint, at} with authorityKey (existing host-id
spellings kept; nested 'SSH target behind server' gets an in-memory key no
host-id parser accepts), HostRoute {target, at}, and resolveOwner, a
focus-free owner resolver over the existing route resolvers. Today's
focus-reading answer is kept behind resolveOwnerWithLegacyFocus, which
counts every answer focus changed. No caller migrates in this PR.
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough
📝 Walkthrough

Walkthrough

The change adds host-authority types, key conversion, and operation-route conversion. It adds workspace owner resolution that does not use active focus settings, plus a legacy-focus resolver and fallback counter. Folder workspace ownership can also provide a nested SSH target. Runtime target resolution now accepts authority keys, and host routes keep the runtime target separate from the authority location. Tests cover these behaviors and their edge cases.



Priority: ⬇️ Low

Merge Risk: 🔵 Low · up to 4f7a1

Current users are unaffected, but this owner-resolution error should be fixed before callers migrate to the new path. The PR is mergeable with that follow-up tracked.

Pre-merge checks | Passed 3 | Failed 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 30.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check Warning The description is detailed and covers the required change summary, rationale, testing, visual proof, compatibility notes, and checklist. However, it explicitly states that no linked issue exists even… Add an actual issue reference in the Linked Issue section, such as Fixes #12345. If the author is a maintainer and the repository permits the exception, state that the maintainer exemption applies.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly and concisely identifies the main changes: host authorities and owner resolution.

Full details: Description check

Explanation

The description is detailed and covers the required change summary, rationale, testing, visual proof, compatibility notes, and checklist. However, it explicitly states that no linked issue exists even though the template requires an issue link unless the author is a maintainer.


  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR



🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…lection

The active workspace's host now resolves through the strict path, so rival
servers proxying one ssh:t stay ambiguous. A folder or repo row on a paired
server that names an SSH connection resolves to that nested target instead
of the server's own machine.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f8e7b1ad-2718-4be9-97a8-b1e76bbac00d
📥 Commits

Reviewing files that changed from the base of the PR and between 1471321 and 4f7a1c7.

📒 Files selected for processing (3)
  • src/renderer/src/lib/folder-workspace-runtime-owner.ts
  • src/renderer/src/lib/resolve-owner.test.ts
  • src/renderer/src/lib/resolve-owner.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

Comment on lines +144 to +146
const owner = folderWorkspace?.executionHostId
? folderWorkspace
: findFolderProjectGroup(state, folderWorkspaceId, executionHostId)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

set -eu
printf '%s\n' '--- helper ---'
nl -ba src/renderer/src/lib/folder-workspace-runtime-owner.ts | sed -n '1,210p'
printf '%s\n' '--- resolver tests around folder routing ---'
nl -ba src/renderer/src/lib/resolve-owner.test.ts | sed -n '150,235p'
printf '%s\n' '--- helper references and exports ---'
rg -n -F --glob '*.ts' -- 'getNestedSshTargetIdForFolderWorkspace' src/renderer/src
rg -n -F --glob '*.ts' -- 'getExecutionHostIdForFolderWorkspace' src/renderer/src

Repository: stablyai/orca

Length of output: 13906


🏁 Script executed:

set -eu
printf '%s\n' '--- resolver call sites ---'
rg -n -F --glob '*.ts' -- 'resolveOwner(' src/renderer/src || test "$?" -eq 1
printf '%s\n' '--- nested target references ---'
rg -n -F --glob '*.ts' -- 'getNestedSshTargetIdForFolderWorkspace' src/renderer/src || test "$?" -eq 1
printf '%s\n' '--- folder fixtures and inherited cases ---'
rg -n -F --glob '*.ts' -- 'function folder' src/renderer/src/lib/resolve-owner.test.ts
rg -n -F --glob '*.ts' -- 'projectGroups:' src/renderer/src/lib/resolve-owner.test.ts
rg -n -F --glob '*.ts' -- 'connectionId' src/renderer/src/lib/folder-workspace-runtime-owner.test.ts src/renderer/src/lib/resolve-owner.test.ts || test "$?" -eq 1
printf '%s\n' '--- owner helper tests ---'
if test -f src/renderer/src/lib/folder-workspace-runtime-owner.test.ts; then
  nl -ba src/renderer/src/lib/folder-workspace-runtime-owner.test.ts | sed -n '1,260p'
fi

Repository: stablyai/orca

Length of output: 2164


🏁 Script executed:

set -eu
printf '%s\n' '--- activity production callers ---'
nl -ba src/renderer/src/components/activity/activity-event-builder-sources.ts | sed -n '1,115p'
printf '%s\n' '--- resolver contract and route construction ---'
nl -ba src/renderer/src/lib/resolve-owner.ts | sed -n '1,155p'
printf '%s\n' '--- activity caller imports and consumers ---'
rg -n -F --glob '*.ts' -- 'activity-event-builder-sources' src/renderer/src
rg -n -F --glob '*.ts' -- 'buildSource' src/renderer/src/components/activity src/renderer/src | head -80

Repository: stablyai/orca

Length of output: 22492


🏁 Script executed:

set -eu
printf '%s\n' '--- activity builder wiring ---'
nl -ba src/renderer/src/components/activity/activity-event-builder.ts | sed -n '1,150p'
rg -n -F --glob '*.ts' -- 'from "../../lib/resolve-owner"' src/renderer/src || test "$?" -eq 1
rg -n -F --glob '*.ts' -- "from '@/lib/resolve-owner'" src/renderer/src || test "$?" -eq 1
rg -n -F --glob '*.ts' -- "from './lib/resolve-owner'" src/renderer/src || test "$?" -eq 1
rg -n -F --glob '*.ts' -- 'appendUnsupportedAndRetainedEvents(' src/renderer/src

Repository: stablyai/orca

Length of output: 7060


Preserve the folder connection for inherited runtime hosts.

When a folder has connectionId: 't' but no executionHostId, the helper selects the project group. If that group supplies runtime:env-a without a connection ID, the helper returns null. resolveOwner then keeps the server machine instead of selecting ssh:t.

This helper is part of a transitional path with no current production caller. The issue is a low-impact functional defect, not a current production data-integrity incident. Fix it before callers migrate to this path.

🐛 Suggested fix
   return owner && parseExecutionHostId(owner.executionHostId)?.kind === 'runtime'
-    ? owner.connectionId?.trim() || null
+    ? folderWorkspace?.connectionId?.trim() || owner.connectionId?.trim() || null

@OrcaWin
OrcaWin merged commit 6af18ab into main Oct 10, 2026
53 of 55 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant