feat(mac): reverse-attach grants survive a daemon restart (#12) - #43
Merged
Merged
Conversation
Live grants are saved on create, replace, revoke, sweep and terminal end: the record in ~/Library/Application Support/oab-instance-mcp/grants.json (mode 600, dir 0700, atomic replace, narrowed if widened) and the attach secret in the login Keychain (service dev.openab.instance-mcp.grant, account = grant id, AfterFirstUnlockThisDeviceOnly). At start AttachManager.resume() re-dials every grant still inside its deadline under its original id. --no-grant-persistence keeps grants in memory. Secrets are deleted when their grant leaves the set. Tests use FileGrantStore with an in-memory SecretStore (CI has no login keychain): 9 new, full suite 109/109.
This was referenced Sep 30, 2026
Merged
jinwei-pikmin
added a commit
to jinwei-pikmin/instance-mcp
that referenced
this pull request
Sep 30, 2026
…e frames Parity with Swift openabdev#43 and the upstream Linux PoC openabdev#42: - attach/store.rs: live grants (not ended, not expired) in $XDG_STATE_HOME/oab-instance-mcp/grants.json — 0600 in a 0700 dir, narrowed again if widened, atomic replace, secret included (never in GET /attach). Saved on create, replace, revoke, sweep and when a dial loop ends for good (client exit hook), so revoked or runtime-ended grants are not resumed. - AttachManager::resume() at start re-dials each persisted grant still inside its deadline under its original id, profile resolved again (a custom profile that is gone is skipped and logged) and the remaining TTL carried over; the file is rewritten without expired or unresumable entries. - --no-grant-persistence keeps grants in memory only (Swift flag name). - On a Close frame from the runtime, flush tungstenite's echoing Close before leaving, so the runtime sees a clean close handshake instead of a bare EOF (the mock runtime reported close_echo_missing; upstream fixed the same). Tests: saved privately and dropped on revoke; a simulated restart redials the same id with the saved secret (the fake runtime 401s any other); expired and undefined-profile grants are not resumed; a runtime-revoked grant leaves the file. 73 pass. Live with upstream's mock_runtime.py (CLOSES=4006): the same grant id resumed across two real daemon restarts with the TTL carried over, and close_echo_missing dropped to 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
macOS side of #12 (Linux side: #42).
GrantStore/FileGrantStore: record →~/Library/Application Support/oab-instance-mcp/grants.json(mode 600 in a 0700 dir, atomic replace, narrowed if widened, no secret in it); secret → login Keychain, servicedev.openab.instance-mcp.grant, account = grant id,kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. Keychain behind aSecretStoreseam.AttachManager: persists on create / replace / revoke / sweep / terminal end;resume()at start re-dials every grant still inside its deadline under its original id. Secrets are deleted when their grant leaves the set.main.swift: on by default;--no-grant-persistencekeeps grants in memory. LinksSecurity.Deviation from the issue text: the record lives in a JSON file rather than UserDefaults — same shape as the Linux side, inspectable, and testable against a temp dir. The secret is in the Keychain as proposed.
Tests: 9 new (
GrantPersistenceTests: round trip without the secret in the file, perms, secret cleanup, expiry/orphan drop, narrowing, corrupt file, manager resume under same id, revoked not resumed, replaced leaves only the new one). Full suite 109/109 on macmini.Live acceptance (
launchctl kickstart -k→ re-attached, same id) needs a signed build on macmini; planned via the v0.6.7 notarized pkg.