fix(views): stop showing backfilled attribution as a warning (MUL-4768)

The transcript/activity AttributionBadge colored the "on behalf of <name>"
chip yellow (text-warning) whenever attribution.precise === false. That flag is
the backend's attribution-*coverage* health bit — owner_fallback, backfill, and
unattributed all fail it — but coverage is an ops metric, not a reader-facing
signal. A backfilled attribution names a human the same waterfall resolved, just
retroactively, so it is confident; rendering it identically to a genuine
owner_fallback guess made a correct "on behalf of Bohan" read like an error.

Fire the cautionary tone only for a fallback guess (any non-precise source
except backfill). Keeping the precise === false base means a future unknown
degraded source still warns (fail-safe). The backfill nuance stays in the
tooltip.

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
J
2026-07-15 03:42:53 +08:00
parent 09f69dfa05
commit a6c5042dfd
2 changed files with 59 additions and 11 deletions

View File

@@ -35,7 +35,7 @@ describe("AttributionBadge", () => {
expect(screen.getByTitle("Direct member action")).toBeInTheDocument();
});
it("marks degraded (non-precise) attribution with a warning tone", () => {
it("marks a fallback guess (owner_fallback) with a warning tone", () => {
const attribution: TaskAttribution = {
source: "owner_fallback",
precise: false,
@@ -51,6 +51,40 @@ describe("AttributionBadge", () => {
.toBeInTheDocument();
});
it("shows a backfilled attribution in the normal tone, not a warning (MUL-4768)", () => {
// Backfill is non-precise for the coverage metric, but it names a human the
// same waterfall resolved — just after the fact — so it must read like any
// other confident attribution rather than a yellow warning.
const attribution: TaskAttribution = {
source: "backfill",
precise: false,
initiator: { id: "u5", name: "Bohan" },
};
const { container } = renderWithI18n(
<AttributionBadge attribution={attribution} />,
);
expect(screen.getByText("on behalf of Bohan")).toBeInTheDocument();
// No warning tone — the confusing yellow that flagged backfilled runs is gone.
expect(container.querySelector(".text-warning")).toBeNull();
expect(container.querySelector(".text-muted-foreground")).not.toBeNull();
// The "backfilled" nuance still lives in the tooltip for anyone who looks.
expect(screen.getByTitle("Backfilled attribution")).toBeInTheDocument();
});
it("avatar variant shows no warning ring for a backfilled attribution (MUL-4768)", () => {
const attribution: TaskAttribution = {
source: "backfill",
precise: false,
initiator: { id: "u5", name: "Bohan" },
};
const { container } = renderWithI18n(
<AttributionBadge attribution={attribution} variant="avatar" />,
);
expect(container.querySelector(".ring-warning\\/60")).toBeNull();
});
it("falls back to a generic name when the initiator has no display name", () => {
const attribution: TaskAttribution = {
source: "delegation",

View File

@@ -23,8 +23,11 @@ function initialsOf(name: string): string {
/**
* AttributionBadge renders who an agent run is accountable to (MUL-4302 §9):
* the "on behalf of <member>" provenance, with the resolution source and a
* distinct warning tone for degraded (non-precise) attribution.
* the "on behalf of <member>" provenance, with the resolution source in a
* tooltip and a cautionary tone ONLY when the named human is a fallback guess
* (owner_fallback) rather than a confidently resolved one. A backfilled
* attribution is confident (resolved after the fact by the same waterfall), so
* it reads like any other attribution instead of a warning (MUL-4768).
*
* Two shapes, both silent when no responsible member resolved (MUL-4765):
* - `variant="badge"` (default): the full "on behalf of <name>" chip. Renders
@@ -82,9 +85,20 @@ export function AttributionBadge({
sourceLabel = attribution.source;
}
// Degraded attribution (owner_fallback / backfill / unattributed) is marked
// distinctly so it never reads as a compliance-grade "who is responsible".
const degraded = attribution.precise === false;
// The backend's `precise` flag is an attribution-*coverage* health bit:
// owner_fallback, backfill, and unattributed all fail it. But coverage is an
// ops metric, not a reader-facing signal. The only thing a viewer of "on
// behalf of <name>" cares about is whether that named human might NOT actually
// be who's responsible — true only for a fallback guess (owner_fallback:
// nothing resolved, so we defaulted to the agent owner). A backfilled
// attribution names a human the same waterfall resolved, just after the fact,
// so it is confident and must read like any other attribution, never as a
// warning (MUL-4768). The cautionary tone therefore fires for any non-precise
// source EXCEPT backfill; keeping the `precise === false` base means a future
// unknown degraded source still warns (fail-safe) instead of silently reading
// as confident.
const uncertain =
attribution.precise === false && attribution.source !== "backfill";
const initiator = attribution.initiator;
// Avatar-only shape: just the accountable member's face, with the name +
@@ -99,9 +113,9 @@ export function AttributionBadge({
<span
className={cn(
"inline-flex shrink-0",
// A subtle ring flags degraded attribution so an owner-fallback
// face never silently reads as a precise responsible member.
degraded && "rounded-full ring-1 ring-warning/60",
// A subtle ring flags a fallback guess so an owner-fallback face
// never silently reads as a confidently resolved responsible member.
uncertain && "rounded-full ring-1 ring-warning/60",
className
)}
>
@@ -122,7 +136,7 @@ export function AttributionBadge({
<span
className={cn(
"text-[11px]",
degraded ? "text-warning" : "text-muted-foreground"
uncertain ? "text-warning" : "text-muted-foreground"
)}
>
{sourceLabel}
@@ -145,7 +159,7 @@ export function AttributionBadge({
variant="outline"
className={cn(
"max-w-40 min-w-0 gap-1 font-normal",
degraded ? "text-warning" : "text-muted-foreground",
uncertain ? "text-warning" : "text-muted-foreground",
className
)}
title={sourceLabel}