Skip to content
This repository was archived by the owner on Aug 17, 2026. It is now read-only.

fix(hive): route mismatched local identity to managed recovery - #91

Merged
100yenadmin merged 2 commits into
mainfrom
fix/78-recovery-classification
Aug 3, 2026
Merged

fix(hive): route mismatched local identity to managed recovery#91
100yenadmin merged 2 commits into
mainfrom
fix/78-recovery-classification

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented Aug 3, 2026

Copy link
Copy Markdown
Member

Bounded stabilization for the installed #78 acceptance failure of 2026-07-31.

Observed failure

Signed candidate 38c68e257dc34b7c057fe64a1539d45cc7c72198, artifact SHA-256 73d9436b003474fbe984019575119b8f03fd7bd60224d85e7ddeb55522cce733, /Applications/Hive.app: Electric OAuth succeeded and the proof-bound one-time backup-code claim was accepted, then Hive rejected the readable local native key with This device's native Buzz identity does not match the canonical Hive identity and returned to Sign In — never attempting same-canonical-key custody recovery, never exposing the explicit lost-identity replacement action.

Root cause

local_identity_ready_for_login called verify_existing_native_identity with ?. On a readable-but-non-canonical key that function returns Err, so the error propagated straight out of complete_login. Control never reached the binding.public_key.is_some() recovery arm, which made keychain_migration::select_legacy_identity_candidate, identity_custody::recover_identity, and the identity_reset_required fallback all unreachable.

Change

Adds a typed classifier ExistingNativeIdentity { Unbound, Ready, Mismatched }. local_identity_ready_for_login now returns Ok(false) for a valid readable mismatch, so complete_login falls through to its existing, unchanged recovery arm: legacy-candidate adoption → recover_identity → on NotAvailable, stage PendingIdentityReset and return identity_reset_required.

Deliberately not reclassified — these stay hard errors:

  • invalid membership id, canonical key failing valid_public_key
  • locked / unreadable Keychain (require_genuine_native_identity_loss untouched)
  • IdentityRecoveryError::Other, bind/entitlement/enrollment/persistence failures

verify_existing_native_identity keeps its Err-on-mismatch behavior for its other caller, the session-resume path (evaos_teams.rs:752). That path correctly cannot recover in place, so it surfaces reauth_required and defers to a fresh OAuth sign-in, which now routes to recovery. No silent new-key enrollment is introduced; the only non-recovery outcome remains the explicit user-confirmed reset.

Verification (run locally on this branch, not just reported)

