Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

The Ops Meta-DSL -- An Ecosystem of Sub-DSLs

"Dev-side DSLs stop at dotnet test. The lifecycle continues to production -- and that entire journey is untyped."


The Gap -- Where Types Stop

The dev-side DSLs cover Requirement to Code to Test. Every artifact is declared with an attribute, validated by an analyzer, and wired by a Source Generator. The traceability chain is complete.

Then the code leaves dotnet test and enters production. What happens next?

  • A wiki page titled "Deployment Checklist -- Order Service v2.4"
  • A Slack message: "run migration 47 before deploying the new pod"
  • A bash script nobody has touched in 8 months
  • A Confluence runbook with 14 steps, 3 of which are outdated

The gap:

Requirement → Code → Test → [typed, validated, generated]
                            ↓
Deploy → Migrate → Observe → Configure → Rollback → [bash, wiki, Slack, hope]

Here is a real deployment "specification" from the wiki (the anti-pattern):

## Order Service v2.4 Deployment

1. Run DB migration script `047_add_payment_status.sql`
2. Wait for migration to complete (~5 min)
3. Deploy order-service v2.4 to staging
4. Run smoke tests (see Postman collection in Teams)
5. If smoke tests pass, deploy to production (rolling update)
6. Verify health check at /health
7. Monitor Grafana dashboard "Order Service" for 15 min
8. If errors > 5%, rollback to v2.3 (kubectl rollout undo)
9. Update Slack channel #deployments

Last updated: 2025-11-03 (by: unknown)

No types. No validation. No way to verify that step 1 still matches the actual migration. No way to know if the health check endpoint exists. No way to detect that the Grafana dashboard was renamed last week. No compile-time error when step 8 references a version that was never tagged.


Why One DSL Is Not Enough

The instinct is to build one big Ops.Lib with every attribute. That fails for the same reason a single monolithic namespace fails in DDD: the concepts are too different.

Domain Vocabulary Constraints
Deployment orchestrator, dependency, app, strategy, environment ordering, DAG validation, circular dependency detection
Migration step, schema, data, index, seed, exe-migration order + parallelism, downtime flags, validation queries
Observability health check, metric, alert, dashboard severity levels, threshold validation, runbook links
Configuration transform, secret, vault, environment matrix env completeness, secret rotation, validation regex
Resilience rollback, canary, circuit breaker, blue-green reverse ordering, metric thresholds, fallback validation

Each domain has its own:

  • Attribute vocabulary -- [MigrationStep] means nothing to [CircuitBreaker]
  • Source Generator -- migration ordering logic is unrelated to health check registration
  • Analyzer rules -- OPS001 (circular deployment dependency) has nothing in common with OPS010 (canary without metric)

Each should be independently publishable as a NuGet. A team that only needs observability should not pull in deployment or migration dependencies.


The Ops Meta-DSL Architecture

Ops (meta-DSL, composes all sub-DSLs)
├── Ops.Deployment.Lib        ← orchestration, ordering, dependencies, strategies
│   ├── Ops.Deployment.Generators
│   └── Ops.Deployment.Analyzers
├── Ops.Migration.Lib         ← DB schema/data/index/exe migrations, parallelism
│   ├── Ops.Migration.Generators
│   └── Ops.Migration.Analyzers
├── Ops.Observability.Lib     ← health checks, metrics, alerts, dashboards
│   ├── Ops.Observability.Generators
│   └── Ops.Observability.Analyzers
├── Ops.Configuration.Lib     ← env transforms, secrets, rotation, validation
│   ├── Ops.Configuration.Generators
│   └── Ops.Configuration.Analyzers
└── Ops.Resilience.Lib        ← rollback, canary, circuit breaker, blue-green
    ├── Ops.Resilience.Generators
    └── Ops.Resilience.Analyzers

The meta-DSL Ops.Lib is just a facade that re-exports all five sub-DSLs:

<!-- Ops.Lib.csproj — the meta-package -->
<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <ProjectReference Include="../Ops.Deployment.Lib/Ops.Deployment.Lib.csproj" />
    <ProjectReference Include="../Ops.Migration.Lib/Ops.Migration.Lib.csproj" />
    <ProjectReference Include="../Ops.Observability.Lib/Ops.Observability.Lib.csproj" />
    <ProjectReference Include="../Ops.Configuration.Lib/Ops.Configuration.Lib.csproj" />
    <ProjectReference Include="../Ops.Resilience.Lib/Ops.Resilience.Lib.csproj" />
  </ItemGroup>
</Project>

Teams choose their granularity:

<!-- Option A: everything -->
<PackageReference Include="Ops.Lib" Version="1.0.0" />

<!-- Option B: just what you need -->
<PackageReference Include="Ops.Observability.Lib" Version="1.0.0" />
<PackageReference Include="Ops.Resilience.Lib" Version="1.0.0" />

Sub-DSL #1: Ops.Deployment

Deployment is about orchestration: what gets deployed, in what order, with what strategy, to which environments.

Attribute Definitions

// ═══════════════════════════════════════════════════════════════
// Ops.Deployment.Lib — Deployment Orchestration DSL
// ═══════════════════════════════════════════════════════════════

/// Mark a class as a deployment orchestrator — the top-level container
/// for a versioned deployment procedure.
[AttributeUsage(AttributeTargets.Class)]
public sealed class DeploymentOrchestratorAttribute : Attribute
{
    public string Version { get; }
    public string Description { get; init; } = "";
    public DeploymentStrategy Strategy { get; init; } = DeploymentStrategy.Rolling;
    public string[] TargetEnvironments { get; init; } = ["staging", "production"];

    public DeploymentOrchestratorAttribute(string version) => Version = version;
}

public enum DeploymentStrategy
{
    Rolling,     // replace pods one by one
    BlueGreen,   // swap entire environment
    Canary,      // gradual traffic shift
    Recreate     // stop all, then start all (downtime)
}

/// Declare a dependency between deployment steps.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class DeploymentDependencyAttribute : Attribute
{
    public Type DependsOn { get; }
    public DependencyKind Kind { get; init; } = DependencyKind.MustCompleteBefore;

    public DeploymentDependencyAttribute(Type dependsOn) => DependsOn = dependsOn;
}

public enum DependencyKind
{
    MustCompleteBefore,   // hard ordering — wait for completion
    CanRunInParallel,     // no ordering constraint
    SoftDependency        // preferred ordering, not enforced
}

