Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
403 lines
13 KiB
TypeScript
403 lines
13 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
DAEMON_GONE_PROOFS,
|
|
probeDaemonProcessIdentity,
|
|
WINDOWS_CREATION_TIME_TOLERANCE_MS,
|
|
type DaemonProcessProbeDependencies,
|
|
type ExactDaemonIncarnation
|
|
} from './daemon-incarnation-evidence'
|
|
import {
|
|
classifyDaemonAuditFailure,
|
|
recordAuthenticatedInventory,
|
|
type DaemonAuditClassifierDependencies,
|
|
type DaemonAuditContext
|
|
} from './daemon-audit-classifier'
|
|
|
|
const endpoint = { socketPath: '/runtime/daemon.sock', tokenPath: '/runtime/daemon.token' }
|
|
const exactIncarnation: ExactDaemonIncarnation = {
|
|
identity: { pid: 42, startedAtMs: 1_700_000_000_000, launchNonce: 'launch-a' },
|
|
linuxStartTicks: '4242',
|
|
bootId: 'boot-a'
|
|
}
|
|
const auditClassifierDependencies = {
|
|
probeProcessIdentity: async () => ({
|
|
state: 'unknown',
|
|
reason: 'inspection_failed',
|
|
evidenceSources: ['process_signal']
|
|
}),
|
|
inspectEndpointState: async (context) =>
|
|
context.endpointKind === 'windows-named-pipe' ? 'named-pipe' : 'missing'
|
|
} satisfies DaemonAuditClassifierDependencies
|
|
|
|
function linuxStat(state: string, startTicks: string): string {
|
|
return `42 (orca daemon with spaces) ${[state, ...Array(18).fill('0'), startTicks].join(' ')}`
|
|
}
|
|
|
|
function linuxDependencies(
|
|
overrides: DaemonProcessProbeDependencies = {}
|
|
): DaemonProcessProbeDependencies {
|
|
return {
|
|
platform: 'linux',
|
|
signalProcess: () => 'occupied',
|
|
readLinuxStat: async () => ({ status: 'present', value: linuxStat('S', '4242') }),
|
|
readBootIdentity: async () => 'boot-a',
|
|
readCommandLine: async () =>
|
|
`node daemon-entry --socket ${endpoint.socketPath} --token ${endpoint.tokenPath}`,
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
describe('daemon process identity evidence', () => {
|
|
it('pins the dated conclusive-gone oracle contract', () => {
|
|
expect(DAEMON_GONE_PROOFS).toEqual({
|
|
linux: ['pid_missing', 'linux_boot_changed', 'linux_start_ticks_mismatch', 'linux_zombie'],
|
|
darwin: ['pid_missing'],
|
|
win32: [
|
|
'windows_process_missing',
|
|
'windows_creation_time_mismatch',
|
|
'windows_named_pipe_missing'
|
|
]
|
|
})
|
|
})
|
|
|
|
it('proves Linux pid reuse from native start ticks without derived milliseconds', async () => {
|
|
const readProcessStartedAtMs = vi.fn(async () => exactIncarnation.identity.startedAtMs)
|
|
|
|
await expect(
|
|
probeDaemonProcessIdentity(
|
|
exactIncarnation,
|
|
endpoint,
|
|
linuxDependencies({
|
|
readLinuxStat: async () => ({ status: 'present', value: linuxStat('S', '4243') }),
|
|
readProcessStartedAtMs
|
|
})
|
|
)
|
|
).resolves.toMatchObject({
|
|
state: 'gone',
|
|
reason: 'linux_start_ticks_mismatch'
|
|
})
|
|
expect(readProcessStartedAtMs).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('proves Linux reboot invalidated the recorded incarnation', async () => {
|
|
await expect(
|
|
probeDaemonProcessIdentity(
|
|
exactIncarnation,
|
|
endpoint,
|
|
linuxDependencies({ readBootIdentity: async () => 'boot-b' })
|
|
)
|
|
).resolves.toMatchObject({ state: 'gone', reason: 'linux_boot_changed' })
|
|
})
|
|
|
|
it('treats a matching unreaped Linux zombie as gone', async () => {
|
|
await expect(
|
|
probeDaemonProcessIdentity(
|
|
exactIncarnation,
|
|
endpoint,
|
|
linuxDependencies({
|
|
readLinuxStat: async () => ({ status: 'present', value: linuxStat('Z', '4242') })
|
|
})
|
|
)
|
|
).resolves.toMatchObject({ state: 'gone', reason: 'linux_zombie' })
|
|
})
|
|
|
|
it('keeps EPERM unknown without an independent identity match', async () => {
|
|
await expect(
|
|
probeDaemonProcessIdentity(
|
|
exactIncarnation,
|
|
endpoint,
|
|
linuxDependencies({
|
|
signalProcess: () => 'permission_denied',
|
|
readLinuxStat: async () => ({ status: 'unavailable' })
|
|
})
|
|
)
|
|
).resolves.toMatchObject({ state: 'unknown', reason: 'permission_denied' })
|
|
})
|
|
|
|
it('keeps a missing proc stat unknown when signal zero still sees the pid', async () => {
|
|
await expect(
|
|
probeDaemonProcessIdentity(
|
|
exactIncarnation,
|
|
endpoint,
|
|
linuxDependencies({
|
|
readLinuxStat: async () => ({ status: 'missing' })
|
|
})
|
|
)
|
|
).resolves.toMatchObject({ state: 'unknown', reason: 'inspection_failed' })
|
|
})
|
|
|
|
it('keeps an unreadable command line unknown even when Linux start identity matches', async () => {
|
|
await expect(
|
|
probeDaemonProcessIdentity(
|
|
exactIncarnation,
|
|
endpoint,
|
|
linuxDependencies({ readCommandLine: async () => undefined })
|
|
)
|
|
).resolves.toMatchObject({ state: 'unknown', reason: 'command_line_unavailable' })
|
|
})
|
|
|
|
it('never treats signal-zero success alone as present', async () => {
|
|
await expect(
|
|
probeDaemonProcessIdentity(
|
|
{ identity: exactIncarnation.identity },
|
|
endpoint,
|
|
linuxDependencies({
|
|
readCommandLine: async () => undefined
|
|
})
|
|
)
|
|
).resolves.toMatchObject({ state: 'unknown' })
|
|
})
|
|
|
|
it('never turns a legacy identity gap into gone', async () => {
|
|
await expect(
|
|
probeDaemonProcessIdentity(null, endpoint, linuxDependencies())
|
|
).resolves.toMatchObject({ state: 'unknown', reason: 'exact_identity_unavailable' })
|
|
})
|
|
|
|
it('uses Windows CreationDate as the primary identity regardless of command line', async () => {
|
|
const base = {
|
|
platform: 'win32' as const,
|
|
signalProcess: () => 'occupied' as const
|
|
}
|
|
await expect(
|
|
probeDaemonProcessIdentity(exactIncarnation, endpoint, {
|
|
...base,
|
|
queryWindowsProcess: async () => ({
|
|
status: 'present',
|
|
commandLine: 'unreadable',
|
|
startedAtMs:
|
|
exactIncarnation.identity.startedAtMs + WINDOWS_CREATION_TIME_TOLERANCE_MS + 1
|
|
})
|
|
})
|
|
).resolves.toMatchObject({
|
|
state: 'gone',
|
|
reason: 'windows_creation_time_mismatch'
|
|
})
|
|
await expect(
|
|
probeDaemonProcessIdentity(exactIncarnation, endpoint, {
|
|
...base,
|
|
queryWindowsProcess: async () => ({
|
|
status: 'present',
|
|
commandLine: null,
|
|
startedAtMs: exactIncarnation.identity.startedAtMs
|
|
})
|
|
})
|
|
).resolves.toMatchObject({
|
|
state: 'present',
|
|
reason: 'windows_identity_match'
|
|
})
|
|
await expect(
|
|
probeDaemonProcessIdentity(exactIncarnation, endpoint, {
|
|
...base,
|
|
queryWindowsProcess: async () => ({
|
|
status: 'present',
|
|
commandLine: 'unrelated process',
|
|
startedAtMs: exactIncarnation.identity.startedAtMs
|
|
})
|
|
})
|
|
).resolves.toMatchObject({
|
|
state: 'present',
|
|
reason: 'windows_identity_match'
|
|
})
|
|
await expect(
|
|
probeDaemonProcessIdentity(exactIncarnation, endpoint, {
|
|
...base,
|
|
queryWindowsProcess: async () => ({
|
|
status: 'present',
|
|
commandLine: 'node daemon-entry',
|
|
startedAtMs: null
|
|
})
|
|
})
|
|
).resolves.toMatchObject({
|
|
state: 'unknown',
|
|
reason: 'windows_process_start_time_unavailable'
|
|
})
|
|
await expect(
|
|
probeDaemonProcessIdentity(exactIncarnation, endpoint, {
|
|
...base,
|
|
queryWindowsProcess: async () => ({ status: 'missing' })
|
|
})
|
|
).resolves.toMatchObject({
|
|
state: 'gone',
|
|
reason: 'windows_process_missing'
|
|
})
|
|
})
|
|
|
|
it('requires Windows CIM evidence even when signal-zero reports a missing pid', async () => {
|
|
await expect(
|
|
probeDaemonProcessIdentity(exactIncarnation, endpoint, {
|
|
platform: 'win32',
|
|
signalProcess: () => 'missing',
|
|
queryWindowsProcess: async () => ({
|
|
status: 'present',
|
|
commandLine: null,
|
|
startedAtMs: exactIncarnation.identity.startedAtMs
|
|
})
|
|
})
|
|
).resolves.toMatchObject({
|
|
state: 'present',
|
|
reason: 'windows_identity_match'
|
|
})
|
|
})
|
|
|
|
it('keeps a macOS lstart mismatch unknown', async () => {
|
|
await expect(
|
|
probeDaemonProcessIdentity(exactIncarnation, endpoint, {
|
|
platform: 'darwin',
|
|
signalProcess: () => 'occupied',
|
|
readCommandLine: async () =>
|
|
`node daemon-entry --socket ${endpoint.socketPath} --token ${endpoint.tokenPath}`,
|
|
readProcessStartedAtMs: async () => exactIncarnation.identity.startedAtMs + 2_500
|
|
})
|
|
).resolves.toMatchObject({ state: 'unknown', reason: 'macos_start_time_mismatch' })
|
|
})
|
|
|
|
it('accepts ESRCH as conclusive POSIX process disappearance', async () => {
|
|
await expect(
|
|
probeDaemonProcessIdentity(exactIncarnation, endpoint, {
|
|
platform: 'darwin',
|
|
signalProcess: () => 'missing'
|
|
})
|
|
).resolves.toMatchObject({ state: 'gone', reason: 'pid_missing' })
|
|
})
|
|
|
|
it('keeps unsupported platforms indeterminate without running a Darwin probe', async () => {
|
|
const signalProcess = vi.fn(() => 'occupied' as const)
|
|
const readCommandLine = vi.fn(async () => 'node daemon-entry')
|
|
|
|
await expect(
|
|
probeDaemonProcessIdentity(exactIncarnation, endpoint, {
|
|
platform: 'freebsd',
|
|
signalProcess,
|
|
readCommandLine
|
|
})
|
|
).resolves.toMatchObject({ state: 'unknown', reason: 'inspection_failed' })
|
|
expect(signalProcess).not.toHaveBeenCalled()
|
|
expect(readCommandLine).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('daemon audit availability evidence', () => {
|
|
const context: DaemonAuditContext = {
|
|
protocolGeneration: 23,
|
|
provider: 'local-daemon',
|
|
endpoint: endpoint.socketPath,
|
|
tokenPath: endpoint.tokenPath,
|
|
endpointKind: 'unix-socket',
|
|
profileScope: '/profile'
|
|
}
|
|
|
|
it('records authoritative inventory as present without endpoint identity', () => {
|
|
expect(recordAuthenticatedInventory(context, null)).toMatchObject({
|
|
state: 'present',
|
|
reason: 'authenticated_inventory',
|
|
exactIncarnation: null,
|
|
inventoryAuthority: 'authoritative'
|
|
})
|
|
})
|
|
|
|
it('represents failed legacy inventory as unknown, never an empty process list', async () => {
|
|
const observation = await classifyDaemonAuditFailure(context, 'inventory_failed', null, {
|
|
dependencies: auditClassifierDependencies
|
|
})
|
|
|
|
expect(observation).toMatchObject({
|
|
state: 'unknown',
|
|
reason: 'inventory_failed',
|
|
inventoryAuthority: 'unavailable',
|
|
processLiveness: 'unknown'
|
|
})
|
|
expect(observation.evidenceSources.length).toBeGreaterThan(0)
|
|
expect(observation).not.toHaveProperty('processes')
|
|
})
|
|
|
|
it('keeps token removal after disconnect as contributing evidence only', async () => {
|
|
const observation = await classifyDaemonAuditFailure(
|
|
context,
|
|
'token_missing_after_authenticated_disconnect',
|
|
null,
|
|
{
|
|
additionalEvidenceSources: ['token_file'],
|
|
dependencies: auditClassifierDependencies
|
|
}
|
|
)
|
|
|
|
expect(observation.state).toBe('unknown')
|
|
expect(observation.evidenceSources).toContain('token_file')
|
|
})
|
|
|
|
it('accepts Windows named-pipe absence only with exact incarnation evidence', async () => {
|
|
const windowsContext: DaemonAuditContext = {
|
|
...context,
|
|
endpoint: '\\\\?\\pipe\\orca-daemon',
|
|
endpointKind: 'windows-named-pipe'
|
|
}
|
|
const observation = await classifyDaemonAuditFailure(
|
|
windowsContext,
|
|
'inventory_failed',
|
|
exactIncarnation,
|
|
{
|
|
additionalEvidenceSources: ['windows_named_pipe'],
|
|
endpointGoneProof: 'windows_named_pipe_missing',
|
|
dependencies: auditClassifierDependencies
|
|
}
|
|
)
|
|
|
|
expect(observation).toMatchObject({
|
|
state: 'gone',
|
|
reason: 'windows_named_pipe_missing',
|
|
endpointState: 'missing',
|
|
exactIncarnation
|
|
})
|
|
})
|
|
|
|
it('keeps contradictory Windows pipe and process evidence unknown', async () => {
|
|
const windowsContext: DaemonAuditContext = {
|
|
...context,
|
|
endpoint: '\\\\?\\pipe\\orca-daemon',
|
|
endpointKind: 'windows-named-pipe'
|
|
}
|
|
const dependencies = {
|
|
...auditClassifierDependencies,
|
|
probeProcessIdentity: async () => ({
|
|
state: 'present',
|
|
reason: 'windows_identity_match',
|
|
evidenceSources: ['windows_cim', 'endpoint_identity']
|
|
})
|
|
} satisfies DaemonAuditClassifierDependencies
|
|
|
|
await expect(
|
|
classifyDaemonAuditFailure(windowsContext, 'inventory_failed', exactIncarnation, {
|
|
additionalEvidenceSources: ['windows_named_pipe'],
|
|
endpointGoneProof: 'windows_named_pipe_missing',
|
|
dependencies
|
|
})
|
|
).resolves.toMatchObject({
|
|
state: 'unknown',
|
|
reason: 'inventory_failed',
|
|
processLiveness: 'present',
|
|
processReason: 'windows_identity_match',
|
|
endpointState: 'missing'
|
|
})
|
|
})
|
|
|
|
it('rejects named-pipe disappearance as a gone proof for Unix endpoints', async () => {
|
|
const observation = await classifyDaemonAuditFailure(
|
|
context,
|
|
'inventory_failed',
|
|
exactIncarnation,
|
|
{
|
|
additionalEvidenceSources: ['windows_named_pipe'],
|
|
endpointGoneProof: 'windows_named_pipe_missing',
|
|
dependencies: auditClassifierDependencies
|
|
}
|
|
)
|
|
|
|
expect(observation).toMatchObject({
|
|
state: 'unknown',
|
|
reason: 'inventory_failed',
|
|
processLiveness: 'unknown'
|
|
})
|
|
})
|
|
})
|