Skip to content

fix(desktop): isolate contexts when Server identity changes - #2291

Open
jackie-cqz wants to merge 3 commits into
oceanbase:masterfrom
jackie-cqz:fix/desktop-deployment-identity
Open

jackie-cqz wants to merge 3 commits into
oceanbase:masterfrom
jackie-cqz:fix/desktop-deployment-identity

Conversation

@jackie-cqz

@jackie-cqz jackie-cqz commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Which issue or RFC does this PR close?

Refs #2101 (deployment identity portion of PR 01-B) and #2103, following RFC #1455. Both issues remain open for their remaining delivery and acceptance work.

Rationale for this change

Replacing a Server behind the same endpoint can preserve the same Principal while changing the deployment. Desktop previously rechecked only the Principal, allowing old Scope selections and late private results to cross that replacement.

What changes are included in this PR?

  • Bind native read snapshots and immutable write receipts to discovery server_id. Recheck deployment identity after collecting connection facts, before business requests, and before returning Scope/Memory results.
  • Clear the active context and its report on identity loss; invalidate Scope selections, cursors and pending reads. Package version changes alone preserve the deployment identity.
  • Preserve explicit qualified legacy handling only while discovery remains 404. Discovery appearing, disappearing, failing or becoming incompatible requires an explicit connection recheck.
  • Preserve the original write endpoint, Server ID, Principal, Scope and generation. If post-write verification fails, a successful HTTP reply becomes an unknown outcome without returned bodies/references or automatic replay. An old write cannot invalidate a newer active context.
  • Remove an earlier cached verification report before rechecking so failed checks cannot leave stale verification visible.
  • Keep uncertain submitted input in a window-local read-only recovery draft after native disconnection. Users can copy or explicitly discard it; confirmed navigation discards it, and late receipts cannot restore discarded input or retarget it.
  • Make installed unknown-write acceptance explicitly cancel and confirm navigation discard before starting the next Server scenario, verify no replay, and preserve the separate duplicate-risk confirmation for a subsequent save.
  • Preserve native session observation coverage and reset-window semantics alongside deployment rechecks.
  • Generate the additive nullable MemoryContext.serverId IPC field, display the original deployment in write recovery feedback, and document behavior and evidence in both README locales and the validation/security guides.

Are there any user-facing changes?

An observed deployment or Principal change disconnects the private context and requires rechecking the connection and selecting the Scope again. Unknown-write feedback identifies the original deployment and preserves the submitted input for manual recovery in the current window. Closing the window loses this recovery draft. No HTTP API, credential or persisted-profile format changes.

Identity is observed around requests. The public mutation contract has no conditional Server-identity precondition, so this cannot atomically prevent replacement between verification and dispatch or detect replacement that changes back between observations. Qualified legacy Servers have no durable deployment fence. Explicit readiness separation and local setup handoff remain follow-up work; existing anonymous readiness checks remain.

How was this change tested?

Windows 10 x64, Python 3.11.15 and Rust 1.95.0:

  • Four regression groups failed against the baseline before the fix. Final native suite: 84 passed, including eleven deployment-identity tests covering unchanged Principals, late reads, activation races, legacy transitions, original write targets, uncertain outcomes and preservation of a newer context.
  • cargo +1.95.0 test --locked --manifest-path desktop/src-tauri/Cargo.toml; Clippy with --all-targets -- -D warnings; Rust formatting and IPC export drift: passed.
  • Frontend: 122 passed, including the full App native-state refresh/disconnection path in both languages, read-only recovery copy/discard, cleared-form navigation after an uncertain write, and late successful/unknown receipts after confirmed context changes. TypeScript, lint/generated drift and production build: passed.
  • Built-wheel SQLite Server acceptance: anonymous loopback, static Bearer, HTTPS with explicit CA/base path and enforced Provider all passed through the native Manager. Includes save/search/exact reads, original Server ID receipts, lost-response writes without replay, Principal changes/revocation and independent Server survival.
  • Server source: aedf7beb1ecff2ea1fade6ccdd8e4bf6a298f799; wheel SHA-256: fde108a81a872a6228aad54832f2cd8f899a53bb4a7ff5c321871891fdbc5b68. This used the disposable fixture qualification with a dirty Desktop tree; executable/contract hashes and measured local round trips are recorded in desktop/VALIDATION.md. Production qualification records are unchanged.
  • Deployment replacement and post-write discovery 503 scenarios use synthetic TCP fixtures; frontend state-refresh regressions use mocked IPC. Real Server execution does not establish installed Windows 11/macOS acceptance.
  • uv run --no-sync prek run -a: all file hooks passed; default Windows ty check retains existing POSIX-only attribute diagnostics in unchanged Python files. ty check --python-platform linux: passed.

AI usage statement

OpenAI Codex (GPT-6) assisted with implementation, local review, regression reproduction, testing and documentation. Commands and real-Server harnesses were executed locally; qualification limits are recorded above.

Copilot AI balanced review requested due to automatic review settings October 28, 2026 08:06

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

.as_ref()
.map(|server| server.server_id.as_str()),
)
.await?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] Clear the cached report when the activation fence fails

When a previously checked profile is checked again and discovery changes or fails between observations, this early return leaves its previous entry in inner.reports although the active context was dropped. Clear or replace that report on failure so the subsequent state refresh cannot continue presenting the old deployment and Principal as verified.

@hidb4ai hidb4ai 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.

The post-write identity fence introduces a draft-loss regression (inline). The existing activation-report finding also reproduces at this commit.

.map(|record| record.artifact.clone())
.collect();
Some(value)
if let Err(error) = identity_result {

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.

[P2] Preserve the submitted draft when post-write verification disconnects

If /v1/memory/remember returns 200 but the subsequent discovery request temporarily returns 503, this new check clears active and increments the generation before returning unknown. NoteForm.save() then refreshes native state, and the [active?.generation] effect clears text and the dirty flag. The user loses the submitted text while being told to inspect an uncertain write; the retained WriteRecord contains no text to recover. I reproduced this through the native Manager and fed its actual state/receipt into MemoryWorkspace: both English and Chinese forms changed preserve me to an empty disabled textarea. The new unknown-write UI test misses this because form() returns the unchanged active state and uses a no-op onState. Keep the original draft available for explicit recovery/discard while clearing private Server results and preventing submission to a different target, and cover the native disconnect/state-refresh path.

Copilot AI balanced review requested due to automatic review settings October 29, 2026 05:18

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

Copilot AI balanced review requested due to automatic review settings October 29, 2026 13:10

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@knqiufan knqiufan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The normal post-write disconnection recovery and cached-report fixes are present. A separate retry path still loses the original uncertain submission; see the inline P2.

)
return;
if (ticket !== lifetime.current || generation.current !== original) return;
onUncertainDraft?.(null);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Preserve the uncertain draft when a retry fails before dispatch

After a first save loses its response while identity remains valid, the original text is retained with an unknown receipt. Confirming another save clears that recovery copy here, before the retry is dispatched. If the retry's pre-dispatch discovery check returns 503, the native Manager disconnects and rejects the call without replacing the original unknown receipt. The finally state refresh then remounts the form and clears its text, leaving no copy of the original submission even though the user never confirmed discarding it. I reproduced this using a synthetic TCP fixture's actual Manager states/receipts in the App test; retaining the existing recovery draft makes that test pass. Keep the original uncertain input through a failed retry until it is explicitly discarded or safely resolved, and cover this retry/disconnect path.

This branch has not been deployed

No deployments
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.

5 participants