mirror of
https://github.com/stablyai/orca.git
synced 2026-10-25 16:03:53 +00:00
* feat(agent-status): measure identity evidence before migrating any consumer PR 1 of the identity migration. It changes no displayed or routed identity — it only measures. Why measure first: the hierarchy shipped in #16148/#16157 has zero consumers, while ~31 sites still derive identity independently. Every migration decision after this is currently a guess, including the one that matters most — how often a real pane has no evidence at all. A live P0 reports "No Claude status shown", and this design trades toward showing nothing when uncertain, so the blank rate has to be a number before any surface moves. - `pane-agent-identity-evidence.ts` — one assembler that gathers a pane's evidence, so consumers stop each inventing their own ladder. - `pane-agent-identity-census.ts` — shadow-only counters keyed by host kind (native / wsl-host / wsl-distro / ssh / relay) and launch mode (typed / orca-launch / resume). Records a bitmask of which sources were present and whether the resolver returned null or ambiguous. No titles, prompts, paths, handles, or agent text. - `pane-agent-identity-inventory.test.ts` — a ratchet that fails when a legacy identity helper gains a new production caller, so the surface cannot grow while the migration runs. Three review findings are encoded rather than deferred: launch stays above run-key-less completed hooks (promoting the hook lets a stale record hijack a pane); OMP/Pi evidence is owner-normalized before assembly, since OMP emits Pi-compatible frames and a wrapper's hook would otherwise be read as the agent it wraps; and Windows-side `wsl.exe` is rejected as process evidence, because the host observes the distro wrapper rather than the agent inside it. The census cannot be completed from a worktree. It needs representative native, SSH, WSL and relay cohorts collected from real use, and that review is the gate on PR 3 — not this PR. * test(agent-status): keep identity migration inventory-only * test(agent-status): reuse reliable source scanner * test(agent-status): bound inventory scan work * test(agent-status): avoid inventory path false negatives * test(agent-status): refresh identity inventory after base repair * test(agent-status): correct inventory classifications * test(agent-status): correct action boundary inventory * test(agent-status): pin inventory occurrence counts * test(agent-status): fail closed on scanner desync
287 lines
11 KiB
TypeScript
287 lines
11 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
PANE_AGENT_EVIDENCE_SOURCES,
|
|
type PaneAgentEvidence,
|
|
resolvePaneAgentIdentity
|
|
} from './pane-agent-identity-resolver'
|
|
|
|
const resolve = (evidence: PaneAgentEvidence[], extra = {}) =>
|
|
resolvePaneAgentIdentity({ evidence, ...extra })
|
|
|
|
const H = 'authority-a'
|
|
|
|
describe('resolvePaneAgentIdentity', () => {
|
|
describe('a display title is the last thing consulted', () => {
|
|
it.each(PANE_AGENT_EVIDENCE_SOURCES.filter((s) => s !== 'title' && s !== 'sibling'))(
|
|
'lets %s outrank a conflicting title',
|
|
(source) => {
|
|
const result = resolve([
|
|
{ source: 'title', agent: 'codex' },
|
|
{ source, agent: 'grok' }
|
|
])
|
|
expect(result.agent).toBe('grok')
|
|
expect(result.source).toBe(source)
|
|
}
|
|
)
|
|
|
|
it('uses the title only when nothing else is eligible', () => {
|
|
expect(resolve([{ source: 'title', agent: 'codex' }])).toMatchObject({
|
|
agent: 'codex',
|
|
source: 'title'
|
|
})
|
|
})
|
|
|
|
it('outranks the launch record with nothing weaker than the launch record', () => {
|
|
// The tab ladder currently puts the parsed title ABOVE activeLaunchAgent, so a string Orca
|
|
// parsed beats a fact Orca owns. That inversion cannot be expressed here.
|
|
const result = resolve([
|
|
{ source: 'launch', agent: 'claude' },
|
|
{ source: 'title', agent: 'gemini' }
|
|
])
|
|
expect(result.agent).toBe('claude')
|
|
})
|
|
})
|
|
|
|
describe('run generation separates the bug from the legitimate reclaim', () => {
|
|
// Both shapes are `completed hook = A, title = B`. Ordering alone cannot tell them apart.
|
|
const shape = (hookRun: number, titleRun: number): PaneAgentEvidence[] => [
|
|
{ source: 'completed-hook', agent: 'claude', run: { authorityId: H, incarnation: hookRun } },
|
|
{ source: 'title', agent: 'codex', run: { authorityId: H, incarnation: titleRun } }
|
|
]
|
|
|
|
it('keeps the completed hook when both belong to the current run', () => {
|
|
// The reported bug: nothing new started, so the hook is still the truth.
|
|
const result = resolvePaneAgentIdentity({
|
|
evidence: shape(7, 7),
|
|
currentRun: { authorityId: H, incarnation: 7 }
|
|
})
|
|
expect(result).toMatchObject({ agent: 'claude', source: 'completed-hook' })
|
|
expect(result.supersededSources).toEqual([])
|
|
})
|
|
|
|
it('drops the completed hook once a new run has started', () => {
|
|
// The legitimate reclaim: the pane was reused, so run 7's hook describes an agent that is
|
|
// no longer there. It is ineligible, not merely outranked.
|
|
const result = resolvePaneAgentIdentity({
|
|
evidence: shape(7, 8),
|
|
currentRun: { authorityId: H, incarnation: 8 }
|
|
})
|
|
expect(result).toMatchObject({ agent: 'codex', source: 'title' })
|
|
expect(result.supersededSources).toEqual(['completed-hook'])
|
|
})
|
|
|
|
it('produces opposite answers from identical evidence, given only the run ids', () => {
|
|
// The whole point, stated as one assertion.
|
|
const bug = resolvePaneAgentIdentity({
|
|
evidence: shape(7, 7),
|
|
currentRun: { authorityId: H, incarnation: 7 }
|
|
})
|
|
const reclaim = resolvePaneAgentIdentity({
|
|
evidence: shape(7, 8),
|
|
currentRun: { authorityId: H, incarnation: 8 }
|
|
})
|
|
expect(bug.agent).not.toBe(reclaim.agent)
|
|
})
|
|
})
|
|
|
|
describe('mixed-version peers', () => {
|
|
it('keeps a run-key-less completed row from hijacking launch evidence', () => {
|
|
const result = resolve([
|
|
{ source: 'completed-hook', agent: 'claude' },
|
|
{ source: 'launch', agent: 'codex' }
|
|
])
|
|
expect(result).toMatchObject({ agent: 'codex', source: 'launch' })
|
|
})
|
|
|
|
it('treats evidence with no run id as eligible', () => {
|
|
// An old host publishes no run ids. Treating unknown as stale would blank every row.
|
|
const result = resolvePaneAgentIdentity({
|
|
evidence: [{ source: 'completed-hook', agent: 'claude' }],
|
|
currentRun: { authorityId: H, incarnation: 9 }
|
|
})
|
|
expect(result.agent).toBe('claude')
|
|
})
|
|
|
|
it('disables run filtering entirely when the pane has no current run', () => {
|
|
const result = resolvePaneAgentIdentity({
|
|
evidence: [
|
|
{ source: 'completed-hook', agent: 'claude', run: { authorityId: H, incarnation: 3 } }
|
|
]
|
|
})
|
|
expect(result).toMatchObject({ agent: 'claude', supersededSources: [] })
|
|
})
|
|
})
|
|
|
|
describe('siblings are tab-scoped', () => {
|
|
it('ignores a sibling by default', () => {
|
|
expect(resolve([{ source: 'sibling', agent: 'codex' }]).agent).toBeNull()
|
|
})
|
|
|
|
it('accepts a sibling when the caller opts in', () => {
|
|
expect(resolve([{ source: 'sibling', agent: 'codex' }], { allowSibling: true }).agent).toBe(
|
|
'codex'
|
|
)
|
|
})
|
|
|
|
it('still ranks a sibling above a title', () => {
|
|
const result = resolve(
|
|
[
|
|
{ source: 'title', agent: 'grok' },
|
|
{ source: 'sibling', agent: 'codex' }
|
|
],
|
|
{ allowSibling: true }
|
|
)
|
|
expect(result.source).toBe('sibling')
|
|
})
|
|
})
|
|
|
|
describe('no eligible evidence', () => {
|
|
it('returns null rather than guessing', () => {
|
|
expect(resolve([])).toMatchObject({ agent: null, source: null })
|
|
})
|
|
|
|
it('returns null when every source belongs to a superseded run', () => {
|
|
const result = resolvePaneAgentIdentity({
|
|
evidence: [
|
|
{ source: 'live-hook', agent: 'claude', run: { authorityId: H, incarnation: 1 } },
|
|
{ source: 'title', agent: 'codex', run: { authorityId: H, incarnation: 1 } }
|
|
],
|
|
currentRun: { authorityId: H, incarnation: 2 }
|
|
})
|
|
expect(result.agent).toBeNull()
|
|
expect(result.supersededSources).toEqual(['live-hook', 'title'])
|
|
})
|
|
})
|
|
|
|
describe('input order does not decide the answer', () => {
|
|
it('resolves the same regardless of how evidence is listed', () => {
|
|
const evidence: PaneAgentEvidence[] = [
|
|
{ source: 'title', agent: 'codex' },
|
|
{ source: 'launch', agent: 'grok' },
|
|
{ source: 'live-hook', agent: 'claude' }
|
|
]
|
|
const forward = resolve([...evidence])
|
|
const reverse = resolve(evidence.toReversed())
|
|
expect(forward).toEqual(reverse)
|
|
expect(forward.source).toBe('live-hook')
|
|
})
|
|
})
|
|
|
|
describe('two observations of the same class cannot be settled by array order', () => {
|
|
// Review finding: `eligible.find(...)` returned the FIRST match, so duplicates of one source
|
|
// naming different agents resolved by input order — the exact property this resolver exists to
|
|
// remove. The earlier order-independence test only used DISTINCT sources, so it never saw it.
|
|
it('returns null when two live hooks name different agents', () => {
|
|
const result = resolve([
|
|
{ source: 'live-hook', agent: 'claude' },
|
|
{ source: 'live-hook', agent: 'codex' }
|
|
])
|
|
expect(result.agent).toBeNull()
|
|
expect(result.ambiguousAt).toBe('live-hook')
|
|
})
|
|
|
|
it('gives the same answer in either order', () => {
|
|
const a: PaneAgentEvidence[] = [
|
|
{ source: 'live-hook', agent: 'claude' },
|
|
{ source: 'live-hook', agent: 'codex' }
|
|
]
|
|
expect(resolve(a)).toEqual(resolve(a.toReversed()))
|
|
})
|
|
|
|
it('still resolves when duplicates agree', () => {
|
|
expect(
|
|
resolve([
|
|
{ source: 'live-hook', agent: 'claude' },
|
|
{ source: 'live-hook', agent: 'claude' }
|
|
]).agent
|
|
).toBe('claude')
|
|
})
|
|
|
|
it('does not fall through to a weaker source on conflict', () => {
|
|
// Falling through would let a title answer whenever two hooks disagreed — strictly worse
|
|
// than saying nothing.
|
|
const result = resolve([
|
|
{ source: 'live-hook', agent: 'claude' },
|
|
{ source: 'live-hook', agent: 'codex' },
|
|
{ source: 'title', agent: 'grok' }
|
|
])
|
|
expect(result.agent).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('run keys from different authorities are incomparable, not stale', () => {
|
|
// Review finding: a bare numeric runId collides across authority restarts. A restarted main
|
|
// regenerates its authorityId and counts incarnations from its own floor, so `1 === 1` would
|
|
// equate two unrelated runs.
|
|
it('keeps evidence whose authority differs, even when the incarnations do not match', () => {
|
|
// The incarnations must DIFFER for this to discriminate. With both at 1, a resolver that
|
|
// ignored authority entirely would still pass on the numeric compare — verified by mutation,
|
|
// which is how the first version of this test was caught as vacuous.
|
|
const result = resolvePaneAgentIdentity({
|
|
evidence: [
|
|
{ source: 'live-hook', agent: 'claude', run: { authorityId: 'host-b', incarnation: 5 } }
|
|
],
|
|
currentRun: { authorityId: 'host-a', incarnation: 2 }
|
|
})
|
|
expect(result.agent).toBe('claude')
|
|
expect(result.supersededSources).toEqual([])
|
|
})
|
|
|
|
it('does not equate the same incarnation number from two authorities', () => {
|
|
// A restarted main regenerates authorityId and counts from its own floor, so `1` from
|
|
// host-b is not `1` from host-a. Incomparable is treated as unknown, so the evidence is
|
|
// kept rather than being read as either current or stale.
|
|
const result = resolvePaneAgentIdentity({
|
|
evidence: [
|
|
{ source: 'launch', agent: 'codex', run: { authorityId: 'host-b', incarnation: 1 } }
|
|
],
|
|
currentRun: { authorityId: 'host-a', incarnation: 1 }
|
|
})
|
|
expect(result).toMatchObject({ agent: 'codex', source: 'launch' })
|
|
})
|
|
|
|
it('supersedes only within the same authority', () => {
|
|
const result = resolvePaneAgentIdentity({
|
|
evidence: [
|
|
{ source: 'live-hook', agent: 'claude', run: { authorityId: 'host-a', incarnation: 1 } }
|
|
],
|
|
currentRun: { authorityId: 'host-a', incarnation: 2 }
|
|
})
|
|
expect(result.agent).toBeNull()
|
|
expect(result.supersededSources).toEqual(['live-hook'])
|
|
})
|
|
})
|
|
|
|
describe('an action consumer can refuse to see weak evidence at all', () => {
|
|
// Review finding: title remained available to consumers that AUTHORIZE A WRITE. Ranking it
|
|
// last makes misuse unlikely; dropping it makes misuse impossible.
|
|
it('ignores a title entirely below the floor', () => {
|
|
const result = resolve([{ source: 'title', agent: 'codex' }], { minimumSource: 'launch' })
|
|
expect(result.agent).toBeNull()
|
|
})
|
|
|
|
it('ignores a sibling below the floor even when opted in', () => {
|
|
const result = resolve([{ source: 'sibling', agent: 'codex' }], {
|
|
allowSibling: true,
|
|
minimumSource: 'launch'
|
|
})
|
|
expect(result.agent).toBeNull()
|
|
})
|
|
|
|
it('still answers from evidence at or above the floor', () => {
|
|
const result = resolve(
|
|
[
|
|
{ source: 'launch', agent: 'claude' },
|
|
{ source: 'title', agent: 'codex' }
|
|
],
|
|
{ minimumSource: 'launch' }
|
|
)
|
|
expect(result).toMatchObject({ agent: 'claude', source: 'launch' })
|
|
})
|
|
|
|
it('leaves display consumers unrestricted when no floor is given', () => {
|
|
expect(resolve([{ source: 'title', agent: 'codex' }]).agent).toBe('codex')
|
|
})
|
|
})
|
|
})
|