Command Result
cargo fmt --check PASS
just desktop-tauri-clippy (CI's exact recipe) PASS, clean
cargo test --features evaos-teams-managed evaos_teams 66 passed, 0 failed
node --test scripts/check-file-sizes-core.test.mjs (reset-boundary ratchet) PASS 7/7

Tests added: mismatch is a recovery state (not Err), matching key is ready, unbound membership is not ready, malformed membership and invalid canonical key remain hard errors.

Diff is +52 net across 3 files, entirely inside the evaos_teams adapter surface. No new modules, crates, Tauri commands, or UI changes — thin-adapter boundary preserved.

Note for reviewers (pre-existing, not fixed here)

cargo clippy with --features evaos-teams-managed reports two needless_borrow errors in desktop/src-tauri/src/evaos_teams/identity_custody.rs (~lines 653, 663). They are present on main unchanged and are invisible to CI because just desktop-tauri-clippy builds without that feature. Flagged rather than fixed — touching an unrelated file is outside this diff's scope. Worth a separate one-liner, and worth deciding whether CI should lint the managed feature at all.

What this does NOT prove

Source and CI only. Merge, signed artifact, installed fresh-Mac recovery, and the primary operator/Benjamin two-person acceptance remain separate claims — see the #78 close-out plan. In particular, recovery still cannot succeed until (a) the custody keyring Edge secrets are confirmed set in production and (b) a custody envelope has actually been enrolled from a device holding the canonical key.

Refs #78

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 72d9ff1a-6ab0-4ed8-9d38-74adbd2a7dd7

📥 Commits

Reviewing files that changed from the base of the PR and between 79cad89 and 3b315ff.

📒 Files selected for processing (4)
  • desktop/src-tauri/src/evaos_teams.rs
  • desktop/src-tauri/src/evaos_teams/identity_binding.rs
  • desktop/src-tauri/src/evaos_teams/login_identity.rs
  • desktop/src-tauri/src/evaos_teams/tests.rs
📝 Walkthrough

Priority Level: P4/NIT

No P0–P3 findings identified.

The change correctly classifies readable local identities as Unbound, Ready, or Mismatched. Login routes valid mismatches to the existing managed recovery flow. Session resume rejects mismatches and requires reauthentication. Invalid bindings and unreadable identities remain hard failures.

The implementation also moves the classifier out of evaos_teams.rs and updates tests for the different login and resume behaviors.

Optional follow-up

  • Add the managed-feature tests to CI.
  • Validate installed-device recovery, signed artifacts, production custody configuration, and two-person acceptance.
  • Review the documented recovery risks: identity reset without a custody envelope and replacement of a healthy local key in a shared service name.

These follow-ups are optional and are not required for merge.

Confidence: 88%

Walkthrough

Native identity verification moved into shared binding logic. Login now distinguishes ready, mismatched, unbound, and invalid identities. Resume verification rejects mismatched keys. Tests cover these outcomes. Confidence: 95%.

Changes

Native identity handling

Layer / File(s) Summary
Classify and verify native identities
desktop/src-tauri/src/evaos_teams/identity_binding.rs
Validates membership IDs and canonical public keys. Classifies identities as Unbound, Ready, or Mismatched.
Apply classification to login and resume paths
desktop/src-tauri/src/evaos_teams.rs, desktop/src-tauri/src/evaos_teams/login_identity.rs, desktop/src-tauri/src/evaos_teams/tests.rs
Login accepts only Ready identities. Resume rejects mismatched keys. Tests cover matching, mismatched, unbound, missing, and malformed identities.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LoginIdentity
  participant IdentityBinding
  participant LocalKeys
  participant ResumeFlow
  LoginIdentity->>IdentityBinding: classify_existing_native_identity(binding, keys)
  IdentityBinding->>LocalKeys: compare local key with canonical key
  IdentityBinding-->>LoginIdentity: return Ready, Unbound, or Mismatched
  ResumeFlow->>IdentityBinding: verify_existing_native_identity(binding, keys)
  IdentityBinding-->>ResumeFlow: return false, true, or validation error
Loading

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states that mismatched local identities now route to managed recovery, which is the main change.
Description check ✅ Passed The description directly explains the identity mismatch classification, recovery flow, preserved hard errors, tests, and scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/78-recovery-classification

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a51217a4c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +130 to +133
Ok(matches!(
classify_existing_native_identity(binding, keys)?,
ExistingNativeIdentity::Ready
))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep unbound memberships on the enrollment path

When a first-time managed membership has binding.public_key == None and a healthy local key exists, the classifier returns Unbound, but this match now reports the identity as not ready. complete_login therefore skips bind_identity, reaches its final unbound branch, and returns “could not establish a native identity,” blocking all new-membership enrollment. Treat Unbound as ready when local keys are present, while reserving the recovery path for Mismatched or missing local keys.

Useful? React with 👍 / 👎.

@100yenadmin

Copy link
Copy Markdown
Member Author

Independent identity/security review is in (Claude-side, cross-model against the Codex-authored diff). It returned BLOCK on one mechanical P0 and could not break the identity logic itself — explicit no on "can anything improper reach recovery", no on silent enrollment / unconfirmed rotation / cross-company adoption, yes on the resume path being safe and coherent, no on any accidentally-load-bearing invariant being weakened.

P0 — fixed in cd9f547

The reviewer reproduced exactly what CI caught: desktop/scripts/check-file-sizes.mjs enforces a hard 1000-line cap and evaos_teams.rs sat at exactly 1000, so +18 failed the ratchet. Desktop Core failed at Desktop lint and format, which skipped every Rust step — so the Rust results in my original table were local-only, not CI-proven. Fair hit.

Fixed as prescribed: ExistingNativeIdentity, classify_existing_native_identity, and verify_existing_native_identity moved into evaos_teams/identity_binding.rs, which already owns IdentityBinding and its validators and is not feature-gated (so the non-managed build keeps working). Net effect on the capped file is negative — the adapter's largest file is now smaller than before this PR.

P1 — test rename, fixed in cd9f547

healthy_mismatched_native_identity_is_rejected_without_reset was left asserting the opposite of the new reality. It also compared an immutable local value against itself and inspected a default() state the code under test never touches. Renamed to healthy_mismatched_native_identity_recovers_on_login_but_reauths_on_resume and rewritten to pin the actual invariant — the two callers deliberately diverge.

P1 — the new tests do not run in CI. Confirmed, not fixed here.

All four are behind #[cfg(feature = "evaos-teams-managed")]; default = ["system-keyring"] and no CI job passes that feature. So the adapter's identity code has no CI coverage at all today — that predates this PR. Not folded in, to keep this diff reviewable: follow-up PR covers it, and it has to also fix two pre-existing needless_borrow errors in identity_custody.rs that only appear once the feature is enabled.

P2 findings — flagged, deliberately not built

Per the program's security posture (flag, don't harden):

  1. Slightly widened entry into PendingIdentityReset: a second Mac with an auto-generated key and no enrolled envelope can now be offered canonical rotation. Not silent — it is behind an explicit destructive-confirm checkbox with a "sign out without replacing identity" alternative — and classify_recovery_issue_error requires both 404 and the exact code, so transient/5xx failures still hard-error.
  2. Local key destruction is now reachable with a healthy key present (mismatch → successful recovery overwrites the keyring entry), on a service name shared with unmanaged Buzz.

