feat(accounts): unify native logins and provider accounts under one namespace (RUSH-2527) - #2563
feat(accounts): unify native logins and provider accounts under one namespace (RUSH-2527)#2563muqsitnawaz wants to merge 20 commits into
Conversation
…amespace (RUSH-2527) Native harness logins get durable, fingerprint-keyed aliases (metadata only — no tokens or raw emails), sharing one account namespace and one renderer with provider credential accounts. Adds positional grammar (name/view/attach/detach, positional sync), safe remove/reference checks, and recovers retired version-bound labels into aliases by their preserved fingerprint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Source: bun run gen:index — adds name/view/attach/detach to docs/command-index.{md,json}
and command-reference.html (RUSH-2527).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-2527) Replace the standalone fingerprint-alias file with the unified model: native account records live in meta.accounts.native (metadata only, no OAuth copy), bindings in meta.accounts.bindings (target -> stable id), and account-capabilities derives version- vs device-scope. accounts name/attach/detach/view now support native attach to an installation or device-scoped harness; resolveAccountSelection resolves explicit -> exact-target -> device-scoped -> per-harness default. Aligns account-core with the reference implementation and the fleet-auth/transport tracks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…527) findUnifiedAccount defaulted its doc arg to readAccountRegistry(), which evaluated before the native check — so a native view/attach/run triggered a bundle read, legacy-accounts.yaml migration, and a keychain decrypt (a Touch ID prompt, or a hard crash on an undecryptable legacy item). Make the arg optional and read the registry lazily only for the provider fallback. Regression test uses a poisoned registry that throws on any access and asserts a native lookup never reaches it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Status update — account-core landed; EXEC-ACCOUNT-3 runtime wiring is the remaining seamConverted to draft: per review, Landed in this PR (account-core: model + command + resolution contract)
Remaining before merge (EXEC-ACCOUNT-3 — runtime wiring; hand-off to root / runtime-fleet-auth track)These touch the execution chokepoint and sibling-track files (
Reference for the seams: |
…2527, EXEC-ACCOUNT-3) resolveSpawnAccount classifies the selected account and exec.ts acts on it: - binding order is honored via a target key — a custom harness binds by its raw profile name, a native/global run by exact agent@version. - provider account: env resolved and injected (fail-closed on missing credential). - native account: no secret/env injected; a pinned version is validated to hold the named identity, and an unpinned run is pinned to the installed version actually signed in as that identity (resolveAccountVersion, not the global default). A native account fails closed for a remote target (no identity forwarding) and for a mismatched harness. Tests cover provider vs native classification, binding order, profile-name binding, and cross-harness rejection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (RUSH-2527) - view.ts / fleet-inventory / harness-inventory: prefix the durable native account name (work · email) wherever a signed-in identity is shown. - runner.ts: routine account selection classifies native vs provider — a native routine account translates its durable name to the identity key before resolveAccountVersion pins the matching install, refuses a cross-harness login, and never forwards/injects; a native default/binding no longer crashes the provider credential path. Tests cover pin-by-identity and cross-harness failure. - Strip a stray NUL byte from harness-inventory.ts (it read as binary) and force a text diff via .gitattributes so the change is reviewable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… escape (RUSH-2527) The signed-out group key is a NUL-prefixed sentinel so a real account label named literally "signed-out" cannot merge into the signed-out bucket. Express it as the TypeScript escape '\0signed-out' (text, no raw NUL byte in the source) rather than an embedded NUL, and drop the .gitattributes workaround. Adds a groupByAccount test with a real 'signed-out' label proving it stays separate from the null bucket. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Runtime wiring complete — EXEC-ACCOUNT-3 seams landedThe bindings are no longer cosmetic; ready for review. Spawn selection ( Routines ( Display — Sentinel fix — the signed-out group key is kept collision-proof as a TS Tests (real paths / injected deps, no mocks): spawn-selection classification + binding order + profile-name binding + cross-harness rejection ( |
|
Non-author review: REQUEST CHANGES. Blocking before merge: (1) routine host/cloud placement bypasses native-account rejection because placement occurs before |
…+ routine placement (RUSH-2527) - Capability truth: version-strong is only Claude/Codex/Grok; Muse is email- conditional; Cursor is blocked; Antigravity/Kimi/Droid/OpenCode are recorded as device-scoped opaque but UNSUPPORTED — a NativeAccount has no device-id discriminator, so an opaque/singleton identity can't be proven unique across synced metadata (Droid has no account key; Antigravity/OpenCode can alias). A truthful "unsupported" over false support. nativeAccountNameable gates naming. - attach validates the target BEFORE mutating: it must be an installed agent@version, a device harness id, or an existing custom profile (typos rejected). A native account is refused on a provider-backed custom profile (which would inject provider env under a native identity claim). - Routines: reject a native account before ANY host/cloud dispatch — assertRoutineAccountLocalForPlacement runs at the top of both the foreground and detached placement blocks (placement resolves before resolveRoutineLaunch). Tests: pinned capability semantics; attach target/profile validation; routine placement rejection for host+cloud with a provider allowed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t spawn (RUSH-2527) `agents run deepseek --account work` bypasses `attach`, so the exec path must also refuse it: resolveSpawnAccount now throws when a native account is selected while a provider is in play (exec passes the profile's provider), since the harness would still inject its provider credentials under a native identity claim. A bare native run (no provider) is unaffected. Adds a direct run-resolution test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…unt-core # Conflicts: # apps/cli/docs/command-index.json # apps/cli/docs/command-index.md # apps/cli/docs/command-reference.html
Review blockers addressed (commits 513af03, 97e2ced) + merged current main
Also: kept the signed-out sentinel collision-proof as a TS |
|
Non-author re-review: prior blockers are resolved; two blockers remain. (1) Provider routine accounts are accepted for host/cloud but |
…n cloud; doc capability truth (RUSH-2527) Review round 2: - A provider routine account is no longer silently dropped on placement: the host dispatch forwards it by NAME (dispatchPromptToHost account; the remote resolves its own local bundle, no secret copied), and cloud placement fails loud before dispatch (no secure provider-account injection there yet). Native stays rejected for both. Guard tests updated for provider-host allow / provider-cloud reject. - credential-management.md: replace the stale "Cursor/OpenCode/Antigravity/Kimi/ Droid attach as device native accounts" claim with the conservative table — only Claude/Codex/Grok (version) + Muse (conditional) are nameable; the opaque device harnesses and Cursor are unsupported. attach row corrected: native binds only to a supported agent@version. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review round 2 addressed (commit 00b8802)
Build + affected sweep green. |
…unt-core # Conflicts: # apps/cli/docs/command-index.json # apps/cli/docs/command-index.md # apps/cli/docs/command-reference.html
|
Non-author re-review: runtime behavior and capability docs are correct. One test blocker remains: the new test calls only |
… (RUSH-2527) - Extract buildHostDispatchOptions (used by executeJobOnHost) so the host dispatch boundary is unit-testable; a test asserts the provider account rides along as account:'prov' (catches the "executeJobOnHost drops config.account" regression a guard-only test would miss). - runner-cloud-account.test.ts drives the real executeJobPlaced with a cloud placement + a provider account and asserts it fails loud at the placement guard with ZERO cloud dispatch calls (resolveProvider never invoked). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review round 3 addressed (commit beab26b)Execution-boundary tests, not guard-only:
Build + affected sweep (94) green. |
|
Final review fix pushed at Evidence:
|
|
Non-author re-review: APPROVE at Verified:
Verdict: APPROVE. |
|
Independent non-author re-review at 114b008: APPROVE. Verified provider host forwarding preserves account='prov'; provider cloud refusal occurs before the supplied dispatcher with zero calls; runner tests use injected production boundaries without module mocks or test-only exports; credential capability docs match the canonical capability table. |
What + type: feature — unify native harness logins and provider credential accounts under one account model, wired end-to-end through run/routine/profile/view/fleet (RUSH-2527).
Model
An account is one authorization identity, in two kinds sharing one namespace (
meta.accounts) and one renderer:accounts add) — a durable key/token stored as a policy-neversecrets bundle; portable,synccopies it.accounts name <agent@version> <name>) — a durable name for a harness's own login, metadata only inmeta.accounts.native(stable id + identity key + scope); the OAuth/session credential is never copied. A native lookup reads onlymeta— never the provider bundle store or keychain.Bindings live in
meta.accounts.bindings(target → stable id).resolveAccountSelectionorder: explicit--account→ exactagent@versionbinding → device-scopedagentbinding → per-harness default.Capability truth (
account-capabilities.ts)Version-scoped strong: Claude/Codex/Grok only. Muse: version, email-conditional. Cursor: blocked. Antigravity/Kimi/Droid/OpenCode: recorded device-scoped opaque but unsupported — a
NativeAccounthas no device-id discriminator, so an opaque/singleton identity can't be proven unique across synced metadata (truthful over false support).Runtime wiring (not cosmetic)
exec.ts— binding order via a target key (a custom harness keys on its profile name). Provider → env injected (fail-closed on missing credential). Native → no secret/env; a pinned version is validated live, an unpinned run is pinned to the install actually signed in as that identity (resolveAccountVersion, not the global default); fails closed for remote/cloud and cross-harness. A native account is rejected on a provider-backed harness (e.g.run deepseek --account work) so provider env is never injected under a native claim.runner.ts— routineaccount:classifies native vs provider; native translates name→identity key then pins the matching install (never forwarded/injected), refuses cross-harness. Native accounts are rejected before any host/cloud dispatch (assertRoutineAccountLocalForPlacementat the top of both the foreground and detached placement blocks, which run beforeresolveRoutineLaunch).accounts.ts—attachvalidates the target exists (installedagent@version, harness id, or existing profile; typos rejected) before mutating, and refuses a native account on a provider-backed profile.agents view+ fleet/harness inventory renderwork · email.Run evidence (fleet-local asset, path not embed)
Captured
agents accounts --help+ the real temp-home unit run:yosemite-s0:/home/muqsit/src/github.com/muqsitnawaz/agents-cli/.agents/worktrees/rush-2527-account-core/apps/cli/../../.agents/artifacts/2026-08-10/rush-2527-accounts-run.logTests (real paths / injected deps, no mocks): spawn-selection classification + binding order + profile-name binding + cross-harness + native-on-provider rejection + native-lookup-no-provider-read regression (
account-registry.test.ts); pinned capability semantics (account-capabilities.test.ts); attach target/profile validation (accounts.test.ts); routine pin-by-identity + cross-harness + host/cloud placement rejection (runner.test.ts); signed-out\0sentinel collision (harness-inventory.test.ts). Affected-surface sweep green;build.sh --skip-testsgreen; command-index + docs verify. Merged currentorigin/main.Ticket: RUSH-2527