Skip to content

feat(core): optional governance metadata on EvalMetadata and EvalTest (OWASP / NIST / ATLAS / controls) #1161

@christso

Description

@christso

Objective

Add an optional, non-breaking governance block to EvalMetadata and EvalTest.metadata so authors can tag suites and individual cases with the controls and risk categories they exercise. Consumers (reports, compare, future attestation exports) can then answer which controls does this eval cover, in which version of which standard?

Motivation

Current EvalTest.metadata is Record<string, unknown> — arbitrary and untyped. Auditors, risk teams, and governance platforms (Credo AI, OneTrust, ServiceNow AI Control Tower, IBM watsonx.governance) increasingly ingest control-mapped evidence keyed to public taxonomies:

Authors can express these today via free-form metadata, but with no shared shape consumers can't aggregate portably across repos or tools. A typed block captures the convergence without prescribing workflow.

This aligns with design principles:

Proposed schema

description: Customer support agent red-team suite
metadata:
  governance:
    schema_version: "1.0"   # agentv's governance block schema; lets the block evolve

    # Typed taxonomy fields — IDE-completable, version baked into the field name.
    # When standards revise (e.g. OWASP LLM Top 10 v2027), agentv adds a new
    # field (`owasp_llm_top_10_2027`) rather than redefining IDs.
    owasp_llm_top_10_2025:     [LLM01, LLM06]
    owasp_agentic_top_10_2025: [T1]
    mitre_atlas:               [AML.T0051, AML.T0075]

    # Cross-framework controls — flat list. String format: <FRAMEWORK>-<VERSION>:<ID>.
    # Custom prefixes are explicitly supported for internal controls.
    controls:
      - NIST-AI-RMF-1.0:MEASURE-2.7
      - ISO-42001-2023:A.6.2.4
      - EU-AI-ACT-2024:Art.55
      - INTERNAL-AI-POLICY-3.2:CTRL-7   # custom; example of allowed extension

    # Risk vocabulary anchored to EU AI Act terminology.
    # Allowed values: prohibited | high | limited | minimal
    risk_tier: high

    owner: security-team

At case level (EvalTest.metadata.governance) — same shape; merges with suite-level (arrays concatenated, scalars override).

All fields optional. The block itself is optional; existing evals without it validate and run unchanged.

Taxonomy conventions (documented, enforced via soft warning only)

  1. Versioned IDs in controls strings. Format <FRAMEWORK>-<VERSION>:<ID>. Examples: NIST-AI-RMF-1.0:MEASURE-2.7, ISO-42001-2023:A.6.2.4. Version baked in so that two years from now, an auditor reading the evidence knows which standard revision was tested. Aligns with OSCAL's catalog-versioning convention (NIST's machine-readable controls language).
  2. Version-suffixed field names for the most-used taxonomies. OWASP LLM Top 10 v2025 redefined LLM06–LLM10; v1.1's LLM06 ≠ v2025's LLM06. Encoding the version in the field name (owasp_llm_top_10_2025) eliminates this ambiguity and lets agentv ship owasp_llm_top_10_2027 as an additional field when v2027 lands, without breaking old YAMLs.
  3. Custom prefixes allowed. Orgs with internal controls (COMPANY:AI-POLICY-3.2, SOX-AI:CTRL-7) follow the same <PREFIX>-<VERSION>:<ID> shape. Validation must not reject them.
  4. risk_tier enum follows EU AI Act: prohibited | high | limited | minimal. Most regulated software is now de-facto subject to EU AI Act vocabulary. NIST 800-30 (very-low | low | moderate | high | very-high) is documented as an alternative organisations can choose; agentv accepts either.
  5. schema_version lets agentv evolve the block ("1.0" initial). Pattern from SPDX, JSON-LD, OpenAPI.

Design latitude

