mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 09:37:33 +00:00
* fix(memory): bound session and lifecycle registries * fix(memory): bound transient filesystem registries * fix(memory): cap path and locale caches * fix(memory): bound runtime recovery registries * fix(memory): bound host mirror gap verdicts * fix(memory): bound shell startup env cache * fix(memory): bound gitlab host context cache * fix(memory): release removed ssh generations * fix(memory): expire cloud refresh replay guards * fix(memory): release retired plugin generations * fix(memory): bound plugin log key retention * fix(memory): bound automation authority generations * fix(memory): bound native chat enrichment cache * fix(memory): bound web session tracking generations * fix(memory): bound codex credential absence paths * fix(memory): bound WSL canonical path cache * fix(memory): bound sparse checkout cache * fix(memory): bound shared directory cache * fix(memory): bound advertised URL scan snapshots * fix(memory): bound automation manager cache * fix(memory): bound web session reorder intents * fix(memory): bound web session focus intents * fix(memory): bound web session handoffs * fix(memory): bound automation dispatch tokens * fix(memory): bound host mirror waiters * fix(memory): bound retained session activity * fix(memory): bound retained session activity * fix(memory): bound web session close intents * fix(memory): bound cloud session cache * fix(memory): bound WSL home cache * fix(memory): bound SSH capability cache * fix(memory): bound trust grant cooldowns * fix(memory): bound WSL auth drain state * fix(memory): bound Linear workspace credential cache * fix(memory): bound local Git capability cache * fix(memory): bound WSL Git environment cache * fix(memory): bound WSL Git environment cache * fix(memory): bound WSL preflight cache * fix(memory): keep hot cache entries warm * fix(memory): preserve generation fences across eviction * fix(memory): close remaining eviction fences * fix(memory): align evicted upstream generations * fix(memory): trim successful capability probes * fix(auth): retain expired refresh replay evidence --------- Co-authored-by: m4air <m4air@Mac.localdomain>
433 lines
16 KiB
TypeScript
433 lines
16 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { existsSyncMock, gitExecFileAsyncMock, readFileMock } = vi.hoisted(() => ({
|
|
existsSyncMock: vi.fn(),
|
|
gitExecFileAsyncMock: vi.fn(),
|
|
readFileMock: vi.fn()
|
|
}))
|
|
|
|
vi.mock('./runner', () => ({
|
|
gitExecFileAsync: gitExecFileAsyncMock,
|
|
gitStreamStdout: async (
|
|
args: string[],
|
|
options: { onStdout: (chunk: string) => boolean | void }
|
|
) => {
|
|
const { stdout } = await gitExecFileAsyncMock(args)
|
|
const stoppedEarly = options.onStdout(stdout ?? '') === true
|
|
return { stoppedEarly }
|
|
},
|
|
gitOptionalLocksDisabledEnv: (env: NodeJS.ProcessEnv = process.env) => ({
|
|
...env,
|
|
GIT_OPTIONAL_LOCKS: '0'
|
|
})
|
|
}))
|
|
|
|
vi.mock('fs/promises', () => ({
|
|
readFile: readFileMock
|
|
}))
|
|
|
|
vi.mock('fs', () => ({
|
|
existsSync: existsSyncMock
|
|
}))
|
|
|
|
function isConfigListSnapshotCommand(args: string[]): boolean {
|
|
return args[0] === 'config' && args[1] === '--list' && args[2] === '-z'
|
|
}
|
|
|
|
function emptyGitConfigSnapshot(): { stdout: string } {
|
|
return { stdout: 'core.repositoryformatversion\n0\0' }
|
|
}
|
|
|
|
import {
|
|
clearEffectiveUpstreamNegativeStatusCache,
|
|
clearEffectiveUpstreamStatusCacheForTests,
|
|
getEffectiveUpstreamStatusCacheCountForTests,
|
|
getEffectiveUpstreamStatusGenerationCountForTests,
|
|
getStatus
|
|
} from './status'
|
|
import {
|
|
getEffectiveUpstreamStatusWriteGeneration,
|
|
readCachedEffectiveUpstreamStatus,
|
|
rememberEffectiveUpstreamStatus
|
|
} from './source-control/effective-upstream-status-cache'
|
|
|
|
describe('local upstream negative cache', () => {
|
|
beforeEach(() => {
|
|
clearEffectiveUpstreamStatusCacheForTests()
|
|
existsSyncMock.mockReset()
|
|
gitExecFileAsyncMock.mockReset()
|
|
readFileMock.mockReset()
|
|
existsSyncMock.mockReturnValue(false)
|
|
readFileMock.mockResolvedValue('gitdir: /repo/.git/worktrees/feature\n')
|
|
})
|
|
|
|
it('bypasses a cached negative result for strict status reads', async () => {
|
|
let originBranchExists = false
|
|
gitExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
|
if (args.includes('status')) {
|
|
return {
|
|
stdout: '# branch.oid abcdef1234567890\n# branch.head feature\n'
|
|
}
|
|
}
|
|
if (args[0] === 'symbolic-ref' && args.includes('HEAD')) {
|
|
return { stdout: 'feature\n' }
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
throw new Error('fatal: no upstream configured for branch feature')
|
|
}
|
|
if (isConfigListSnapshotCommand(args)) {
|
|
return emptyGitConfigSnapshot()
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('refs/remotes/origin/feature')) {
|
|
if (originBranchExists) {
|
|
return { stdout: 'abc123\n' }
|
|
}
|
|
throw new Error('missing remote branch')
|
|
}
|
|
if (args[0] === 'rev-list' && args.includes('HEAD...origin/feature')) {
|
|
return { stdout: '0\t1\n' }
|
|
}
|
|
throw new Error(`unexpected git command: ${args.join(' ')}`)
|
|
})
|
|
|
|
const first = await getStatus('/repo')
|
|
originBranchExists = true
|
|
const automatic = await getStatus('/repo')
|
|
const strict = await getStatus('/repo', { bypassEffectiveUpstreamNegativeCache: true })
|
|
|
|
expect(first.upstreamStatus).toEqual({ hasUpstream: false, ahead: 0, behind: 0 })
|
|
expect(automatic.upstreamStatus).toEqual(first.upstreamStatus)
|
|
expect(strict.upstreamStatus).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'origin/feature',
|
|
ahead: 0,
|
|
behind: 1
|
|
})
|
|
})
|
|
|
|
it('keeps an older automatic negative probe from overwriting a strict positive result', async () => {
|
|
let originBranchExists = false
|
|
let deferredOriginReject: ((error: Error) => void) | null = null
|
|
let statusCommandCalls = 0
|
|
gitExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
|
if (args.includes('status')) {
|
|
statusCommandCalls += 1
|
|
return {
|
|
stdout: '# branch.oid abcdef1234567890\n# branch.head feature\n'
|
|
}
|
|
}
|
|
if (args[0] === 'symbolic-ref' && args.includes('HEAD')) {
|
|
return { stdout: 'feature\n' }
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
throw new Error('fatal: no upstream configured for branch feature')
|
|
}
|
|
if (isConfigListSnapshotCommand(args)) {
|
|
return emptyGitConfigSnapshot()
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('refs/remotes/origin/feature')) {
|
|
if (originBranchExists) {
|
|
return { stdout: 'abc123\n' }
|
|
}
|
|
return await new Promise<{ stdout: string }>((_, reject) => {
|
|
deferredOriginReject = reject
|
|
})
|
|
}
|
|
if (args[0] === 'rev-list' && args.includes('HEAD...origin/feature')) {
|
|
return { stdout: '0\t1\n' }
|
|
}
|
|
throw new Error(`unexpected git command: ${args.join(' ')}`)
|
|
})
|
|
|
|
const automatic = getStatus('/repo')
|
|
await vi.waitFor(() => expect(deferredOriginReject).toBeTruthy())
|
|
|
|
originBranchExists = true
|
|
const strict = await getStatus('/repo', { bypassEffectiveUpstreamNegativeCache: true })
|
|
expect(statusCommandCalls).toBe(2)
|
|
if (!deferredOriginReject) {
|
|
throw new Error('expected deferred origin reject')
|
|
}
|
|
;(deferredOriginReject as (error: Error) => void)(new Error('missing remote branch'))
|
|
const staleAutomatic = await automatic
|
|
const nextAutomatic = await getStatus('/repo')
|
|
|
|
expect(strict.upstreamStatus).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'origin/feature',
|
|
ahead: 0,
|
|
behind: 1
|
|
})
|
|
expect(staleAutomatic.upstreamStatus).toEqual({ hasUpstream: false, ahead: 0, behind: 0 })
|
|
expect(nextAutomatic.upstreamStatus).toEqual(strict.upstreamStatus)
|
|
})
|
|
|
|
it('does not trim generation for an unresolved automatic probe', async () => {
|
|
let originBranchExists = false
|
|
let deferredOriginReject: ((error: Error) => void) | null = null
|
|
const branchQueue = [
|
|
'feature',
|
|
'feature',
|
|
...Array.from({ length: 512 }, (_, index) => `other-${index}`),
|
|
'feature'
|
|
]
|
|
let currentBranch = 'feature'
|
|
gitExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
|
if (args.includes('status')) {
|
|
currentBranch = branchQueue.shift() ?? currentBranch
|
|
return {
|
|
stdout: `# branch.oid abcdef1234567890\n# branch.head ${currentBranch}\n`
|
|
}
|
|
}
|
|
if (args[0] === 'symbolic-ref' && args.includes('HEAD')) {
|
|
return { stdout: `${currentBranch}\n` }
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
throw new Error(`fatal: no upstream configured for branch ${currentBranch}`)
|
|
}
|
|
if (isConfigListSnapshotCommand(args)) {
|
|
return emptyGitConfigSnapshot()
|
|
}
|
|
if (args[0] === 'rev-parse' && args.some((arg) => arg.startsWith('refs/remotes/origin/'))) {
|
|
if (originBranchExists) {
|
|
return { stdout: 'abc123\n' }
|
|
}
|
|
return await new Promise<{ stdout: string }>((_, reject) => {
|
|
deferredOriginReject = reject
|
|
})
|
|
}
|
|
if (args[0] === 'rev-list' && args.some((arg) => arg.startsWith('HEAD...origin/'))) {
|
|
return { stdout: '0\t1\n' }
|
|
}
|
|
throw new Error(`unexpected git command: ${args.join(' ')}`)
|
|
})
|
|
|
|
const automatic = getStatus('/repo')
|
|
await vi.waitFor(() => expect(deferredOriginReject).toBeTruthy())
|
|
|
|
originBranchExists = true
|
|
const strict = await getStatus('/repo', { bypassEffectiveUpstreamNegativeCache: true })
|
|
for (let index = 0; index < 512; index += 1) {
|
|
await getStatus('/repo', { bypassEffectiveUpstreamNegativeCache: true })
|
|
}
|
|
if (!deferredOriginReject) {
|
|
throw new Error('expected deferred origin reject')
|
|
}
|
|
;(deferredOriginReject as (error: Error) => void)(new Error('missing remote branch'))
|
|
await automatic
|
|
const nextAutomatic = await getStatus('/repo')
|
|
|
|
expect(strict.upstreamStatus).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'origin/feature',
|
|
ahead: 0,
|
|
behind: 1
|
|
})
|
|
expect(nextAutomatic.upstreamStatus).toEqual(strict.upstreamStatus)
|
|
expect(getEffectiveUpstreamStatusGenerationCountForTests()).toBeLessThanOrEqual(512)
|
|
})
|
|
|
|
it('does not trim generation for a cleared automatic probe before it settles', async () => {
|
|
let originBranchExists = false
|
|
let deferredOriginReject: ((error: Error) => void) | null = null
|
|
const branchQueue = [
|
|
'feature',
|
|
...Array.from({ length: 512 }, (_, index) => `other-${index}`),
|
|
'feature'
|
|
]
|
|
let currentBranch = 'feature'
|
|
gitExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
|
if (args.includes('status')) {
|
|
currentBranch = branchQueue.shift() ?? currentBranch
|
|
return {
|
|
stdout: `# branch.oid abcdef1234567890\n# branch.head ${currentBranch}\n`
|
|
}
|
|
}
|
|
if (args[0] === 'symbolic-ref' && args.includes('HEAD')) {
|
|
return { stdout: `${currentBranch}\n` }
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
throw new Error(`fatal: no upstream configured for branch ${currentBranch}`)
|
|
}
|
|
if (isConfigListSnapshotCommand(args)) {
|
|
return emptyGitConfigSnapshot()
|
|
}
|
|
if (args[0] === 'rev-parse' && args.some((arg) => arg.startsWith('refs/remotes/origin/'))) {
|
|
if (originBranchExists) {
|
|
return { stdout: 'abc123\n' }
|
|
}
|
|
return await new Promise<{ stdout: string }>((_, reject) => {
|
|
deferredOriginReject = reject
|
|
})
|
|
}
|
|
if (args[0] === 'rev-list' && args.some((arg) => arg.startsWith('HEAD...origin/'))) {
|
|
return { stdout: '0\t1\n' }
|
|
}
|
|
throw new Error(`unexpected git command: ${args.join(' ')}`)
|
|
})
|
|
|
|
const automatic = getStatus('/repo')
|
|
await vi.waitFor(() => expect(deferredOriginReject).toBeTruthy())
|
|
|
|
originBranchExists = true
|
|
clearEffectiveUpstreamNegativeStatusCache({ worktreePath: '/repo', branchName: 'feature' })
|
|
for (let index = 0; index < 512; index += 1) {
|
|
await getStatus('/repo', { bypassEffectiveUpstreamNegativeCache: true })
|
|
}
|
|
if (!deferredOriginReject) {
|
|
throw new Error('expected deferred origin reject')
|
|
}
|
|
;(deferredOriginReject as (error: Error) => void)(new Error('missing remote branch'))
|
|
await automatic
|
|
const nextAutomatic = await getStatus('/repo')
|
|
|
|
expect(nextAutomatic.upstreamStatus).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'origin/feature',
|
|
ahead: 0,
|
|
behind: 1
|
|
})
|
|
expect(getEffectiveUpstreamStatusGenerationCountForTests()).toBeLessThanOrEqual(512)
|
|
})
|
|
|
|
it('does not let an evicted strict probe republish a stale negative', async () => {
|
|
let deferredOriginReject: ((error: Error) => void) | null = null
|
|
const branchQueue = ['feature', ...Array.from({ length: 512 }, (_, index) => `other-${index}`)]
|
|
let currentBranch = 'feature'
|
|
gitExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
|
if (args.includes('status')) {
|
|
currentBranch = branchQueue.shift() ?? currentBranch
|
|
return {
|
|
stdout: `# branch.oid abcdef1234567890\n# branch.head ${currentBranch}\n`
|
|
}
|
|
}
|
|
if (args[0] === 'symbolic-ref' && args.includes('HEAD')) {
|
|
return { stdout: `${currentBranch}\n` }
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
throw new Error(`fatal: no upstream configured for branch ${currentBranch}`)
|
|
}
|
|
if (isConfigListSnapshotCommand(args)) {
|
|
return emptyGitConfigSnapshot()
|
|
}
|
|
if (args[0] === 'rev-parse' && args.some((arg) => arg.startsWith('refs/remotes/origin/'))) {
|
|
if (currentBranch === 'feature') {
|
|
return await new Promise<{ stdout: string }>((_, reject) => {
|
|
deferredOriginReject = reject
|
|
})
|
|
}
|
|
return { stdout: 'abc123\n' }
|
|
}
|
|
if (args[0] === 'rev-list' && args.some((arg) => arg.startsWith('HEAD...origin/'))) {
|
|
return { stdout: '0\t1\n' }
|
|
}
|
|
throw new Error(`unexpected git command: ${args.join(' ')}`)
|
|
})
|
|
|
|
const strict = getStatus('/repo', { bypassEffectiveUpstreamNegativeCache: true })
|
|
await vi.waitFor(() => expect(deferredOriginReject).toBeTruthy())
|
|
clearEffectiveUpstreamNegativeStatusCache({ worktreePath: '/repo', branchName: 'feature' })
|
|
for (let index = 0; index < 512; index += 1) {
|
|
await getStatus(`/repo-${index}`, { bypassEffectiveUpstreamNegativeCache: true })
|
|
}
|
|
if (!deferredOriginReject) {
|
|
throw new Error('expected deferred origin reject')
|
|
}
|
|
;(deferredOriginReject as (error: Error) => void)(new Error('missing remote branch'))
|
|
await strict
|
|
|
|
expect(getEffectiveUpstreamStatusCacheCountForTests()).toBe(0)
|
|
expect(getEffectiveUpstreamStatusGenerationCountForTests()).toBe(512)
|
|
})
|
|
|
|
it('bounds effective-upstream negative entries', async () => {
|
|
let branchIndex = 0
|
|
let currentBranch = 'feature-0'
|
|
gitExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
|
if (args.includes('status')) {
|
|
currentBranch = `feature-${branchIndex}`
|
|
branchIndex += 1
|
|
return {
|
|
stdout: `# branch.oid abcdef1234567890\n# branch.head ${currentBranch}\n`
|
|
}
|
|
}
|
|
if (args[0] === 'symbolic-ref' && args.includes('HEAD')) {
|
|
return { stdout: `${currentBranch}\n` }
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
throw new Error(`fatal: no upstream configured for branch ${currentBranch}`)
|
|
}
|
|
if (isConfigListSnapshotCommand(args)) {
|
|
return emptyGitConfigSnapshot()
|
|
}
|
|
if (args[0] === 'rev-parse' && args.some((arg) => arg.startsWith('refs/remotes/origin/'))) {
|
|
throw new Error('missing remote branch')
|
|
}
|
|
throw new Error(`unexpected git command: ${args.join(' ')}`)
|
|
})
|
|
|
|
for (let index = 0; index < 513; index += 1) {
|
|
await getStatus('/repo')
|
|
}
|
|
|
|
expect(getEffectiveUpstreamStatusCacheCountForTests()).toBe(512)
|
|
expect(getEffectiveUpstreamStatusGenerationCountForTests()).toBeLessThanOrEqual(512)
|
|
})
|
|
|
|
it('bounds write-generation entries from positive strict probes', async () => {
|
|
let branchIndex = 0
|
|
let currentBranch = 'feature-0'
|
|
gitExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
|
if (args.includes('status')) {
|
|
currentBranch = `feature-${branchIndex}`
|
|
branchIndex += 1
|
|
return {
|
|
stdout: `# branch.oid abcdef1234567890\n# branch.head ${currentBranch}\n`
|
|
}
|
|
}
|
|
if (args[0] === 'symbolic-ref' && args.includes('HEAD')) {
|
|
return { stdout: `${currentBranch}\n` }
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
throw new Error(`fatal: no upstream configured for branch ${currentBranch}`)
|
|
}
|
|
if (isConfigListSnapshotCommand(args)) {
|
|
return emptyGitConfigSnapshot()
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes(`refs/remotes/origin/${currentBranch}`)) {
|
|
return { stdout: 'abc123\n' }
|
|
}
|
|
if (args[0] === 'rev-list' && args.includes(`HEAD...origin/${currentBranch}`)) {
|
|
return { stdout: '0\t1\n' }
|
|
}
|
|
throw new Error(`unexpected git command: ${args.join(' ')}`)
|
|
})
|
|
|
|
for (let index = 0; index < 513; index += 1) {
|
|
await getStatus('/repo', { bypassEffectiveUpstreamNegativeCache: true })
|
|
}
|
|
|
|
expect(getEffectiveUpstreamStatusCacheCountForTests()).toBe(0)
|
|
expect(getEffectiveUpstreamStatusGenerationCountForTests()).toBe(512)
|
|
})
|
|
|
|
it('continues caching new negatives after generation eviction', () => {
|
|
const now = Date.now()
|
|
for (let index = 0; index < 513; index += 1) {
|
|
rememberEffectiveUpstreamStatus(
|
|
`positive-${index}`,
|
|
{ hasUpstream: true, ahead: 0, behind: 1 },
|
|
now,
|
|
true,
|
|
0
|
|
)
|
|
}
|
|
const cacheKey = 'new-negative'
|
|
const writeGeneration = getEffectiveUpstreamStatusWriteGeneration(cacheKey)
|
|
const status = { hasUpstream: false, ahead: 0, behind: 0 }
|
|
rememberEffectiveUpstreamStatus(cacheKey, status, now, true, writeGeneration)
|
|
|
|
expect(readCachedEffectiveUpstreamStatus(cacheKey, now)).toEqual(status)
|
|
})
|
|
})
|