From f633493bd69be502e93e63d388571e0810677639 Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Thu, 23 Jul 2026 11:09:58 +0200 Subject: [PATCH] fix(task-execution): resolve bias badge from catalog --- .../nodes/task-step-node/task-step-node.html | 4 +- .../task-step-node/task-step-node.spec.ts | 39 +++++++++++++++++++ .../nodes/task-step-node/task-step-node.ts | 15 +++++-- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/app/shared/nodes/task-step-node/task-step-node.html b/src/app/shared/nodes/task-step-node/task-step-node.html index e74ec13..97b2553 100644 --- a/src/app/shared/nodes/task-step-node/task-step-node.html +++ b/src/app/shared/nodes/task-step-node/task-step-node.html @@ -87,8 +87,8 @@ {{ activeBiasAnnotationCount() }}/{{ allBiasAnnotations().length }} } - @if (biasCapabilities?.supported) { - + @if (isBiasCapable()) { + Bias capable } diff --git a/src/app/shared/nodes/task-step-node/task-step-node.spec.ts b/src/app/shared/nodes/task-step-node/task-step-node.spec.ts index 817b084..822fd49 100644 --- a/src/app/shared/nodes/task-step-node/task-step-node.spec.ts +++ b/src/app/shared/nodes/task-step-node/task-step-node.spec.ts @@ -120,10 +120,49 @@ describe('TaskStepNodeComponent bias canvas highlighting', () => { expect(component.allBiasAnnotations()).toHaveLength(2); expect(component.activeBiasAnnotationCount()).toBe(1); expect(component.capabilitiesTooltip()).toContain('Bias annotations: allowed'); + expect(component.isBiasCapable()).toBe(true); + }); + + it('does not mark EndBlock as bias capable when the catalog forbids bias annotations', () => { + component.data.data.typeName = 'EndBlock'; + component.data.data.capabilities = undefined; + (component as any).blockDescriptor = { + capabilities: { + visualRole: 'END', + terminal: true, + biasAnnotationsAllowed: false, + allowsIncomingConnections: true, + allowsOutgoingConnections: false, + canDependOnOtherNodes: true, + canHaveDependentNodes: false + } + }; + fixture.detectChanges(); + + expect(component.isBiasCapable()).toBe(false); + expect(fixture.nativeElement.querySelector('.llm-node-bias-capability')).toBeNull(); + }); + + it('waits for catalog capabilities instead of using permissive defaults for the badge', () => { + component.data.data.capabilities = undefined; + (component as any).blockDescriptor = null; + fixture.detectChanges(); + + expect(component.isBiasCapable()).toBe(false); + expect(fixture.nativeElement.querySelector('.llm-node-bias-capability')).toBeNull(); }); describe('Measure bias impact availability', () => { beforeEach(() => { + component.data.data.capabilities = { + visualRole: 'PROCESS', + terminal: false, + biasAnnotationsAllowed: true, + allowsIncomingConnections: true, + allowsOutgoingConnections: true, + canDependOnOtherNodes: true, + canHaveDependentNodes: true + }; component.data.data.biasAnnotations = [ { id: 'annotation-1', behavioralProbe: { activationMode: 'PROMPT_DIRECTIVE', instruction: 'Nudge it' } } ]; diff --git a/src/app/shared/nodes/task-step-node/task-step-node.ts b/src/app/shared/nodes/task-step-node/task-step-node.ts index 21ef4c3..a1e01db 100644 --- a/src/app/shared/nodes/task-step-node/task-step-node.ts +++ b/src/app/shared/nodes/task-step-node/task-step-node.ts @@ -451,11 +451,14 @@ export class TaskStepNodeComponent { } typeCapabilities(): NodeTypeCapabilities { - return this.data?.data?.capabilities - ?? this.blockDescriptor?.capabilities + return this.resolvedTypeCapabilities() ?? DEFAULT_NODE_CAPABILITIES; } + isBiasCapable(): boolean { + return this.resolvedTypeCapabilities()?.biasAnnotationsAllowed === true; + } + visualRoleLabel(): string { const role = this.typeCapabilities().visualRole.toLowerCase(); return role.charAt(0).toUpperCase() + role.slice(1); @@ -474,7 +477,7 @@ export class TaskStepNodeComponent { } hasMeasurableBiasAnnotations(): boolean { - return this.typeCapabilities().biasAnnotationsAllowed + return this.isBiasCapable() && this.executableBiasAnnotations().length > 0 && this.biasCapabilities?.isolatedExperimentSupported === true; } @@ -610,6 +613,12 @@ export class TaskStepNodeComponent { return typeof typeName === 'string' && typeName.length > 0 ? typeName : null; } + private resolvedTypeCapabilities(): NodeTypeCapabilities | null { + return this.data?.data?.capabilities + ?? this.blockDescriptor?.capabilities + ?? null; + } + private actionDescriptionValue(): string { const value = this.blockConfiguration?.['actionDescription']; if (typeof value === 'string') return value;