The taxonomy conventions above are fixed. Remaining latitude is implementation-only:

  1. Where to validate values. Permissive at schema level. Soft-warning lint in validation/eval-validator.ts for obvious typos (e.g. LLM-01 vs LLM01, missing version segment in a controls string, unknown risk_tier value). No hard errors on unknown keys — orgs need to add custom taxonomies without forking agentv.
  2. Test-case override semantics. Arrays concatenate, scalars override. Authors tag a suite once and refine per case.
  3. TypeScript shape. Recommend GovernanceMetadata as a top-level exported type with all fields optional. Versioned-field naming (owaspLlmTopTen2025 in TS) follows JS conventions; YAML stays snake_case.

Acceptance signals

  • Existing evals without a governance block validate and run unchanged.
  • EvalTest.metadata.governance round-trips through the YAML loader, TS-eval loader, and JSONL parser into EvaluationResult.
  • Typed GovernanceMetadata type exported from @agentv/core, with all fields optional, including schema_version.
  • HTML report and JSONL include the governance block when present.
  • A case tagged owasp_llm_top_10_2025: [LLM01] can be identified in results via jq '.metadata.governance.owasp_llm_top_10_2025' without special flags.
  • A controls entry of the form INTERNAL-AI-POLICY-3.2:CTRL-7 validates without warnings — custom prefixes are first-class.
  • A risk_tier of high validates; risk_tier: critical produces a soft warning suggesting the EU AI Act vocabulary (prohibited | high | limited | minimal).

Non-goals

  • Defining or maintaining authoritative control lists. Taxonomies update on their own cadence — agentv does not ship hardcoded ID enums.
  • Enforcement / CI gating. This is metadata for reporting; gating is a separate feature (composable via existing threshold flags or a wrapper).
  • Cross-case aggregation / coverage reporting — separate issue.
  • Attestation / signed export — separate issue.
  • UI or dashboards.
  • No new CLI flags or subcommands. This is a schema change; do not add --governance-* flags or new commands.
  • No new config sections in .agentv/workspace.yaml or .agentv/targets.yaml. Target-level governance fields are a separate concern.
  • No new dependencies. The change is type definitions plus an optional warning in an existing validator.
  • No changes to existing command behaviour. agentv eval, agentv compare, agentv results continue to work identically; they merely propagate the new metadata.

Surface map

  • packages/core/src/evaluation/metadata.ts — add governance?: GovernanceMetadata on EvalMetadata.
  • packages/core/src/evaluation/types.ts — add GovernanceMetadata type; reference from EvalTest.metadata via a helper (the Record<string, unknown> stays for forward-compat).
  • packages/core/src/evaluation/validation/eval-validator.ts — soft-warning lint for typos, malformed controls strings, and non-EU-AI-Act risk_tier values.
  • packages/core/src/index.ts — export GovernanceMetadata.
  • Result propagation follows existing metadata paths (already verified through agent-skills-parser.ts, jsonl-parser.ts, ts-eval-loader.ts).

Related

  • Existing metadata.tags: string[] in EvalMetadata — typed governance complements; does not replace.
  • Existing category field on EvalTest — typed governance is a different axis (categories group cases by domain; governance tags them against external standards).

Manual test plan (green-path e2e)

