Skip to content

Scenario Cross-Reference Validation

This tutorial explains how FCC scenarios reference persona interactions, how to validate cross-reference integrity using quality gates, and how to build custom validators for persona-specific rules.

How Scenarios Reference Persona Interactions

An FCC scenario defines which personas participate, what artifacts they produce, and what validation rules apply. When a scenario specifies personas, those personas must have valid cross-reference entries connecting them -- otherwise the scenario describes a workflow that cannot execute.

Scenario Structure Recap

# data/scenarios/example_scenario.yaml
scenario:
  id: SC-001
  name: Platform Documentation
  setup:
    personas:
      - Research Crafter
      - Blueprint Crafter
      - Documentation Evangelist
    expected_artifacts:
      - research_inventory
      - blueprint_package
      - documentation_set
    champion_id: RCHM
    orchestrated_personas:
      - RC
      - CIA
      - STE
      - RIC
    max_steps: 100
  validation_rules:
    - rule: full_fcc_cycle
    - rule: artifact_completeness
    - rule: champion_orchestration
    - rule: cross_category_coverage
      min_categories: 3
    - rule: governance_gate_passed

The personas and champion_id fields create an implicit dependency on the cross-reference matrix: every persona pair in the scenario should have a path through the matrix.

Writing Validation Rules That Check Cross-Reference Integrity

Verifying Scenario Personas Exist in the Matrix

from fcc.personas.cross_reference import CrossReferenceMatrix
from fcc.personas.registry import PersonaRegistry

matrix = CrossReferenceMatrix.from_yaml("data/personas/cross_reference.yaml")
registry = PersonaRegistry.from_yaml_directory("data/personas/")

# Scenario personas (by name) -> resolve to IDs
scenario_persona_names = ["Research Crafter", "Blueprint Crafter", "Documentation Evangelist"]
scenario_ids = {registry.get_by_name(name).id for name in scenario_persona_names}

# Check all scenario persona IDs exist in the matrix
matrix_ids = matrix.all_persona_ids()
missing = scenario_ids - matrix_ids
if missing:
    print(f"ERROR: Scenario personas not in cross-reference matrix: {missing}")
else:
    print(f"All {len(scenario_ids)} scenario personas found in matrix")

Verifying Handoff Paths Exist

def verify_handoff_chain(matrix, persona_ids):
    """Verify that every consecutive pair of personas has a handoff path."""
    id_list = sorted(persona_ids)  # Sort for deterministic output
    issues = []
    for pid in id_list:
        downstream = matrix.downstream(pid)
        downstream_targets = {e.target_id for e in downstream if e.relationship_type == "handoff"}
        reachable = downstream_targets & persona_ids
        if not reachable and pid not in terminal_ids(matrix, persona_ids):
            issues.append(f"{pid} has no handoff to any other scenario persona")
    return issues


def terminal_ids(matrix, persona_ids):
    """Personas with no downstream handoffs within the set (terminal nodes)."""
    terminals = set()
    for pid in persona_ids:
        downstream = matrix.downstream(pid)
        handoff_targets = {e.target_id for e in downstream if e.relationship_type == "handoff"}
        if not (handoff_targets & persona_ids):
            terminals.add(pid)
    return terminals


issues = verify_handoff_chain(matrix, {"RC", "BC", "DE"})
if issues:
    for issue in issues:
        print(f"  WARNING: {issue}")
else:
    print("All handoff chains valid")

Using Quality Gates QG-XREF-001/002/003

The three cross-reference quality gates validate matrix integrity at the framework level, not at the scenario level. However, you can incorporate them into scenario validation as a pre-flight check.

QG-XREF-001: Cross-Reference Coverage

Ensures all 24 personas appear in the matrix.

def check_qg_xref_001(matrix):
    """All 24 personas must be represented in the matrix."""
    expected = {
        "RC", "BC", "DE", "RB", "UG",
        "CIA", "UMC", "STE", "TS", "BV", "RIC", "GCA",
        "DGS", "PTE", "AMS",
        "CO", "SMC", "EC", "RS", "SCP",
        "RCHM", "BCHM", "UGCH", "RBCH",
    }
    actual = matrix.all_persona_ids()
    missing = expected - actual

    if missing:
        return False, f"Missing personas: {sorted(missing)}"

    # Every persona has downstream or is terminal
    for pid in expected:
        has_downstream = len(matrix.downstream(pid)) > 0
        has_upstream = len(matrix.upstream(pid)) > 0
        if not has_downstream and not has_upstream:
            return False, f"Persona {pid} has no upstream or downstream entries"

    return True, f"All {len(expected)} personas covered"