/// Declare an application that participates in this deployment.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class DeploymentAppAttribute : Attribute
{
    public string AppName { get; }
    public string Runtime { get; init; } = "dotnet";
    public int Replicas { get; init; } = 2;
    public string[] RequiredConfigs { get; init; } = [];

    public DeploymentAppAttribute(string appName) => AppName = appName;
}

Usage

// ── A real deployment orchestrator ───────────────────────────

[DeploymentOrchestrator("2.4",
    Description = "Order service v2.4 — adds payment status tracking",
    Strategy = DeploymentStrategy.Rolling,
    TargetEnvironments = ["staging", "production"])]
[DeploymentApp("order-service",
    Runtime = "dotnet",
    Replicas = 3,
    RequiredConfigs = ["ConnectionStrings:OrderDb", "PaymentGateway:ApiKey"])]
[DeploymentApp("order-worker",
    Runtime = "dotnet",
    Replicas = 1,
    RequiredConfigs = ["ConnectionStrings:OrderDb", "RabbitMq:ConnectionString"])]
[DeploymentDependency(typeof(PaymentServiceV12Deployment),
    Kind = DependencyKind.MustCompleteBefore)]
[DeploymentDependency(typeof(SharedInfraV8Deployment),
    Kind = DependencyKind.CanRunInParallel)]
public sealed class OrderServiceV24Deployment { }

[DeploymentOrchestrator("1.2",
    Description = "Payment service v1.2 — adds refund support",
    Strategy = DeploymentStrategy.BlueGreen)]
[DeploymentApp("payment-service", Replicas = 2)]
public sealed class PaymentServiceV12Deployment { }

[DeploymentOrchestrator("8.0",
    Description = "Shared infrastructure — message broker, cache")]
[DeploymentApp("rabbitmq", Runtime = "docker", Replicas = 3)]
[DeploymentApp("redis", Runtime = "docker", Replicas = 2)]
public sealed class SharedInfraV8Deployment { }

SG: Deployment Dependency Graph Validation

The Source Generator builds the DAG at compile time and validates it:

// ── Generated: DeploymentDag.g.cs ────────────────────────────

// <auto-generated by Ops.Deployment.Generators />
namespace Ops.Deployment.Generated;

public static class DeploymentDag
{
    /// All deployment orchestrators discovered in this compilation.
    public static readonly IReadOnlyList<DeploymentNode> Nodes =
    [
        new("OrderServiceV24Deployment", "2.4", DeploymentStrategy.Rolling,
            Apps: ["order-service (3 replicas)", "order-worker (1 replica)"],
            DependsOn: ["PaymentServiceV12Deployment", "SharedInfraV8Deployment"]),

        new("PaymentServiceV12Deployment", "1.2", DeploymentStrategy.BlueGreen,
            Apps: ["payment-service (2 replicas)"],
            DependsOn: []),

        new("SharedInfraV8Deployment", "8.0", DeploymentStrategy.Rolling,
            Apps: ["rabbitmq (3 replicas)", "redis (2 replicas)"],
            DependsOn: []),
    ];

    /// Topological order — deploy in this sequence.
    public static readonly IReadOnlyList<string> ExecutionOrder =
    [
        "SharedInfraV8Deployment",     // no dependencies → first
        "PaymentServiceV12Deployment", // no dependencies → parallel with SharedInfra
        "OrderServiceV24Deployment",   // depends on Payment (MustCompleteBefore)
    ];

    /// Validate the DAG at startup (optional runtime check).
    public static void Validate()
    {
        // Verifies no circular dependencies exist
        // Verifies all DependsOn targets resolve to known orchestrators
        // Verifies all RequiredConfigs are satisfied by Configuration DSL
    }
}

public sealed record DeploymentNode(
    string Name, string Version, DeploymentStrategy Strategy,
    IReadOnlyList<string> Apps, IReadOnlyList<string> DependsOn);

Analyzer Diagnostics

// ── OPS001: Circular Deployment Dependency ───────────────────

[DeploymentOrchestrator("1.0")]
[DeploymentDependency(typeof(ServiceBDeployment))]  // B depends on A (below)
public sealed class ServiceADeployment { }

[DeploymentOrchestrator("1.0")]
[DeploymentDependency(typeof(ServiceADeployment))]  // A depends on B (above)
public sealed class ServiceBDeployment { }

// Compiler output:
// error OPS001: Circular deployment dependency detected:
//               ServiceADeployment → ServiceBDeployment → ServiceADeployment
//               — break the cycle by changing one dependency to SoftDependency
//               or CanRunInParallel

// ── OPS002: Missing Health Check for Deployed App ────────────

[DeploymentOrchestrator("3.0")]
[DeploymentApp("inventory-service", Replicas = 2)]  // ← no [HealthCheck] anywhere
public sealed class InventoryV3Deployment { }

// Compiler output:
// warning OPS002: [DeploymentApp] 'inventory-service' has no corresponding
//                 [HealthCheck] in the compilation — deployment cannot verify
//                 app readiness after rollout
//                 — add [HealthCheck("inventory-service", ...)] to a method

Sub-DSL #2: Ops.Migration

Migration is about data changes: schema alterations, data transforms, index creation, seed data, and executable migrations that run custom code.

Attribute Definitions

// ═══════════════════════════════════════════════════════════════
// Ops.Migration.Lib — Database & Data Migration DSL
// ═══════════════════════════════════════════════════════════════

/// Mark a method as a migration step — one atomic unit of database change.
[AttributeUsage(AttributeTargets.Method)]
public sealed class MigrationStepAttribute : Attribute
{
    public int Order { get; }
    public string Database { get; init; } = "default";
    public MigrationKind Kind { get; init; } = MigrationKind.Schema;
    public bool Parallel { get; init; } = false;
    public string EstimatedDuration { get; init; } = "";
    public bool RequiresDowntime { get; init; } = false;
    public string[] DependsOn { get; init; } = [];

    public MigrationStepAttribute(int order) => Order = order;
}

public enum MigrationKind
{
    Schema,          // ALTER TABLE, CREATE TABLE
    Data,            // INSERT, UPDATE, DELETE (transforms)
    Index,           // CREATE INDEX (can be slow, often parallel-safe)
    Seed,            // INSERT reference data
    ExeMigration     // custom C# code (complex transforms, file processing)
}

/// Mark a method as an executable migration — runs custom C# code
/// for transforms too complex for SQL.
[AttributeUsage(AttributeTargets.Method)]
public sealed class ExeMigrationAttribute : Attribute
{
    public int Order { get; }
    public string App { get; init; } = "";
    public string Description { get; init; } = "";
    public string[] InputFiles { get; init; } = [];
    public int ExpectedRowCount { get; init; } = 0;
    public string Timeout { get; init; } = "00:30:00";

    public ExeMigrationAttribute(int order) => Order = order;
}

/// Declare a post-migration validation — a query that must succeed
/// after the migration completes.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class MigrationValidationAttribute : Attribute
{
    public string Query { get; }
    public string ExpectedResult { get; init; } = "";
    public MigrationValidationKind Kind { get; init; } = MigrationValidationKind.RowCount;

    public MigrationValidationAttribute(string query) => Query = query;
}

public enum MigrationValidationKind
{
    RowCount,         // SELECT COUNT(*) must match expected
    SchemaCheck,      // column/table existence
    DataIntegrity,    // foreign key consistency
    IndexExists       // index presence check
}

Usage

// ── Migration steps on the deployment orchestrator ───────────

[DeploymentOrchestrator("2.4",
    Description = "Order service v2.4 — adds payment status tracking")]
public sealed class OrderServiceV24Deployment
{
    [MigrationStep(1,
        Database = "OrderDb",
        Kind = MigrationKind.Schema,
        EstimatedDuration = "00:00:30",
        RequiresDowntime = false)]
    [MigrationValidation(
        "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'Orders' AND column_name = 'PaymentStatus'",
        ExpectedResult = "1",
        Kind = MigrationValidationKind.SchemaCheck)]
    public void AddPaymentStatusColumn() { }

    [MigrationStep(2,
        Database = "OrderDb",
        Kind = MigrationKind.Data,
        EstimatedDuration = "00:05:00",
        DependsOn = ["AddPaymentStatusColumn"])]
    [MigrationValidation(
        "SELECT COUNT(*) FROM Orders WHERE PaymentStatus IS NULL",
        ExpectedResult = "0",
        Kind = MigrationValidationKind.DataIntegrity)]
    public void BackfillPaymentStatus() { }

    [MigrationStep(3,
        Database = "OrderDb",
        Kind = MigrationKind.Index,
        Parallel = true,
        EstimatedDuration = "00:02:00")]
    [MigrationValidation(
        "SELECT COUNT(*) FROM sys.indexes WHERE name = 'IX_Orders_PaymentStatus'",
        ExpectedResult = "1",
        Kind = MigrationValidationKind.IndexExists)]
    public void CreatePaymentStatusIndex() { }

    [ExeMigration(4,
        App = "order-migrator",
        Description = "Recalculate order totals using new rounding rules",
        ExpectedRowCount = 150_000,
        Timeout = "01:00:00")]
    [MigrationValidation(
        "SELECT COUNT(*) FROM Orders WHERE TotalRecalculated = 0",
        ExpectedResult = "0",
        Kind = MigrationValidationKind.DataIntegrity)]
    public void RecalculateOrderTotals() { }
}

SG: Ordered Migration Script

// ── Generated: MigrationPlan.g.cs ────────────────────────────

// <auto-generated by Ops.Migration.Generators />
namespace Ops.Migration.Generated;

public static class OrderServiceV24MigrationPlan
{
    public static readonly IReadOnlyList<MigrationStepInfo> Steps =
    [
        new(Order: 1, Name: "AddPaymentStatusColumn",
            Database: "OrderDb", Kind: MigrationKind.Schema,
            Parallel: false, RequiresDowntime: false,
            EstimatedDuration: TimeSpan.FromSeconds(30),
            Validations: [
                new("SchemaCheck", "column 'PaymentStatus' exists in 'Orders'")
            ]),

        new(Order: 2, Name: "BackfillPaymentStatus",
            Database: "OrderDb", Kind: MigrationKind.Data,
            Parallel: false, RequiresDowntime: false,
            EstimatedDuration: TimeSpan.FromMinutes(5),
            DependsOn: ["AddPaymentStatusColumn"],
            Validations: [
                new("DataIntegrity", "no NULL PaymentStatus in Orders")
            ]),

        new(Order: 3, Name: "CreatePaymentStatusIndex",
            Database: "OrderDb", Kind: MigrationKind.Index,
            Parallel: true, RequiresDowntime: false,
            EstimatedDuration: TimeSpan.FromMinutes(2),
            Validations: [
                new("IndexExists", "IX_Orders_PaymentStatus exists")
            ]),

        new(Order: 4, Name: "RecalculateOrderTotals",
            Database: "OrderDb", Kind: MigrationKind.ExeMigration,
            Parallel: false, RequiresDowntime: false,
            EstimatedDuration: TimeSpan.FromHours(1),
            App: "order-migrator",
            ExpectedRowCount: 150_000,
            Validations: [
                new("DataIntegrity", "no unrecalculated orders")
            ]),
    ];

    /// Steps 1–2 are sequential (DependsOn chain).
    /// Step 3 can run in parallel with step 2 (Parallel = true, no DependsOn conflict).
    /// Step 4 must wait for steps 2 and 3 (order > 3, sequential).
    public static readonly IReadOnlyList<IReadOnlyList<string>> ExecutionWaves =
    [
        ["AddPaymentStatusColumn"],                              // wave 1
        ["BackfillPaymentStatus", "CreatePaymentStatusIndex"],   // wave 2 (parallel)
        ["RecalculateOrderTotals"],                              // wave 3
    ];

    public static readonly TimeSpan TotalEstimatedDuration =
        TimeSpan.FromMinutes(5) + TimeSpan.FromMinutes(2) + TimeSpan.FromHours(1)
        + TimeSpan.FromSeconds(30);
    // = ~1h 7m 30s (wave 2 parallel → max of 5min and 2min = 5min)
}

Analyzer Diagnostics

// ── OPS003: Migration Without Validation ─────────────────────

[MigrationStep(5, Database = "OrderDb", Kind = MigrationKind.Data)]
public void MigrateCustomerEmails() { }  // ← no [MigrationValidation]

// Compiler output:
// warning OPS003: [MigrationStep] 'MigrateCustomerEmails' (Kind: Data)
//                 has no [MigrationValidation] — data migrations MUST have
//                 a validation query to verify correctness after execution

// ── OPS004: Parallel Conflict ────────────────────────────────

[MigrationStep(10, Database = "OrderDb", Kind = MigrationKind.Schema,
    Parallel = true)]
public void AlterOrdersTable() { }

[MigrationStep(11, Database = "OrderDb", Kind = MigrationKind.Schema,
    Parallel = true, DependsOn = ["AlterOrdersTable"])]
public void AlterOrderLinesTable() { }

// Compiler output:
// error OPS004: [MigrationStep] 'AlterOrderLinesTable' is marked Parallel = true
//               but has DependsOn = ["AlterOrdersTable"] — a step cannot be
//               parallel AND have sequential dependencies on the same database
//               — remove Parallel = true or remove DependsOn

Sub-DSL #3: Ops.Observability

Observability is about knowing what is happening: health checks confirm the system is alive, metrics quantify behavior, alerts notify when thresholds break, dashboards visualize the whole picture.

Attribute Definitions

// ═══════════════════════════════════════════════════════════════
// Ops.Observability.Lib — Health, Metrics, Alerts, Dashboards
// ═══════════════════════════════════════════════════════════════

/// Declare a health check for an application or dependency.
[AttributeUsage(AttributeTargets.Method)]
public sealed class HealthCheckAttribute : Attribute
{
    public string App { get; }
    public string Endpoint { get; init; } = "/health";
    public string Timeout { get; init; } = "00:00:05";
    public HealthCheckKind Kind { get; init; } = HealthCheckKind.Http;
    public int RetryCount { get; init; } = 3;
    public string RetryDelay { get; init; } = "00:00:02";
    public HealthCheckSeverity Severity { get; init; } = HealthCheckSeverity.Critical;

    public HealthCheckAttribute(string app) => App = app;
}

public enum HealthCheckKind
{
    Http,       // GET endpoint, expect 200
    Tcp,        // TCP connect to host:port
    Database,   // execute SELECT 1
    Queue,      // check queue depth / connectivity
    Custom      // user-defined logic
}

public enum HealthCheckSeverity
{
    Critical,       // system is down — page immediately
    Degraded,       // system is impaired — alert within minutes
    Informational   // non-critical dependency — log only
}

/// Declare a metric to be emitted by the application.
[AttributeUsage(AttributeTargets.Method)]
public sealed class MetricAttribute : Attribute
{
    public string Name { get; }
    public MetricKind Kind { get; init; } = MetricKind.Counter;
    public string Unit { get; init; } = "";
    public string[] Labels { get; init; } = [];
    public double AlertThreshold { get; init; } = double.NaN;
    public AlertSeverity AlertSeverity { get; init; } = AlertSeverity.Warning;

    public MetricAttribute(string name) => Name = name;
}

public enum MetricKind
{
    Counter,      // monotonically increasing (e.g., requests_total)
    Gauge,        // point-in-time value (e.g., queue_depth)
    Histogram,    // distribution (e.g., request_duration_seconds)
    Summary       // quantiles (e.g., p50, p95, p99)
}

/// Declare an alert rule — fires when a condition holds for a duration.
[AttributeUsage(AttributeTargets.Method)]
public sealed class AlertRuleAttribute : Attribute
{
    public string Name { get; }
    public string Condition { get; init; } = "";
    public string Duration { get; init; } = "5m";
    public AlertSeverity Severity { get; init; } = AlertSeverity.Warning;
    public string[] NotifyChannels { get; init; } = [];
    public string Runbook { get; init; } = "";

    public AlertRuleAttribute(string name) => Name = name;
}

public enum AlertSeverity
{
    Info,        // informational — logged, not notified
    Warning,     // team channel notification
    Critical,    // on-call pager
    PageNow      // immediate escalation, all hands
}

/// Declare a Grafana dashboard panel grouping.
[AttributeUsage(AttributeTargets.Class)]
public sealed class DashboardAttribute : Attribute
{
    public string Name { get; }
    public string[] Panels { get; init; } = [];
    public string RefreshInterval { get; init; } = "30s";

    public DashboardAttribute(string name) => Name = name;
}

Usage

// ── Observability declarations on the deployment class ───────

[DeploymentOrchestrator("2.4")]
[Dashboard("Order Service",
    Panels = ["request_rate", "error_rate", "p95_latency", "queue_depth",
              "payment_status_distribution"],
    RefreshInterval = "10s")]
public sealed class OrderServiceV24Deployment
{
    [HealthCheck("order-service",
        Endpoint = "/health",
        Kind = HealthCheckKind.Http,
        Timeout = "00:00:05",
        RetryCount = 3,
        RetryDelay = "00:00:02",
        Severity = HealthCheckSeverity.Critical)]
    public void OrderServiceHealth() { }

    [HealthCheck("order-db",
        Kind = HealthCheckKind.Database,
        Timeout = "00:00:03",
        Severity = HealthCheckSeverity.Critical)]
    public void OrderDatabaseHealth() { }

    [HealthCheck("rabbitmq",
        Kind = HealthCheckKind.Queue,
        Timeout = "00:00:05",
        Severity = HealthCheckSeverity.Degraded)]
    public void RabbitMqHealth() { }

    [Metric("order_requests_total",
        Kind = MetricKind.Counter,
        Unit = "requests",
        Labels = ["method", "status_code"],
        AlertThreshold = 0,
        AlertSeverity = AlertSeverity.Critical)]
    public void OrderRequestsTotal() { }

    [Metric("order_request_duration_seconds",
        Kind = MetricKind.Histogram,
        Unit = "seconds",
        Labels = ["method", "endpoint"],
        AlertThreshold = 2.0,
        AlertSeverity = AlertSeverity.Warning)]
    public void OrderRequestDuration() { }

    [Metric("order_queue_depth",
        Kind = MetricKind.Gauge,
        Unit = "messages",
        Labels = ["queue_name"],
        AlertThreshold = 1000,
        AlertSeverity = AlertSeverity.Warning)]
    public void OrderQueueDepth() { }

    [AlertRule("OrderServiceHighErrorRate",
        Condition = "rate(order_requests_total{status_code=~\"5..\"}[5m]) / rate(order_requests_total[5m]) > 0.05",
        Duration = "5m",
        Severity = AlertSeverity.Critical,
        NotifyChannels = ["#order-team", "pagerduty:order-oncall"],
        Runbook = "https://wiki.internal/runbooks/order-service-errors")]
    public void HighErrorRateAlert() { }

    [AlertRule("OrderServiceHighLatency",
        Condition = "histogram_quantile(0.95, order_request_duration_seconds) > 2.0",
        Duration = "10m",
        Severity = AlertSeverity.Warning,
        NotifyChannels = ["#order-team"],
        Runbook = "https://wiki.internal/runbooks/order-service-latency")]
    public void HighLatencyAlert() { }
}

SG: Health Check Registration Code

// ── Generated: HealthChecks.g.cs ─────────────────────────────

// <auto-generated by Ops.Observability.Generators />
namespace Ops.Observability.Generated;

public static class OrderServiceV24HealthChecks
{
    /// Register all health checks discovered for OrderServiceV24Deployment.
    public static IServiceCollection AddOrderServiceV24HealthChecks(
        this IServiceCollection services)
    {
        services.AddHealthChecks()

            // [HealthCheck("order-service")] → HTTP probe
            .AddUrlGroup(
                new Uri("http://order-service/health"),
                name: "order-service",
                timeout: TimeSpan.FromSeconds(5),
                failureStatus: HealthStatus.Unhealthy,    // Severity: Critical
                tags: ["critical", "http"])

            // [HealthCheck("order-db")] → Database probe
            .AddSqlServer(
                name: "order-db",
                connectionString: "{{ConnectionStrings:OrderDb}}",
                timeout: TimeSpan.FromSeconds(3),
                failureStatus: HealthStatus.Unhealthy,    // Severity: Critical
                tags: ["critical", "database"])

            // [HealthCheck("rabbitmq")] → Queue probe
            .AddRabbitMQ(
                name: "rabbitmq",
                timeout: TimeSpan.FromSeconds(5),
                failureStatus: HealthStatus.Degraded,     // Severity: Degraded
                tags: ["degraded", "queue"]);

        return services;
    }

    /// Retry policy for health checks.
    public static readonly HealthCheckRetryPolicy RetryPolicy = new(
        RetryCount: 3,
        RetryDelay: TimeSpan.FromSeconds(2));
}

Analyzer Diagnostics

// ── OPS005: Metric Without Alert ─────────────────────────────

[Metric("payment_processing_duration_seconds",
    Kind = MetricKind.Histogram,
    AlertThreshold = 5.0)]  // threshold set, but...
public void PaymentDuration() { }
// ← no [AlertRule] references "payment_processing_duration_seconds"

// Compiler output:
// warning OPS005: [Metric] 'payment_processing_duration_seconds' has
//                 AlertThreshold = 5.0 but no [AlertRule] references this
//                 metric — the threshold will never trigger an alert
//                 — add an [AlertRule] with a condition using this metric

// ── OPS006: Alert Without Runbook ────────────────────────────

[AlertRule("DatabaseConnectionPoolExhausted",
    Condition = "db_connection_pool_active >= db_connection_pool_max",
    Duration = "2m",
    Severity = AlertSeverity.PageNow,
    NotifyChannels = ["pagerduty:db-oncall"])]
    // ← Runbook = "" (missing!)
public void DbPoolAlert() { }

// Compiler output:
// error OPS006: [AlertRule] 'DatabaseConnectionPoolExhausted' has
//               Severity = PageNow but no Runbook — a PageNow alert
//               MUST have a runbook URL so the on-call engineer knows
//               what to do at 3am

Sub-DSL #4: Ops.Configuration

Configuration is about environment correctness: what settings each environment needs, where secrets live, how to validate that a deployment has the right config before it starts.

Attribute Definitions

// ═══════════════════════════════════════════════════════════════
// Ops.Configuration.Lib — Environment, Secrets, Transforms
// ═══════════════════════════════════════════════════════════════

/// Declare a configuration transform for a specific environment.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class ConfigTransformAttribute : Attribute
{
    public string Environment { get; }
    public string Section { get; init; } = "";
    public ConfigTransformKind Kind { get; init; } = ConfigTransformKind.Replace;
    public bool RequiresRestart { get; init; } = false;
    public string ValidationRegex { get; init; } = "";

    public ConfigTransformAttribute(string environment) => Environment = environment;
}

public enum ConfigTransformKind
{
    Replace,   // overwrite the value entirely
    Merge,     // deep-merge objects
    Append,    // add to arrays
    Remove     // remove the key from config
}

/// Declare a secret reference — a value that must come from a vault.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
public sealed class SecretAttribute : Attribute
{
    public string VaultPath { get; }
    public SecretProvider Provider { get; init; } = SecretProvider.Environment;
    public string RotationPolicy { get; init; } = "";

    public SecretAttribute(string vaultPath) => VaultPath = vaultPath;
}

public enum SecretProvider
{
    AzureKeyVault,
    AwsSecretsManager,
    HashiCorpVault,
    Environment        // fallback: environment variable
}

/// Declare the complete configuration matrix — which environments
/// need which configs and secrets.
[AttributeUsage(AttributeTargets.Class)]
public sealed class EnvironmentMatrixAttribute : Attribute
{
    public string[] Environments { get; init; } = [];
    public string[] RequiredConfigs { get; init; } = [];
    public string[] RequiredSecrets { get; init; } = [];
}

Usage

// ── Configuration declarations on the deployment class ───────

[DeploymentOrchestrator("2.4")]
[EnvironmentMatrix(
    Environments = ["development", "staging", "production"],
    RequiredConfigs = [
        "ConnectionStrings:OrderDb",
        "PaymentGateway:BaseUrl",
        "RabbitMq:ConnectionString",
        "Logging:MinimumLevel"],
    RequiredSecrets = [
        "PaymentGateway:ApiKey",
        "OrderDb:Password",
        "RabbitMq:Password"])]
public sealed class OrderServiceV24Deployment
{
    [ConfigTransform("staging",
        Section = "PaymentGateway:BaseUrl",
        Kind = ConfigTransformKind.Replace,
        ValidationRegex = @"^https://.*\.staging\.internal/.*$")]
    public void StagingPaymentGatewayUrl() { }

    [ConfigTransform("production",
        Section = "PaymentGateway:BaseUrl",
        Kind = ConfigTransformKind.Replace,
        ValidationRegex = @"^https://api\.payment\.com/.*$")]
    public void ProductionPaymentGatewayUrl() { }

    [ConfigTransform("production",
        Section = "Logging:MinimumLevel",
        Kind = ConfigTransformKind.Replace,
        RequiresRestart = true)]
    public void ProductionLoggingLevel() { }

    [Secret("secrets/order-service/payment-gateway-api-key",
        Provider = SecretProvider.HashiCorpVault,
        RotationPolicy = "90d")]
    public string PaymentGatewayApiKey => "";

    [Secret("secrets/order-service/order-db-password",
        Provider = SecretProvider.HashiCorpVault,
        RotationPolicy = "30d")]
    public string OrderDbPassword => "";

    [Secret("secrets/order-service/rabbitmq-password",
        Provider = SecretProvider.AzureKeyVault,
        RotationPolicy = "60d")]
    public string RabbitMqPassword => "";
}

SG: Environment Config Validation

// ── Generated: EnvironmentConfigValidator.g.cs ───────────────

// <auto-generated by Ops.Configuration.Generators />
namespace Ops.Configuration.Generated;

public static class OrderServiceV24ConfigValidator
{
    /// Validate that all required configs and secrets are present
    /// for a given environment.
    public static ConfigValidationResult Validate(
        string environment, IConfiguration config, ISecretProvider secrets)
    {
        var errors = new List<string>();

        // ── Required configs (from [EnvironmentMatrix]) ──
        var requiredConfigs = new[]
        {
            "ConnectionStrings:OrderDb",
            "PaymentGateway:BaseUrl",
            "RabbitMq:ConnectionString",
            "Logging:MinimumLevel",
        };

        foreach (var key in requiredConfigs)
        {
            if (string.IsNullOrEmpty(config[key]))
                errors.Add($"Missing config: '{key}' for environment '{environment}'");
        }

        // ── Required secrets (from [EnvironmentMatrix]) ──
        var requiredSecrets = new[]
        {
            ("PaymentGateway:ApiKey",  "secrets/order-service/payment-gateway-api-key"),
            ("OrderDb:Password",       "secrets/order-service/order-db-password"),
            ("RabbitMq:Password",      "secrets/order-service/rabbitmq-password"),
        };

        foreach (var (configKey, vaultPath) in requiredSecrets)
        {
            if (!secrets.Exists(vaultPath))
                errors.Add($"Missing secret: '{vaultPath}' (for config '{configKey}')");
        }

        // ── Config transforms validation (from [ConfigTransform]) ──
        if (environment == "staging")
        {
            var url = config["PaymentGateway:BaseUrl"] ?? "";
            if (!Regex.IsMatch(url, @"^https://.*\.staging\.internal/.*$"))
                errors.Add(
                    $"Config 'PaymentGateway:BaseUrl' = '{url}' does not match " +
                    $"staging pattern '^https://.*\\.staging\\.internal/.*$'");
        }

        if (environment == "production")
        {
            var url = config["PaymentGateway:BaseUrl"] ?? "";
            if (!Regex.IsMatch(url, @"^https://api\.payment\.com/.*$"))
                errors.Add(
                    $"Config 'PaymentGateway:BaseUrl' = '{url}' does not match " +
                    $"production pattern '^https://api\\.payment\\.com/.*$'");
        }

        return new ConfigValidationResult(
            Environment: environment,
            IsValid: errors.Count == 0,
            Errors: errors);
    }

    /// Secret rotation audit — checks if any secret is past its rotation date.
    public static IReadOnlyList<SecretRotationWarning> CheckRotation(
        ISecretProvider secrets)
    {
        return new SecretRotationWarning[]
        {
            Check(secrets, "secrets/order-service/payment-gateway-api-key",
                rotationDays: 90),
            Check(secrets, "secrets/order-service/order-db-password",
                rotationDays: 30),
            Check(secrets, "secrets/order-service/rabbitmq-password",
                rotationDays: 60),
        }.Where(w => w.IsOverdue).ToArray();
    }
}

Analyzer Diagnostics

// ── OPS007: Missing Secret for Required Config ───────────────

[EnvironmentMatrix(
    Environments = ["staging", "production"],
    RequiredConfigs = ["Redis:ConnectionString"],
    RequiredSecrets = ["Redis:Password"])]     // ← declared as required
public sealed class CacheServiceDeployment
{
    // No [Secret] attribute maps to "Redis:Password"
}

// Compiler output:
// error OPS007: [EnvironmentMatrix] declares RequiredSecret 'Redis:Password'
//               but no [Secret] attribute in 'CacheServiceDeployment' maps
//               to this secret — add [Secret("secrets/.../redis-password")]
//               to a property or method

// ── OPS008: Environment Matrix Gap ───────────────────────────

[EnvironmentMatrix(
    Environments = ["development", "staging", "production"],
    RequiredConfigs = ["PaymentGateway:BaseUrl"])]
public sealed class OrderServiceV24Deployment
{
    [ConfigTransform("staging", Section = "PaymentGateway:BaseUrl")]
    public void StagingPaymentUrl() { }

    [ConfigTransform("production", Section = "PaymentGateway:BaseUrl")]
    public void ProductionPaymentUrl() { }

    // ← no [ConfigTransform("development", ...)] for PaymentGateway:BaseUrl
}

// Compiler output:
// warning OPS008: [EnvironmentMatrix] declares environment 'development'
//                 with RequiredConfig 'PaymentGateway:BaseUrl' but no
//                 [ConfigTransform("development", Section = "PaymentGateway:BaseUrl")]
//                 exists — 'development' will use the default value (if any)
//                 — add a transform or document that the default is intentional

Sub-DSL #5: Ops.Resilience

Resilience is about recovery: what happens when a deployment fails, how to undo a migration, how to gradually roll out with canary traffic, how to isolate a failing dependency with a circuit breaker.

Attribute Definitions

// ═══════════════════════════════════════════════════════════════
// Ops.Resilience.Lib — Rollback, Canary, Circuit Breaker
// ═══════════════════════════════════════════════════════════════

/// Declare a rollback procedure — the reverse of a deployment or migration step.
[AttributeUsage(AttributeTargets.Method)]
public sealed class RollbackProcedureAttribute : Attribute
{
    public string ReverseOf { get; }
    public RollbackKind Kind { get; init; } = RollbackKind.Automatic;
    public string EstimatedDuration { get; init; } = "";
    public bool RequiresApproval { get; init; } = false;
    public string[] PreConditions { get; init; } = [];

    public RollbackProcedureAttribute(string reverseOf) => ReverseOf = reverseOf;
}

public enum RollbackKind
{
    Automatic,     // system executes automatically on failure
    Manual,        // human must execute (with documented steps)
    Conditional,   // automatic if condition met, manual otherwise
    DataOnly       // only reverses data changes, not schema
}

/// Declare a canary deployment rule — gradual traffic shift with automatic rollback.
[AttributeUsage(AttributeTargets.Method)]
public sealed class CanaryRuleAttribute : Attribute
{
    public int TrafficPercentage { get; }
    public string Duration { get; init; } = "15m";
    public string SuccessMetric { get; init; } = "";
    public double RollbackThreshold { get; init; } = 0.05;

    public CanaryRuleAttribute(int trafficPercentage) =>
        TrafficPercentage = trafficPercentage;
}

/// Declare a circuit breaker for an external dependency.
[AttributeUsage(AttributeTargets.Method)]
public sealed class CircuitBreakerAttribute : Attribute
{
    public string Dependency { get; }
    public int FailureThreshold { get; init; } = 5;
    public string OpenDuration { get; init; } = "00:00:30";
    public string FallbackMethod { get; init; } = "";

    public CircuitBreakerAttribute(string dependency) => Dependency = dependency;
}

Usage

// ── Resilience declarations on the deployment class ──────────

[DeploymentOrchestrator("2.4",
    Strategy = DeploymentStrategy.Canary)]
public sealed class OrderServiceV24Deployment
{
    // ── Canary rules — gradual rollout ──

    [CanaryRule(10,
        Duration = "10m",
        SuccessMetric = "order_requests_total",
        RollbackThreshold = 0.05)]
    public void CanaryPhase1_10Percent() { }

    [CanaryRule(50,
        Duration = "15m",
        SuccessMetric = "order_requests_total",
        RollbackThreshold = 0.03)]
    public void CanaryPhase2_50Percent() { }

    [CanaryRule(100,
        Duration = "5m",
        SuccessMetric = "order_requests_total",
        RollbackThreshold = 0.01)]
    public void CanaryPhase3_100Percent() { }

    // ── Rollback procedures — reverse of migration steps ──

    [RollbackProcedure("AddPaymentStatusColumn",
        Kind = RollbackKind.Automatic,
        EstimatedDuration = "00:00:15")]
    public void RollbackPaymentStatusColumn() { }

    [RollbackProcedure("BackfillPaymentStatus",
        Kind = RollbackKind.DataOnly,
        EstimatedDuration = "00:03:00",
        PreConditions = ["No orders created with new PaymentStatus values"])]
    public void RollbackPaymentStatusBackfill() { }

    [RollbackProcedure("RecalculateOrderTotals",
        Kind = RollbackKind.Manual,
        RequiresApproval = true,
        EstimatedDuration = "00:45:00",
        PreConditions = [
            "Verify no invoices generated from recalculated totals",
            "Finance team approval required"])]
    public void RollbackOrderTotalRecalculation() { }

    // ── Circuit breakers — external dependency isolation ──

    [CircuitBreaker("payment-gateway",
        FailureThreshold = 5,
        OpenDuration = "00:00:30",
        FallbackMethod = nameof(PaymentGatewayFallback))]
    public void PaymentGatewayCircuitBreaker() { }

    public void PaymentGatewayFallback()
    {
        // Queue payment for retry, return 202 Accepted
    }

    [CircuitBreaker("inventory-service",
        FailureThreshold = 10,
        OpenDuration = "00:01:00",
        FallbackMethod = nameof(InventoryFallback))]
    public void InventoryCircuitBreaker() { }

    public void InventoryFallback()
    {
        // Use cached stock levels, flag order for manual review
    }
}

SG: Rollback Ordering Validation

// ── Generated: RollbackPlan.g.cs ─────────────────────────────

// <auto-generated by Ops.Resilience.Generators />
namespace Ops.Resilience.Generated;

public static class OrderServiceV24RollbackPlan
{
    /// Rollback steps in REVERSE order of their corresponding migration steps.
    /// Migration order: 1 → 2 → 3 → 4
    /// Rollback order:  4 → 2 → 1  (step 3 = index, no explicit rollback)
    public static readonly IReadOnlyList<RollbackStepInfo> Steps =
    [
        new(Name: "RollbackOrderTotalRecalculation",
            ReverseOf: "RecalculateOrderTotals",
            Kind: RollbackKind.Manual,
            RequiresApproval: true,
            EstimatedDuration: TimeSpan.FromMinutes(45),
            PreConditions: [
                "Verify no invoices generated from recalculated totals",
                "Finance team approval required"
            ]),

        new(Name: "RollbackPaymentStatusBackfill",
            ReverseOf: "BackfillPaymentStatus",
            Kind: RollbackKind.DataOnly,
            EstimatedDuration: TimeSpan.FromMinutes(3),
            PreConditions: [
                "No orders created with new PaymentStatus values"
            ]),

        new(Name: "RollbackPaymentStatusColumn",
            ReverseOf: "AddPaymentStatusColumn",
            Kind: RollbackKind.Automatic,
            EstimatedDuration: TimeSpan.FromSeconds(15),
            PreConditions: []),
    ];

    /// Migration steps WITHOUT a rollback procedure.
    public static readonly IReadOnlyList<string> UnprotectedSteps =
    [
        "CreatePaymentStatusIndex",   // no [RollbackProcedure("CreatePaymentStatusIndex")]
    ];

    /// Total estimated rollback duration (sum of all steps).
    public static readonly TimeSpan TotalEstimatedDuration =
        TimeSpan.FromMinutes(45) + TimeSpan.FromMinutes(3) + TimeSpan.FromSeconds(15);

    /// Canary rollback stages (from [CanaryRule] attributes).
    public static readonly IReadOnlyList<CanaryStage> CanaryStages =
    [
        new(TrafficPercentage: 10, Duration: TimeSpan.FromMinutes(10),
            SuccessMetric: "order_requests_total", RollbackThreshold: 0.05),
        new(TrafficPercentage: 50, Duration: TimeSpan.FromMinutes(15),
            SuccessMetric: "order_requests_total", RollbackThreshold: 0.03),
        new(TrafficPercentage: 100, Duration: TimeSpan.FromMinutes(5),
            SuccessMetric: "order_requests_total", RollbackThreshold: 0.01),
    ];
}

Analyzer Diagnostics

// ── OPS009: Migration Without Rollback ───────────────────────

[MigrationStep(7, Kind = MigrationKind.Data, EstimatedDuration = "00:10:00")]
public void MigratePaymentRecords() { }
// ← no [RollbackProcedure("MigratePaymentRecords")]

// Compiler output:
// warning OPS009: [MigrationStep] 'MigratePaymentRecords' (Kind: Data)
//                 has no corresponding [RollbackProcedure] — if this
//                 migration fails, there is no documented way to reverse it
//                 — add [RollbackProcedure("MigratePaymentRecords")] to a method

// ── OPS010: Canary Without Metric ────────────────────────────

[CanaryRule(25,
    Duration = "10m",
    SuccessMetric = "checkout_success_rate",   // ← no [Metric] with this name
    RollbackThreshold = 0.02)]
public void CanaryPhase1() { }

// Compiler output:
// error OPS010: [CanaryRule] references SuccessMetric 'checkout_success_rate'
//               but no [Metric] with Name = 'checkout_success_rate' exists
//               in the compilation — the canary has no way to measure success
//               — add [Metric("checkout_success_rate", ...)] to a method

// ── OPS011: Circuit Breaker Without Fallback ─────────────────

[CircuitBreaker("email-service",
    FailureThreshold = 3,
    OpenDuration = "00:01:00")]
    // ← FallbackMethod = "" (missing!)
public void EmailCircuitBreaker() { }

// Compiler output:
// error OPS011: [CircuitBreaker] for dependency 'email-service' has no
//               FallbackMethod — when the circuit opens, there is no
//               fallback behavior defined
//               — set FallbackMethod = nameof(EmailFallback) and implement
//               the fallback method

Cross-DSL References

The five sub-DSLs are independent NuGet packages, but they are not isolated. A deployment is not just steps in a DAG -- it is migrations verified by health checks, monitored by metrics, guarded by canary rules that watch those metrics, and recoverable by rollback procedures that reverse those migrations.

How Sub-DSLs Reference Each Other

[MigrationStep] ──────links to──────► [HealthCheck]
  "After migration, verify health"     via app name match

[RollbackProcedure] ──reverses──────► [MigrationStep]
  "Undo this specific step"            via ReverseOf = method name

[AlertRule] ──────────watches────────► [Metric]
  "Fire when metric exceeds threshold" via Condition referencing metric name

[CanaryRule] ─────────measures───────► [Metric]
  "Use this metric to judge success"   via SuccessMetric = metric name

[DeploymentApp] ──────requires───────► [ConfigTransform] + [Secret]
  "App needs these configs to start"   via RequiredConfigs / RequiredSecrets

[CircuitBreaker] ─────depends on─────► [HealthCheck]
  "Monitor this dependency's health"   via dependency name match

Concrete Example -- The Full Cross-Reference Chain

// ── Migration step defines the change ────────────────────────

[MigrationStep(1, Database = "OrderDb", Kind = MigrationKind.Schema)]
public void AddPaymentStatusColumn() { }

// ── Health check verifies the app after migration ────────────

[HealthCheck("order-service",
    Endpoint = "/health",
    Severity = HealthCheckSeverity.Critical)]
public void OrderServiceHealth() { }

// ── Metric measures the app's behavior post-deployment ───────

[Metric("order_requests_total",
    Kind = MetricKind.Counter,
    Labels = ["status_code"])]
public void OrderRequestsTotal() { }

// ── Alert fires if the metric crosses a threshold ────────────

[AlertRule("OrderHighErrorRate",
    Condition = "rate(order_requests_total{status_code=~\"5..\"}[5m]) > 0.05",
    Severity = AlertSeverity.Critical,
    Runbook = "https://wiki.internal/runbooks/order-errors")]
public void HighErrorRateAlert() { }

// ── Canary uses the SAME metric to judge rollout success ─────

[CanaryRule(10,
    SuccessMetric = "order_requests_total",
    RollbackThreshold = 0.05)]
public void CanaryPhase1() { }

// ── Rollback reverses the migration if everything fails ──────

[RollbackProcedure("AddPaymentStatusColumn",
    Kind = RollbackKind.Automatic)]
public void RollbackPaymentStatusColumn() { }

// The cross-reference chain:
// MigrationStep → HealthCheck (verify after) → Metric (measure)
//   → AlertRule (notify) → CanaryRule (gate) → RollbackProcedure (recover)
// Every link is typed. Every link is validated at compile time.

Analyzer: OPS012 -- Cross-DSL Reference Target Not Found

// ── When a cross-DSL reference points to nothing ─────────────

[RollbackProcedure("DropLegacyOrdersTable",   // ← no such MigrationStep
    Kind = RollbackKind.Automatic)]
public void RollbackDropLegacy() { }

// Compiler output:
// error OPS012: [RollbackProcedure] ReverseOf = 'DropLegacyOrdersTable'
//               but no [MigrationStep] method named 'DropLegacyOrdersTable'
//               exists in this deployment class
//               — verify the method name or add the missing [MigrationStep]

[CanaryRule(25,
    SuccessMetric = "inventory_checkout_rate")]   // ← no such Metric
public void CanaryPhase() { }

// Compiler output:
// error OPS012: [CanaryRule] SuccessMetric = 'inventory_checkout_rate'
//               but no [Metric] with Name = 'inventory_checkout_rate'
//               exists in the compilation
//               — add [Metric("inventory_checkout_rate", ...)] or fix the name

[AlertRule("StaleQueueAlert",
    Condition = "queue_lag_seconds > 300")]   // ← references metric not declared
public void StaleQueueAlert() { }

// Compiler output:
// warning OPS012: [AlertRule] Condition references 'queue_lag_seconds'
//                 but no [Metric] with Name = 'queue_lag_seconds' exists
//                 in the compilation — the alert condition may reference
//                 an external metric (suppress if intentional)

The cross-DSL analyzer parses attribute values (string names, method names, metric references) and resolves them against all attributes in the compilation. If a reference target does not exist, you know at compile time -- not at 3am when the canary rule tries to read a metric that was renamed last sprint.


What's Next

The five sub-DSLs provide the typed metadata for the entire ops lifecycle. Every deployment step, migration, health check, metric, alert, config transform, secret, rollback, canary rule, and circuit breaker is declared with an attribute, validated by an analyzer, and wired by a Source Generator.

Part V shows the generated output: not just C# code, but Grafana dashboard JSON, Prometheus alert YAML, Kubernetes liveness/readiness probes, Helm values.yaml, and Markdown runbooks. The Document DSL reads all five sub-DSLs and produces a complete, cross-referenced deployment package -- from a single dotnet build.