mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* test: strengthen launch, subject identity, and release witnesses * test: strengthen approval paths and prompt answer boundaries * test: strengthen conversation names and mobile log persistence * refactor: group background task stop contract with stop operations * test: strengthen question, title, and release validation * test: distinguish row text and generated dispatch titles * test: verify mobile revival events and retry exclusivity * test: reject incomplete child row output assertions * test: verify copied modules and option selection contracts * test: verify mobile host state and status retry boundaries * test: observe connector refusals and real diagnostic delivery * test: consolidate platform memory coverage and verify bounded work * test: allow cached legacy failure reads within work bounds * test: verify packaging boundaries with independent fixtures --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
133 lines
5.8 KiB
TypeScript
133 lines
5.8 KiB
TypeScript
/**
|
|
* Which launch inputs the replay digest covers, and — just as deliberately — which it does not.
|
|
*
|
|
* The digest decides whether a retry replays the first answer or is refused as a different call
|
|
* wearing the same id, so every field added to the launch wire is a decision in one of two
|
|
* directions. Both directions are pinned here: a behavioural field that fell OUT would let a retry
|
|
* carrying different arguments silently inherit the original's outcome, and a telemetry field that
|
|
* crept IN would refuse an honest retry that merely got re-attributed.
|
|
*/
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
computeAgentLaunchFingerprint,
|
|
type AgentLaunchFingerprintInput
|
|
} from './agent-launch-operation'
|
|
import { canonicalAgentSessionDigest } from './agent-session-mutation-envelope'
|
|
|
|
const BASE = {
|
|
agent: 'claude',
|
|
target: { kind: 'existing' as const, worktree: 'wt-1' }
|
|
}
|
|
const PANE_KEY = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d:3f2504e0-4f89-41d3-9a0c-0305e82c3301'
|
|
const OTHER_PANE_KEY = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d:6fa459ea-ee8a-4ca4-894e-db77e160355e'
|
|
|
|
/**
|
|
* The handler digests the whole params object, `launchSource` included — excess properties are only
|
|
* rejected on an object literal, never on the params value it actually passes. Going through this
|
|
* signature keeps the exclusion tests honest: they prove the digest IGNORES the field at runtime,
|
|
* not merely that the input type has no name for it.
|
|
*/
|
|
function fingerprintOfWirePayload(
|
|
params: AgentLaunchFingerprintInput & { launchSource?: string }
|
|
): string {
|
|
return computeAgentLaunchFingerprint(params)
|
|
}
|
|
|
|
describe('fields the launch fingerprint covers', () => {
|
|
it('separates two launches that differ only in agentArgs', () => {
|
|
expect(computeAgentLaunchFingerprint({ ...BASE, agentArgs: '--model opus' })).not.toBe(
|
|
computeAgentLaunchFingerprint({ ...BASE, agentArgs: '--model sonnet' })
|
|
)
|
|
})
|
|
|
|
it('treats an explicit "no arguments" as different from falling back to the settings default', () => {
|
|
// `null` and absent are the tri-state the wire keeps; collapsing them here would let a retry
|
|
// that cleared its arguments replay an answer produced with the user's configured ones.
|
|
expect(computeAgentLaunchFingerprint({ ...BASE, agentArgs: null })).not.toBe(
|
|
computeAgentLaunchFingerprint(BASE)
|
|
)
|
|
})
|
|
|
|
it('separates two launches that differ only in cwd', () => {
|
|
expect(computeAgentLaunchFingerprint({ ...BASE, cwd: '/repo/packages/a' })).not.toBe(
|
|
computeAgentLaunchFingerprint({ ...BASE, cwd: '/repo/packages/b' })
|
|
)
|
|
})
|
|
|
|
it('separates two launches that reserved different panes or sessions', () => {
|
|
// The key is baked into the pane's env; replaying another pane's key strands the new reservation.
|
|
expect(computeAgentLaunchFingerprint({ ...BASE, paneKey: PANE_KEY })).not.toBe(
|
|
computeAgentLaunchFingerprint({ ...BASE, paneKey: OTHER_PANE_KEY })
|
|
)
|
|
expect(computeAgentLaunchFingerprint({ ...BASE, paneKey: PANE_KEY })).not.toBe(
|
|
computeAgentLaunchFingerprint(BASE)
|
|
)
|
|
const sessionId = '11111111-1111-4111-8111-111111111111'
|
|
expect(computeAgentLaunchFingerprint({ ...BASE, sessionId })).not.toBe(
|
|
computeAgentLaunchFingerprint({ ...BASE, sessionId: '22222222-2222-4222-8222-222222222222' })
|
|
)
|
|
expect(computeAgentLaunchFingerprint({ ...BASE, sessionId })).not.toBe(
|
|
computeAgentLaunchFingerprint(BASE)
|
|
)
|
|
})
|
|
|
|
it('separates a retry that picked another surface, or none, from the first launch', () => {
|
|
const terminal = computeAgentLaunchFingerprint({ ...BASE, surface: 'terminal' })
|
|
expect(terminal).not.toBe(computeAgentLaunchFingerprint({ ...BASE, surface: 'chat' }))
|
|
expect(terminal).not.toBe(computeAgentLaunchFingerprint(BASE))
|
|
})
|
|
})
|
|
|
|
describe('fields the launch fingerprint deliberately ignores', () => {
|
|
it('does not separate two launches that differ only in launchSource', () => {
|
|
// Telemetry. Two buttons producing the same launch are one operation, and a retry that got
|
|
// re-attributed must replay rather than be refused as a conflict.
|
|
expect(fingerprintOfWirePayload({ ...BASE, launchSource: 'shortcut' })).toBe(
|
|
fingerprintOfWirePayload({ ...BASE, launchSource: 'tab_bar_quick_launch' })
|
|
)
|
|
})
|
|
|
|
it('ignores launchSource entirely, so sending one matches sending none', () => {
|
|
expect(fingerprintOfWirePayload({ ...BASE, launchSource: 'sidebar' })).toBe(
|
|
computeAgentLaunchFingerprint(BASE)
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('compatibility with rows written before these fields existed', () => {
|
|
/**
|
|
* A client that sends none of the new fields must still digest to what the previous build
|
|
* produced, or an upgraded host would refuse the in-flight retries of every launch admitted
|
|
* before it restarted. This is the old expression verbatim rather than a captured constant, so it
|
|
* keeps checking the property rather than a value someone can re-record.
|
|
*/
|
|
it('matches the pre-existing digest when no new field is sent', () => {
|
|
const previousBuild = canonicalAgentSessionDigest({
|
|
method: 'agent.launch',
|
|
agent: BASE.agent,
|
|
target: BASE.target,
|
|
prompt: undefined,
|
|
sessionOptions: undefined,
|
|
reuseTerminal: undefined
|
|
})
|
|
expect(computeAgentLaunchFingerprint(BASE)).toBe(previousBuild)
|
|
})
|
|
|
|
it('matches the digest from before the pane key existed when no pane is reserved', () => {
|
|
const beforePaneKey = canonicalAgentSessionDigest({
|
|
method: 'agent.launch',
|
|
agent: BASE.agent,
|
|
target: BASE.target,
|
|
prompt: undefined,
|
|
sessionOptions: undefined,
|
|
reuseTerminal: undefined,
|
|
agentArgs: '--model opus',
|
|
cwd: '/repo/packages/a'
|
|
})
|
|
expect(
|
|
computeAgentLaunchFingerprint({ ...BASE, agentArgs: '--model opus', cwd: '/repo/packages/a' })
|
|
).toBe(beforePaneKey)
|
|
})
|
|
})
|