fix(agents): preserve canonical coverage provenance

This commit is contained in:
Merge Sim
2026-09-02 20:25:44 -07:00
parent e998237f9f
commit cf269289e4
3 changed files with 46 additions and 7 deletions
+19 -1
View File
@@ -120,6 +120,19 @@ describe('uncovered compatibility lane', () => {
})
expect(identity).toMatchObject({ agent: null, source: null, coverage: 'uncovered' })
})
it('does not label a foreground-only compatibility answer as title-only', () => {
const identity = resolveCanonicalPaneAgentIdentity({
foregroundAgent: 'codex',
uncoveredFallback: { agent: 'codex' }
})
expect(identity).toMatchObject({
agent: 'codex',
source: null,
coverage: 'uncovered',
titleOnly: false
})
})
})
describe('canonical ladder inside the covered lane', () => {
@@ -178,7 +191,12 @@ describe('reclaim-versus-stale-hook discriminator (run keys, not title text)', (
currentRun: run2,
title: 'STA-4011 Linux Antigravity Commit Messages - grok'
})
expect(identity).toMatchObject({ agent: 'grok', source: 'title' })
expect(identity).toMatchObject({
agent: 'grok',
source: 'title',
coverage: 'uncovered',
titleOnly: true
})
expect(identity.supersededSources).toEqual(['completed-hook'])
})
+19 -4
View File
@@ -145,9 +145,24 @@ export function resolveCanonicalPaneAgentIdentity(
input: CanonicalPaneAgentIdentityInput
): CanonicalPaneAgentIdentity {
const processEvidence = processEvidenceFromProof(input)
// Coverage comes from authority-bearing sources only: hook, proven process, launch, sleeping
// session. A bare foreground name, a sibling, and a title never cover a pane.
// Coverage comes from authority-bearing sources that are still eligible for this run. A stale
// hook/launch row can remain in the input after a pane is replaced; it must not make a title-only
// answer look covered to a future action consumer.
const runIsEligible = (run: PaneAgentRunKey | undefined): boolean =>
run === undefined ||
input.currentRun === undefined ||
run.authorityId !== input.currentRun.authorityId ||
run.incarnation === input.currentRun.incarnation
const covered = Boolean(
(input.hookAgent && runIsEligible(input.hookRun)) ||
(input.completedHookAgent && runIsEligible(input.completedHookRun)) ||
processEvidence ||
(input.launchAgent && runIsEligible(input.launchRun)) ||
(input.sleepingSessionAgent && runIsEligible(input.sleepingRun))
)
// Keep stale evidence in the resolver so diagnostics still report which source was superseded,
// even when it no longer qualifies the pane as covered.
const hasAuthorityEvidence = Boolean(
input.hookAgent ||
input.completedHookAgent ||
processEvidence ||
@@ -156,7 +171,7 @@ export function resolveCanonicalPaneAgentIdentity(
)
const titleAgent = input.title ? collectAgentTitleEvidence(input.title).agent : null
if (!covered) {
if (!hasAuthorityEvidence) {
if (input.uncoveredFallback) {
const agent = input.uncoveredFallback.agent
const titleOnly =
@@ -227,7 +242,7 @@ export function resolveCanonicalPaneAgentIdentity(
return {
agent: resolved.agent,
source: resolved.source,
coverage: 'covered',
coverage: covered ? 'covered' : 'uncovered',
titleOnly: resolved.source === 'title',
...(resolved.ambiguousAt ? { ambiguousAt: resolved.ambiguousAt } : {}),
supersededSources: resolved.supersededSources
@@ -65,15 +65,21 @@ function recordCanonicalDivergence(
}
// No host process PROOF exists yet, so the canonical lane sees the foreground name as a weak
// hint only. Where that alone flips the answer is precisely what this window measures.
const titleAgent = args.title ? collectAgentTitleEvidence(args.title).agent : null
const canonical = resolveCanonicalPaneAgentIdentity({
hookAgent: args.hookAgent,
hookIsLive: args.hookIsLive,
launchAgent: args.launchAgent,
foregroundAgent: args.foregroundAgent,
title: args.title,
uncoveredFallback: { agent: published, titleOnly: published !== null }
uncoveredFallback: {
agent: published,
// A bare foreground name is an uncovered compatibility answer, but it is not title proof.
// Mark title-only only when no foreground signal exists and the frozen answer matches title
// evidence exactly.
titleOnly: args.foregroundAgent == null && published !== null && published === titleAgent
}
})
const titleAgent = args.title ? collectAgentTitleEvidence(args.title).agent : null
recorder.record({
surface: args.surface,
paneId: args.paneId,