From e35459875b787854b7633b281fe262bb838086bf Mon Sep 17 00:00:00 2001 From: aarroyo Date: Mon, 13 Jul 2026 17:53:14 -0500 Subject: [PATCH] fix(agent-runtime): route structural-review skill via canonical 'evidence' kind (GT-535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `code-quality-structural-review` skill declared `evaluationKinds: ['code-quality']`, but 'code-quality' is a quality-signal DIMENSION (the tag on the Evidence StructuralReviewProvider emits), NOT a member of the core-domain `EvaluationKind` union. `buildEvaluationContext` filters declared kinds against a KNOWN_KINDS allowlist, so 'code-quality' was silently dropped and the context fell back to `['gate']` — the skill was declared but never routed as the quality-signal evaluation it is, leaving the GT-535 provider unreachable. Fix (map onto an existing supported kind, not forge a phantom one): - default-skills.ts: `evaluationKinds: ['code-quality']` -> `['evidence']`, the canonical kind for declared quality-signal Evidence (ADR-0111 / GT-533). The 'code-quality' dimension stays where it belongs — on the emitted Evidence. - context-mapper.ts: sync the KNOWN_KINDS allowlist to the full canonical `EvaluationKind` union (add 'design', 'phase-artifacts') with a maintenance note, so the same "declared-but-dropped" class can't recur for those kinds. - structural-review.spec.ts: add a routing guard proving the skill forwards 'evidence' through buildEvaluationContext (not the 'gate' fallback), and that a dimension-shaped kind still collapses to ['gate']. Kind routing only; the concrete IStructuralReviewer LLM adapter is a separate follow-on. agent-runtime jest green (17 suites / 121 tests), tsc --noEmit clean. Co-Authored-By: Claude Opus 4.8 --- .../src/__tests__/structural-review.spec.ts | 39 +++++++++++++++++++ .../src/adapters/skills/default-skills.ts | 8 +++- .../src/application/context-mapper.ts | 9 +++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/packages/agent-runtime/src/__tests__/structural-review.spec.ts b/src/packages/agent-runtime/src/__tests__/structural-review.spec.ts index af50dbb21..4d360b10c 100644 --- a/src/packages/agent-runtime/src/__tests__/structural-review.spec.ts +++ b/src/packages/agent-runtime/src/__tests__/structural-review.spec.ts @@ -32,6 +32,10 @@ import { compareSeverity, } from '../domain/rubrics/structural-review-rubric'; import type { CollectionContext, CollectionTarget } from '../domain/ports/quality-signal-provider.port'; +import { DEFAULT_SKILLS } from '../adapters/skills/default-skills'; +import { buildEvaluationContext } from '../application/context-mapper'; +import { parseAgentRuntimeRequest } from '../domain/contracts/agent-runtime-request'; +import type { SkillDescriptor } from '../domain/contracts/capability'; /** A deterministic stub reviewer standing in for the probabilistic LLM/agent. */ function stubReviewer(findings: readonly RawStructuralFinding[]): IStructuralReviewer { @@ -208,3 +212,38 @@ describe('Structural Quality Gate (deterministic severity → decision)', () => expect(decideForSeverity('medium', DEFAULT_STRUCTURAL_GATE_POLICY)).toBe('warn'); }); }); + +/** + * Kind routing (GT-535) — the structural-review skill must forward a canonical + * EvaluationKind through buildEvaluationContext, NOT be dropped to the 'gate' + * fallback. Regression guard: 'code-quality' is a quality-signal DIMENSION, not a + * kind; the skill declares the canonical 'evidence' kind so the provider stays + * reachable when the IStructuralReviewer adapter lands. + */ +describe('structural-review skill kind routing (GT-535)', () => { + const skill = DEFAULT_SKILLS.find((s) => s.id === 'code-quality-structural-review'); + const req = parseAgentRuntimeRequest({ tenant: 't-1', intent: 'structural_review' }); + + it('declares a canonical evaluation kind (not the code-quality dimension)', () => { + expect(skill).toBeDefined(); + expect(skill?.evaluationKinds).toEqual(['evidence']); + // 'code-quality' is the Evidence DIMENSION, never an EvaluationKind. + expect(skill?.evaluationKinds).not.toContain('code-quality'); + }); + + it('forwards the declared kind through buildEvaluationContext (not the gate fallback)', () => { + const ctx = buildEvaluationContext(req, skill as SkillDescriptor); + expect(ctx.kinds).toEqual(['evidence']); + // Guard the regression: an unknown/dimension-shaped kind would collapse to ['gate']. + expect(ctx.kinds).not.toEqual(['gate']); + }); + + it('drops a non-canonical kind to the gate fallback (documents the filter)', () => { + const bogus: SkillDescriptor = { + ...(skill as SkillDescriptor), + evaluationKinds: ['code-quality'], + }; + const ctx = buildEvaluationContext(req, bogus); + expect(ctx.kinds).toEqual(['gate']); + }); +}); diff --git a/src/packages/agent-runtime/src/adapters/skills/default-skills.ts b/src/packages/agent-runtime/src/adapters/skills/default-skills.ts index 8f96bbc14..40c192f78 100644 --- a/src/packages/agent-runtime/src/adapters/skills/default-skills.ts +++ b/src/packages/agent-runtime/src/adapters/skills/default-skills.ts @@ -93,8 +93,14 @@ export const DEFAULT_SKILLS: readonly SkillDescriptor[] = [ intents: ['structural_review', 'code_quality_review', 'review_structure'], // Orchestration runs the probabilistic reviewer behind IStructuralReviewer and // hands the normalized Evidence to the Core as a code-quality signal (GT-535). + // NOTE: 'code-quality' is the quality-signal DIMENSION the emitted Evidence is + // tagged with (StructuralReviewProvider.DEFAULT_DIMENSION), NOT an EvaluationKind. + // The canonical EvaluationKind for declared quality-signal Evidence is 'evidence' + // (ADR-0111 / GT-533). Declaring it here keeps the kind routed by + // buildEvaluationContext instead of being dropped to the 'gate' fallback, so the + // StructuralReviewProvider stays reachable when the IStructuralReviewer adapter lands. kind: 'evaluation', - evaluationKinds: ['code-quality'], + evaluationKinds: ['evidence'], permissions: ['read:repo'], requiresApproval: false, emitsTrace: true, diff --git a/src/packages/agent-runtime/src/application/context-mapper.ts b/src/packages/agent-runtime/src/application/context-mapper.ts index e0da69dd5..b8b55718a 100644 --- a/src/packages/agent-runtime/src/application/context-mapper.ts +++ b/src/packages/agent-runtime/src/application/context-mapper.ts @@ -35,9 +35,18 @@ export function toPhaseId(phase?: string): PhaseIdT | undefined { return (CANONICAL_PHASES as readonly string[]).includes(v) ? (v as PhaseIdT) : undefined; } +/** + * Allowlist of canonical evaluation kinds a skill may forward to the Core. It MUST + * mirror the core-domain `EvaluationKind` union (evaluation-context.ts) — a skill + * that declares a kind absent from this list is silently dropped to the 'gate' + * fallback (that is the bug GT-535 hit: 'code-quality' is a quality-signal + * DIMENSION, not a kind). Kept as a literal (not imported) because this file is a + * type-only consumer of core-domain — the hexagon carries no runtime dependency. + */ const KNOWN_KINDS: readonly string[] = [ 'gate', 'artifact', 'evidence', 'architecture', 'blueprint', 'topology', 'checkpoint', 'deployment', 'rule', 'compliance', + 'design', 'phase-artifacts', ]; /** Build a canonical EvaluationContext from a runtime request + resolved skill. */