passed, msg = check_qg_xref_001(matrix)
print(f"QG-XREF-001: {'PASS' if passed else 'FAIL'} - {msg}")

QG-XREF-002: Cross-Reference Type Completeness

Ensures all 5 relationship types are present.

def check_qg_xref_002(matrix):
    """All 5 relationship types must be used."""
    required = {"handoff", "feedback", "coordination", "governance", "champion-of"}
    found = {e.relationship_type for e in matrix}
    missing = required - found

    if missing:
        return False, f"Missing types: {sorted(missing)}"
    return True, "All 5 relationship types present"

passed, msg = check_qg_xref_002(matrix)
print(f"QG-XREF-002: {'PASS' if passed else 'FAIL'} - {msg}")

QG-XREF-003: Workflow-CrossReference Alignment

Ensures matrix entries align with workflow graph edges and there are no orphan IDs.

from fcc.workflow.graph import WorkflowGraph

def check_qg_xref_003(matrix, registry, graph):
    """Verify matrix aligns with workflow graph."""
    matrix_ids = matrix.all_persona_ids()
    registry_ids = set(registry.ids)

    # No orphan persona IDs
    orphans = matrix_ids - registry_ids
    if orphans:
        return False, f"Orphan IDs not in registry: {sorted(orphans)}"

    # Check that workflow edges have corresponding matrix entries
    missing_edges = []
    for edge in graph.edges:
        entries = matrix.between(edge.from_id, edge.to_id)
        if not entries:
            missing_edges.append(f"{edge.from_id} -> {edge.to_id} ({edge.label})")

    if missing_edges:
        return False, f"Workflow edges missing matrix entries: {missing_edges[:5]}..."

    return True, "Workflow and matrix are aligned"

graph = WorkflowGraph.from_json("data/workflows/complete_24.json")
passed, msg = check_qg_xref_003(matrix, registry, graph)
print(f"QG-XREF-003: {'PASS' if passed else 'FAIL'} - {msg}")

Automated Cross-Reference Validation in CI

Combine all three quality gates into a CI-compatible validation script:

#!/usr/bin/env python3
"""CI validation: cross-reference quality gates for scenario integrity."""

import sys
from pathlib import Path

from fcc.personas.cross_reference import CrossReferenceMatrix
from fcc.personas.registry import PersonaRegistry


def validate_scenario_xref(scenario_personas, matrix, registry):
    """Validate a scenario's persona list against the cross-reference matrix."""
    errors = []

    # Resolve persona names to IDs
    scenario_ids = set()
    for name in scenario_personas:
        try:
            persona = registry.get_by_name(name)
            scenario_ids.add(persona.id)
        except KeyError:
            errors.append(f"Unknown persona name: {name}")

    # Check all IDs exist in matrix
    matrix_ids = matrix.all_persona_ids()
    missing = scenario_ids - matrix_ids
    if missing:
        errors.append(f"Scenario personas not in matrix: {sorted(missing)}")

    # Check connectivity: every non-terminal persona should have a downstream handoff
    # to at least one other scenario persona
    for pid in scenario_ids:
        downstream = matrix.downstream(pid)
        handoff_to_scenario = [
            e for e in downstream
            if e.target_id in scenario_ids and e.relationship_type == "handoff"
        ]
        feedback_from_scenario = [
            e for e in matrix.upstream(pid)
            if e.source_id in scenario_ids
        ]
        if not handoff_to_scenario and not feedback_from_scenario:
            errors.append(f"Persona {pid} is isolated from other scenario personas")

    return errors


def main():
    data_dir = Path("data/personas/")
    matrix = CrossReferenceMatrix.from_yaml(data_dir / "cross_reference.yaml")
    registry = PersonaRegistry.from_yaml_directory(data_dir)

    # Example: validate a scenario
    scenario_personas = [
        "Research Crafter", "Blueprint Crafter", "Documentation Evangelist",
        "Runbook Crafter", "User Guide Crafter",
    ]
    errors = validate_scenario_xref(scenario_personas, matrix, registry)

    if errors:
        for e in errors:
            print(f"ERROR: {e}", file=sys.stderr)
        sys.exit(1)
    else:
        print("Scenario cross-reference validation passed")
        sys.exit(0)


if __name__ == "__main__":
    main()

Custom Validators for Persona-Specific Cross-Reference Rules

The FCCValidator class supports custom rules. You can extend it to add cross-reference-specific validations.

