mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 10:37:35 +00:00
* feat(pairing): re-pairing the same server keeps its environment The web client minted a new environment id on every runtime-scope pairing link, even for the same server key, so existing terminals kept the old id and failed as ambiguous. A key-matched re-pair now keeps the id (bumping the pairing revision) and pins the server's host descriptor; a same-key server with a different installation gets a new id, and a downgraded server never replaces the pin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): a reply from the replaced pairing never overwrites a same-server re-pair Re-pairing now keeps the environment id, so fence on the pairing revision too: a request captured before the re-pair can neither reopen a client with the replaced token nor save the old pairing back over the new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
1.9 KiB
TypeScript
48 lines
1.9 KiB
TypeScript
import type { RuntimeHostDescriptor } from './runtime-host-descriptor'
|
|
|
|
/**
|
|
* How a new pairing offer relates to a server this client already pairs with.
|
|
* `pin` is the descriptor to keep pinned. `descriptor`:
|
|
* - `pinned`: the offer's descriptor matches (or is now pinned for the first time).
|
|
* - `moved`: same installation, different machine binding: the profile moved or was reinstalled.
|
|
* - `unverifiable`: a pinned server now sends no descriptor (downgraded); the old pin is kept.
|
|
* - `unpinned`: neither side has a descriptor yet.
|
|
*/
|
|
export type RuntimeHostRePairMatch =
|
|
| {
|
|
kind: 'same-host'
|
|
pin: RuntimeHostDescriptor | undefined
|
|
descriptor: 'pinned' | 'moved' | 'unverifiable' | 'unpinned'
|
|
}
|
|
| { kind: 'different-host' }
|
|
|
|
/**
|
|
* The host key, which the pairing handshake proves, is the trust root: a different key is a
|
|
* different server. The descriptor only separates a reinstall or a deliberate clone that kept the key.
|
|
*/
|
|
export function classifyRuntimeHostRePair(
|
|
previous: { publicKeys: readonly string[]; pin?: RuntimeHostDescriptor },
|
|
offer: { publicKeyB64: string; hostDescriptor?: RuntimeHostDescriptor }
|
|
): RuntimeHostRePairMatch {
|
|
if (!previous.publicKeys.includes(offer.publicKeyB64)) {
|
|
return { kind: 'different-host' }
|
|
}
|
|
const { pin } = previous
|
|
const next = offer.hostDescriptor
|
|
if (!next) {
|
|
// Why keep the old pin: an older server must never let this client learn a replacement.
|
|
return { kind: 'same-host', pin, descriptor: pin ? 'unverifiable' : 'unpinned' }
|
|
}
|
|
if (!pin) {
|
|
return { kind: 'same-host', pin: next, descriptor: 'pinned' }
|
|
}
|
|
if (pin.installationId !== next.installationId) {
|
|
return { kind: 'different-host' }
|
|
}
|
|
const moved =
|
|
pin.machineBinding !== undefined &&
|
|
next.machineBinding !== undefined &&
|
|
pin.machineBinding !== next.machineBinding
|
|
return { kind: 'same-host', pin: next, descriptor: moved ? 'moved' : 'pinned' }
|
|
}
|