Skip to content
Arxo Arxo

Policy Examples

This guide provides real-world policy examples for different project types and architectural styles.

Policies in Arxo are defined in YAML and consist of invariants - conditions that must always be true.

policy:
invariants:
- metric: <metric_id>
op: <operator>
value: <threshold>
message: <optional_message>

Operators: ==, !=, <, <=, >, >=


Use Case: New projects, microservices, libraries

Enforce a completely acyclic architecture:

policy:
invariants:
# No circular dependencies at all
- metric: scc.max_cycle_size
op: "=="
value: 0
message: "Circular dependencies are not allowed"
# All nodes should be in their own SCC
- metric: scc.total_nodes_in_cycles
op: "=="
value: 0
message: "No modules should participate in cycles"

Use Case: Service-oriented architecture, domain boundaries

Ensure low coupling between services:

data:
import_graph:
group_by: folder
group_depth: 2 # Group by service folders
policy:
invariants:
# Low system-wide coupling
- metric: propagation_cost.system.ratio
op: "<="
value: 0.10
message: "Services should be loosely coupled"
# No circular dependencies between services
- metric: scc.max_cycle_size
op: "=="
value: 0
message: "Services must not have circular dependencies"
# Prevent god services
- metric: centrality.module.max_fan_out
op: "<="
value: 8
message: "No service should depend on more than 8 others"
# Good modular structure
- metric: modularity.module.best_q
op: ">="
value: 0.40
message: "Services should form clear modules"

Use Case: Frontend applications with FSD architecture

Enforce layer hierarchy:

data:
import_graph:
group_by: folder
group_depth: 2 # app/pages, app/features, shared/ui, etc.
policy:
invariants:
# Strict hierarchy (low weighted upward flow)
- metric: hierarchy.module.edge.upward_weight_ratio
op: "<="
value: 0.05
message: "Layers should not import from upper layers"
# Low hierarchy erosion
- metric: hierarchy.module.agony.weighted_depth_normalized
op: "<="
value: 0.08
message: "Minimize inferred hierarchy erosion"
# No cycles between features
- metric: scc.max_cycle_size
op: "=="
value: 0
message: "Features must not have circular dependencies"
# Good modular separation
- metric: modularity.module.best_q
op: ">="
value: 0.35
message: "Features should be well-separated"

Use Case: Nx, Turborepo, Lerna monorepos

Enforce package boundaries:

data:
import_graph:
group_by: folder
group_depth: 2
exclude:
- "**/node_modules/**"
- "**/dist/**"
policy:
invariants:
# Apps should not depend on each other
- metric: smells.cycles.count
op: "=="
value: 0
message: "Apps should not depend on each other"
# Reasonable coupling
- metric: propagation_cost.system.ratio
op: "<="
value: 0.15
message: "Keep inter-package coupling low"
# Shared libraries should be stable
- metric: package_metrics.module.instability.max
op: "<="
value: 0.5
message: "Shared packages should be stable"

Use Case: Existing projects with technical debt

Set achievable goals while preventing regression:

policy:
invariants:
# Allow small cycles, but limit size
- metric: scc.max_cycle_size
op: "<="
value: 5
message: "Keep cycles small (max 5 modules)"
# Limit nodes in cycles
- metric: scc.total_nodes_in_cycles
op: "<="
value: 30
message: "No more than 30 modules in cycles"
# Prevent new god components
- metric: smells.god_components.count
op: "<="
value: 3
message: "No more than 3 god components allowed"
# System coupling should not increase
- metric: propagation_cost.system.ratio
op: "<="
value: 0.30
message: "Do not increase system coupling"

Use Case: REST/GraphQL APIs, backend services

Focus on layering and boundaries:

data:
import_graph:
group_by: folder
group_depth: 1 # controllers, services, repositories, models
policy:
invariants:
# Enforce layered architecture (weighted by dependency importance)
- metric: hierarchy.module.edge.upward_weight_ratio
op: "<="
value: 0.12
message: "Respect layer boundaries (controllers → services → repositories)"
# No cross-layer violations
- metric: smells.hierarchy.cyclic_count
op: "=="
value: 0
message: "Controllers should not import from repositories directly"
# Keep services independent
- metric: scc.max_cycle_size
op: "=="
value: 0
message: "No circular dependencies between services"

Use Case: Mobile applications with screens/features

data:
import_graph:
group_by: folder
group_depth: 2
exclude:
- "**/node_modules/**"
- "**/__tests__/**"
- "**/*.test.tsx"
policy:
invariants:
# Screens should not depend on each other
- metric: smells.cycles.severe_count
op: "=="
value: 0
message: "Screens must not have circular dependencies"
# Reasonable coupling
- metric: propagation_cost.system.ratio
op: "<="
value: 0.20
message: "Keep coupling manageable for mobile"
# No navigation bloat
- metric: centrality.module.max_fan_out
op: "<="
value: 10
message: "No screen should navigate to more than 10 others"

Use Case: Reusable Python packages