Both are consequences of #78's intended design rather than defects introduced here. Recording them so the decision is visible; happy to open issues if you'd rather track them.

Verification after cd9f547 (local)

Command Result
just desktop-check (the step that failed) PASS — ratchet included
just desktop-tauri-clippy PASS, clean
cargo fmt --check PASS
cargo test --features evaos-teams-managed evaos_teams 66 passed, 0 failed
cargo test evaos_teams (default features) 40 passed, 0 failed — the move does not break the non-managed build

CI is the real gate; watching it now.

@100yenadmin

100yenadmin commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Ready to merge — needs a human approver.

This is identity/authentication code, so it should not land on AI review alone. Flagging rather than merging.

Gate status:

  • CI: fully green — 23 success, 0 failure, including the Desktop Core job that failed on the first push.
  • Independent identity/security review (cross-model — Codex authored, Claude reviewed): initial BLOCK on the file-size ratchet plus a misnamed near-vacuous test; both fixed in cd9f547. Delta re-review: APPROVE, no new findings — confirmed the move is byte-identical in behavior, pub(super) does not widen the adapter boundary, no managed semantics leak into non-managed builds, and the rewritten test pins both callers in both directions.
  • Ratchet: evaos_teams.rs is now 981 lines, 19 below the cap — the adapter's largest file is smaller than before this PR.
  • Local, both feature configurations: cargo test --features evaos-teams-managed evaos_teams 66/0, cargo test evaos_teams 40/0.

What still is not proven after this merges, per #78's proof boundary: signed artifact, installed fresh-Mac recovery, custody keyring configuration in production, and the primary operator/Benjamin two-person acceptance. See the close-out plan comment on #78 — and note #92 first, since a canary built from the committed workflow would not contain this code at all.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@desktop/src-tauri/src/evaos_teams/identity_binding.rs`:
- Around line 37-40: Add a Rust doc comment to classify_existing_native_identity
documenting the classification contract: Unbound when no native identity is
bound, Ready when the existing binding matches the provided keys, and Mismatched
when a binding exists but does not match. Keep the documentation focused on this
pub(super) API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b7d27f54-2c96-4dd5-8a3d-24e8757cd6fe

📥 Commits

Reviewing files that changed from the base of the PR and between a37dfac and 79cad89.

📒 Files selected for processing (4)
  • desktop/src-tauri/src/evaos_teams.rs
  • desktop/src-tauri/src/evaos_teams/identity_binding.rs
  • desktop/src-tauri/src/evaos_teams/login_identity.rs
  • desktop/src-tauri/src/evaos_teams/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (11)
  • GitHub Check: Rust Lint
  • GitHub Check: Desktop Build (macOS)
  • GitHub Check: Desktop Smoke E2E (2)
  • GitHub Check: Desktop Smoke E2E (4)
  • GitHub Check: Windows Rust (x86_64-pc-windows-msvc)
  • GitHub Check: Desktop Smoke E2E (1)
  • GitHub Check: Desktop Smoke E2E (3)
  • GitHub Check: Desktop Core
  • GitHub Check: Desktop E2E Relay
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (rust)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Do not introduce unsafe Rust code.
Do not introduce new unwrap() or expect() calls in production Rust paths; use ? and proper error types.
Add doc comments to every new public Rust API.

Files:

  • desktop/src-tauri/src/evaos_teams.rs
  • desktop/src-tauri/src/evaos_teams/tests.rs
  • desktop/src-tauri/src/evaos_teams/login_identity.rs
  • desktop/src-tauri/src/evaos_teams/identity_binding.rs
🧠 Learnings (1)
📚 Learning: 2026-07-30T11:25:18.286Z
Learnt from: 100yenadmin
Repo: electricsheephq/evaOS-Hive PR: 82
File: desktop/src-tauri/src/shutdown.rs:258-294
Timestamp: 2026-07-30T11:25:18.286Z
Learning: When reviewing code that concerns the Hive Internal Canary supported architecture, use the supported-release-path model as the baseline: it is a single company, single relay/community, single VM, and single Hermes gateway. Do not treat multi-community runtime-pair behavior (e.g., a single local managed-agent record spanning multiple communities) as a “release-path regression” in review—this multi-community pattern is not a supported release path, so its presence should not trigger regression warnings.

Applied to files:

  • desktop/src-tauri/src/evaos_teams.rs
  • desktop/src-tauri/src/evaos_teams/tests.rs
  • desktop/src-tauri/src/evaos_teams/login_identity.rs
  • desktop/src-tauri/src/evaos_teams/identity_binding.rs
🔇 Additional comments (1)
desktop/src-tauri/src/evaos_teams/tests.rs (1)

631-632: 📐 Maintainability & Code Quality

Confirm evaos-teams-managed CI coverage.

The CI configuration does not establish whether a job runs the managed-feature tests and clippy checks. Add a job if no existing job enables evaos-teams-managed. Confidence: 50%.

Comment thread desktop/src-tauri/src/evaos_teams/identity_binding.rs
@evaos-code-review-bot

evaos-code-review-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

evaOS review status: failed

PR: #91 - fix(hive): route mismatched local identity to managed recovery
Head: 79cad89fe9183324e9f1cabd3a9531eaf435deb5
Updated: 2026-08-03T15:53:50.729Z

evaOS review failed for this head and needs retry or operator attention.

Automation note: agents should wait for this comment to reach completed, stale_head, closed_or_merged_before_review, skipped, or failed before treating evaOS review as settled for this head. provider_deferred means evaOS still intends to retry.

PR URL: #91

Details: Review failed; see bot evidence for operator-only details.

A Mac that completed Electric OAuth while holding a readable but non-canonical
native key hard-errored with "This device's native Buzz identity does not match
the canonical Hive identity" and returned to Sign In, without ever attempting
custody recovery.

local_identity_ready_for_login called verify_existing_native_identity with ?, so
the mismatch Err propagated straight out of complete_login. Control never
reached the recovery arm, making select_legacy_identity_candidate,
identity_custody::recover_identity, and the identity_reset_required fallback all
unreachable.

Classify the local identity as Ready / Unbound / Mismatched and treat only a
valid, readable mismatch as a recovery state, so complete_login falls through to
its existing recovery arm unchanged. Everything else stays a hard error: invalid
membership, invalid canonical key, locked or unreadable Keychain, and
IdentityRecoveryError::Other. verify_existing_native_identity keeps its Err on
mismatch for the session-resume path, which correctly defers to a fresh OAuth
sign-in rather than recovering in place. No silent new-key enrollment path is
introduced.

Refs #78
evaos_teams.rs sits exactly at the 1000-line desktop ratchet, so the added
classifier pushed it to 1018 and CI's Desktop Core job failed at desktop-check
before any Rust step ran.

Move ExistingNativeIdentity, classify_existing_native_identity, and
verify_existing_native_identity into evaos_teams/identity_binding.rs, which
already owns IdentityBinding and its validators and is not feature-gated. Net
effect on the capped file is negative; behavior is unchanged.

Also rename healthy_mismatched_native_identity_is_rejected_without_reset: under
this change a healthy mismatch is no longer 'rejected without reset' on managed
sign-in, it is routed to recovery. The test now pins the actual invariant - the
two callers diverge - instead of comparing an immutable local value against
itself and inspecting a default state the code under test never touches.
@100yenadmin
100yenadmin force-pushed the fix/78-recovery-classification branch from 79cad89 to 3b315ff Compare August 3, 2026 16:06
@evaos-code-review-bot

evaos-code-review-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

evaOS review status: failed

PR: #91 - fix(hive): route mismatched local identity to managed recovery
Head: 3b315ff3dd9373ca2c13dd08bc6bb0560610c142
Updated: 2026-08-03T16:08:00.805Z

evaOS review failed for this head and needs retry or operator attention.

Automation note: agents should wait for this comment to reach completed, stale_head, closed_or_merged_before_review, skipped, or failed before treating evaOS review as settled for this head. provider_deferred means evaOS still intends to retry.

PR URL: #91

Details: Review failed; see bot evidence for operator-only details.

@100yenadmin
100yenadmin merged commit 73c2ee3 into main Aug 3, 2026
30 checks passed
@100yenadmin

Copy link
Copy Markdown
Member Author

Fable sign-off: merged after CI went CLEAN. Independently verified before the force-push that the identity logic was byte-identical to the reviewed commit (identity_binding.rs and login_identity.rs both diff 0 lines vs cd9f547), so the rebase onto current main carried no silent change. Cross-model review record on this PR (Codex authored → Claude reviewed → BLOCK on two real issues → fixed → re-review APPROVE) is the pattern to keep for identity code. Lane E was right to refuse to self-merge this class; the merge came from the program session on the owner's direct authority.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant