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:
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_idandtarget_idmatch the pattern^[A-Z]{2,5}$relationship_typeis one of the five allowed valuesinteractionis a non-empty stringstrengthis 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¶
- Interaction Cookbook -- Recipes for adding and validating entries
- Quality Gates Specification -- Full gate definitions
- Cross-Reference Matrix Specification -- Data model reference
- Contributing Cross-References -- Adding entries to the YAML file