data:
import_graph:
group_by: folder
group_depth: 2
exclude:
- "**/tests/**"
- "**/__pycache__/**"
policy:
invariants:
# Library should be acyclic
- metric: scc.max_cycle_size
op: "=="
value: 0
message: "No circular imports in library code"
# Public API should be stable
- metric: package_metrics.module.instability.mean
op: "<="
value: 0.3
message: "Keep public API stable"
# Prevent tight coupling
- metric: propagation_cost.system.ratio
op: "<="
value: 0.15
message: "Modules should be loosely coupled"

Use Case: Applications with LLM integrations

metrics:
- id: llm_integration
- id: rag_architecture
- id: finetuning_architecture
- id: agent_architecture
policy:
invariants:
# Observability is critical
- metric: llm.observability_gap
op: "<="
value: 0.20
message: "At least 80% of LLM calls must have tracing"
# No PII leakage
- metric: llm.pii_leakage_risk
op: "=="
value: 0
message: "No PII should flow to LLM prompts without redaction"
# Cost tracking
- metric: llm.cost_tracking_gap
op: "<="
value: 0.30
message: "Track token usage for cost control"
# Require evaluation harness
- metric: llm.eval_harness_present
op: "=="
value: 1.0
message: "Evaluation tests are required"
# Fine-tuning reproducibility and eval quality gate
- metric: finetuning_architecture.overall_finetuning_health
op: ">="
value: 0.70
message: "Fine-tuning architecture baseline not met"

Use Case: Teams that want a dedicated CI gate for agent reliability and governance

metrics:
- id: agent_architecture
policy:
invariants:
- metric: agent_architecture.loop_guard_absence
op: "<="
value: 0.20
message: "Agent loops must be bounded with step/time guards"
- metric: agent_architecture.governance_readiness
op: ">="
value: 80
message: "Tool governance baseline not met"
- metric: agent_architecture.overall_agent_health
op: ">="
value: 0.70
message: "Overall agent health below minimum threshold"

See Agent Architecture policy and CI gates for strict/pragmatic/no-regression profiles.


Use Case: Financial, healthcare, compliance-heavy apps

metrics:
- id: scc
- id: security
- id: sensitive_data_flow
policy:
invariants:
# Zero cycles for predictability
- metric: scc.max_cycle_size
op: "=="
value: 0
message: "Circular dependencies not allowed"
# No sensitive data leaks
- metric: security.sensitive_data_flow_violations
op: "=="
value: 0
message: "Sensitive data must not flow to unsafe sinks"
# Low coupling for isolation
- metric: propagation_cost.system.ratio
op: "<="
value: 0.10
message: "Security-critical modules must be isolated"

Start strict, allow exceptions temporarily:

policy:
invariants:
# Ideal: zero cycles
- metric: scc.max_cycle_size
op: "=="
value: 0
message: "No circular dependencies (eventually)"
# Current: allow up to 3 small cycles
- metric: scc.component_count
op: ">="
value: 147 # Total nodes minus allowed cycles
message: "Do not introduce new cycles"
# Track progress
- metric: scc.total_nodes_in_cycles
op: "<="
value: 12
message: "Reduce cycles over time"

Strategy: Tighten thresholds gradually in each sprint.


Combine metrics for comprehensive policies:

policy:
invariants:
# Structural
- metric: scc.max_cycle_size
op: "=="
value: 0
# Coupling
- metric: propagation_cost.system.ratio
op: "<="
value: 0.15
# Modularity
- metric: modularity.module.best_q
op: ">="
value: 0.35
# Smells
- metric: smells.god_components.count
op: "=="
value: 0
- metric: smells.cycles.severe_count
op: "=="
value: 0
# Centrality
- metric: centrality.module.max_fan_out
op: "<="
value: 10

Prevent regression without fixing everything:

Terminal window
# Generate baseline from main branch
git checkout main
arxo analyze --format json --output baseline.json
git checkout feature-branch
# Compare against baseline
arxo analyze --baseline baseline.json

Policy: Metrics should not get worse than baseline.


Provide actionable guidance:

policy:
invariants:
- metric: scc.max_cycle_size
op: "=="
value: 0
message: |
Circular dependency detected!
To fix:
1. Run: arxo analyze --format json | jq '.results[] | select(.id=="scc")'
2. Identify the cycle nodes
3. Extract shared code to a new module
4. Use dependency injection
See: https://docs.arxo.io/guides/breaking-cycles

Use different configs for different stages:

.arxo.dev.yaml (Development):

policy:
invariants:
- metric: scc.max_cycle_size
op: "<="
value: 5 # Relaxed

.arxo.ci.yaml (CI):

policy:
invariants:
- metric: scc.max_cycle_size
op: "=="
value: 0 # Strict

Usage:

Terminal window
# Development
arxo analyze --config .arxo.dev.yaml
# CI
arxo analyze --config .arxo.ci.yaml

Use the MCP server for on-demand policy checks with your AI assistant:

You: "Check if my changes violate our architecture policies"
AI: [Uses evaluate_policy] "Found 1 violation: scc.max_cycle_size exceeded..."

The AI can evaluate policies inline or use your .arxo.yaml config automatically. See MCP Workflows for examples.