Using the Built-In Validator

from fcc.scenarios.validators import FCCValidator

validator = FCCValidator.from_registry(registry)

# Validate champion orchestration
result = validator.validate_champion_orchestration(
    champion_id="RCHM",
    orchestrated_personas=["RC", "CIA", "STE", "RIC"],
    personas_executed=["RC", "CIA", "STE", "RIC"],
)
print(f"{result.rule}: {'PASS' if result.passed else 'FAIL'} - {result.message}")

Validate Cross-Category Coverage

# Ensure at least 3 categories are represented
result = validator.validate_cross_category_coverage(
    personas_executed=["RC", "BC", "DE", "DGS", "CO"],
    min_categories=3,
)
print(f"{result.rule}: {'PASS' if result.passed else 'FAIL'} - {result.message}")

Output:

cross_category_coverage: PASS - Categories covered: ['core', 'governance', 'stakeholder'] (3/3 required)

Validate Governance Gate

result = validator.validate_governance_gate(
    personas_executed=["RC", "BC", "DE", "GCA", "CO"],
)
print(f"{result.rule}: {'PASS' if result.passed else 'FAIL'} - {result.message}")

Output:

governance_gate_passed: PASS - Governance personas: ['GCA']

Building a Custom Cross-Reference Validator

from fcc.scenarios.models import ValidationResult, ValidationSeverity


def validate_feedback_loops(matrix, scenario_ids):
    """Custom rule: every handoff in the scenario should have a feedback loop."""
    missing_feedback = []
    for entry in matrix:
        if (entry.source_id in scenario_ids
                and entry.target_id in scenario_ids
                and entry.relationship_type == "handoff"):
            reverse = matrix.between(entry.target_id, entry.source_id)
            has_feedback = any(e.relationship_type == "feedback" for e in reverse)
            if not has_feedback:
                missing_feedback.append(f"{entry.source_id} -> {entry.target_id}")

    passed = len(missing_feedback) == 0
    return ValidationResult(
        rule="feedback_loop_completeness",
        passed=passed,
        severity=ValidationSeverity.WARNING if not passed else ValidationSeverity.INFO,
        message=(
            "All handoffs have feedback loops" if passed
            else f"Missing feedback for: {missing_feedback}"
        ),
    )


# Run the custom validator
scenario_ids = {"RC", "BC", "DE", "RB", "UG"}
result = validate_feedback_loops(matrix, scenario_ids)
print(f"{result.rule}: {'PASS' if result.passed else 'FAIL'} - {result.message}")

Using fcc validate

The CLI command fcc validate runs schema validation on all data files, including cross_reference.yaml:

# Validate all data files including cross-references
fcc validate

# Validate a specific file against its schema
fcc validate --schema data/schemas/cross_reference.schema.json data/personas/cross_reference.yaml

The schema enforces:

  • source_id and target_id match the pattern ^[A-Z]{2,5}$
  • relationship_type is one of the five allowed values
  • interaction is a non-empty string
  • strength is either "primary" or "secondary"

Integrating Validation into Scenario Execution

from fcc.scenarios.validators import FCCValidator
from fcc.personas.cross_reference import CrossReferenceMatrix
from fcc.personas.registry import PersonaRegistry


def run_validated_scenario(scenario_config, data_dir="data/personas/"):
    """Run a scenario with cross-reference validation as a pre-flight check."""
    registry = PersonaRegistry.from_yaml_directory(data_dir)
    matrix = CrossReferenceMatrix.from_yaml(f"{data_dir}/cross_reference.yaml")
    validator = FCCValidator.from_registry(registry)

    # Pre-flight: verify cross-reference integrity
    persona_names = scenario_config["setup"]["personas"]
    persona_ids = [registry.get_by_name(n).id for n in persona_names]

    # Check all IDs in matrix
    matrix_ids = matrix.all_persona_ids()
    missing = set(persona_ids) - matrix_ids
    if missing:
        raise ValueError(f"Pre-flight failed: personas not in matrix: {missing}")

    # Check champion orchestration if applicable
    champion_id = scenario_config["setup"].get("champion_id")
    if champion_id:
        orchestrated = scenario_config["setup"].get("orchestrated_personas", [])
        result = validator.validate_champion_orchestration(
            champion_id, orchestrated, persona_ids,
        )
        if not result.passed:
            raise ValueError(f"Pre-flight failed: {result.message}")

    print("Pre-flight cross-reference validation passed")
    # ... proceed with simulation

Next Steps