Assumes a local checkout with the change applied and agentv built (bun install && bun run build).

  1. Non-breaking baseline. Pick any existing example without a governance block:

    cd examples/features/functional-grading
    agentv eval

    Green: exits 0; report renders; no warnings about unknown metadata.

  2. Suite-level governance block round-trips. Edit one eval to add:

    metadata:
      governance:
        schema_version: "1.0"
        owasp_llm_top_10_2025: [LLM01]
        controls:
          - NIST-AI-RMF-1.0:MEASURE-2.7
          - INTERNAL-POLICY-1.0:CTRL-1
        risk_tier: high
        owner: platform-team

    Re-run agentv eval.
    Green: exits 0.

    jq '.metadata.governance' .agentv/results/runs/*/index.jsonl | head

    Green: block appears verbatim in JSONL — owasp_llm_top_10_2025: ["LLM01"], risk_tier: "high", schema_version: "1.0".

  3. Case-level overrides merge with suite-level. Add a single test with its own metadata.governance.owasp_llm_top_10_2025: [LLM06].

    jq '.tests[] | select(.id=="<your-id>") | .metadata.governance' .agentv/results/runs/*/index.jsonl

    Green: merged view shows suite controls + owasp_llm_top_10_2025: ["LLM01", "LLM06"] (arrays concatenate, scalars override).

  4. HTML report surfaces governance. Open .agentv/results/runs/*/report.html.
    Green: governance metadata visible in the row detail (column, badge, or expandable block — acceptable as long as it's present and labelled).

  5. SDK type export. In a fresh TypeScript scratch file:

    import type { GovernanceMetadata } from '@agentv/core';
    const g: GovernanceMetadata = {
      schema_version: '1.0',
      controls: ['ISO-42001-2023:A.6.2.4'],
      owasp_llm_top_10_2025: ['LLM01'],
      risk_tier: 'high',
    };
    console.log(g);

    Green: compiles with tsc --noEmit against the built @agentv/core.

  6. Validator warns on typos and malformed strings, does not block. Introduce each of:

    • owasp_lm_top_10_2025: [LLM01] (typo on field)
    • controls: ['NIST-AI-RMF:MEASURE-2.7'] (missing version segment)
    • risk_tier: critical (not in EU AI Act vocabulary)

    Run agentv eval.
    Green: exit 0 (not a hard error). Stderr includes warnings: unknown field, malformed control string (suggesting <FRAMEWORK>-<VERSION>:<ID>), risk_tier outside EU AI Act vocabulary (suggesting prohibited | high | limited | minimal).

  7. Custom prefixes are first-class, no warning. Use controls: ['INTERNAL-AI-POLICY-3.2:CTRL-7'].
    Green: validates with no warnings.

  8. Non-breaking: existing JSONL without governance still loads. Re-run agentv compare over a pair of runs where one has governance metadata and one does not.
    Green: comparison completes; no schema errors; cases without governance simply have no tag info.

Fail conditions that must NOT appear:

  • Existing evals error out or emit new warnings (regression).
  • governance block dropped / renamed silently in JSONL.
  • Report HTML breaks when the block is absent.
  • Custom prefix in controls triggers a warning or error.
  • A control string with version (NIST-AI-RMF-1.0:MEASURE-2.7) is rejected for being too long.

Code review quality gate

Reviewers should treat this as a strictly additive schema change. The PR is the smallest possible version, not a starting point.

Request changes if the PR includes any of:

  • A new CLI flag, subcommand, or argument anywhere.
  • A new field in workspace.yaml or targets.yaml.
  • A new runtime dependency (packages/core/package.json).
  • A hardcoded list of OWASP IDs, NIST controls, ATLAS techniques. Validation must remain permissive.
  • A hardcoded enum for risk_tier that rejects values outside prohibited | high | limited | minimal — they must produce a warning, not an error.
  • Rejection of custom-prefix controls strings (e.g. INTERNAL-X:Y). Warnings only on malformed shape (no <FRAMEWORK>-<VERSION>:<ID> separator), never on unknown framework prefixes.
  • More than a soft warning on typos. No hard errors on unknown governance keys.
  • Any change to agentv eval / agentv compare / agentv results behaviour beyond passing the metadata through.
  • Any modification of existing tests beyond pure additions (snapshot churn is fine; rewriting existing assertions is not).
  • Files changed outside this set (additions allowed; modifications outside should be flagged):
    • packages/core/src/evaluation/metadata.ts
    • packages/core/src/evaluation/types.ts
    • packages/core/src/evaluation/validation/eval-validator.ts
    • packages/core/src/index.ts (barrel export only)
    • test files alongside the above
  • Diff size > ~250 lines of source (excluding tests + types). If larger, ask: "what's the smallest version that satisfies the acceptance signals?"

The bias on review: every comment should reduce surface area. If something might be useful later, it belongs in a separate issue, not this PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    coreAnything pertaining to core functionality of AgentVenhancementNew feature or requestgovernanceAI governance: control tagging, red-team content, register conventions, attestation, model cards

    Type

    No type

    Projects

    Status

    In progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions