<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ sdops - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ sdops - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 23 Oct 2026 12:35:59 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/sdops/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Detect Operational Drift Before It Becomes an Incident ]]>
                </title>
                <description>
                    <![CDATA[ In my previous article, I explored operational conformance: how to evaluate whether observed evidence satisfies an operational specification. That gives us a useful point-in-time question: Does the s ]]>
                </description>
                <link>https://www.freecodecamp.org/news/detect-operational-drift-before-incident/</link>
                <guid isPermaLink="false">6ad5be696f06108d923b29a5</guid>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ observability ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ sdops ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hugo Teijiz ]]>
                </dc:creator>
                <pubDate>Mon, 19 Oct 2026 06:53:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/883e3abc-de6f-48c8-aff5-5c5cab4093c2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In <a href="https://www.freecodecamp.org/news/measure-conformance-operational-specification-execution/">my previous article</a>, I explored operational conformance: how to evaluate whether observed evidence satisfies an operational specification.</p>
<p>That gives us a useful point-in-time question:</p>
<blockquote>
<p>Does the system satisfy the specification right now?</p>
</blockquote>
<p>But production systems don't stay still.</p>
<p>Traffic changes. Dependencies degrade. Certificates expire. Configuration is modified. Feature flags move. Data grows. People make manual changes. Agents take actions. Infrastructure is replaced. And so on.</p>
<p>A system can be conformant immediately after a deployment and stop being conformant hours or days later.</p>
<p>That's the problem I want to explore here.</p>
<p>Operational drift is the divergence between the operational specification and the state we continue to observe over time.</p>
<p>This is broader than configuration drift. Configuration drift asks whether infrastructure or configuration differs from a declared state.</p>
<p>Operational drift asks whether the system still behaves within the boundaries we intended, even if the configuration has not changed at all.</p>
<p>A service can have exactly the same configuration it had yesterday and still drift operationally because latency increased, error rate degraded, replica availability fell, a dependency became unhealthy, a policy is no longer being enforced, or evidence disappeared.</p>
<p>In this article, I’ll show you how to model operational drift, establish a conformance baseline, reevaluate a system over time, distinguish temporary noise from meaningful divergence, preserve drift evidence, classify severity, and decide when drift should trigger an alert, an investigation, or an automated response.</p>
<p>The central idea is this: conformance tells you whether reality matches intent at a point in time. Drift tells you whether that relationship is changing.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You should be comfortable with:</p>
<ul>
<li><p>software architecture</p>
</li>
<li><p>TypeScript or a similar language</p>
</li>
<li><p>observability</p>
</li>
<li><p>operational specifications</p>
</li>
<li><p>evidence-based evaluation</p>
</li>
<li><p>conformance</p>
</li>
<li><p>basic monitoring concepts</p>
</li>
</ul>
<p>You don't need a specific infrastructure platform.</p>
<p>The examples use a deployment scenario, but the same model applies to infrastructure, data pipelines, backup systems, access controls, AI agents, and other operational workflows.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-operational-drift-means">What Operational Drift Means</a></p>
</li>
<li><p><a href="#heading-operational-drift-isnt-the-same-as-configuration-drift">Operational Drift Isn't the Same as Configuration Drift</a></p>
</li>
<li><p><a href="#heading-start-with-a-conformance-baseline">Start with a Conformance Baseline</a></p>
</li>
<li><p><a href="#heading-observe-the-same-constraints-over-time">Observe the Same Constraints Over Time</a></p>
</li>
<li><p><a href="#heading-model-drift-explicitly">Model Drift Explicitly</a></p>
</li>
<li><p><a href="#heading-temporary-variation-isnt-always-drift">Temporary Variation Isn't Always Drift</a></p>
</li>
<li><p><a href="#heading-use-windows-instead-of-single-samples">Use Windows Instead of Single Samples</a></p>
</li>
<li><p><a href="#heading-distinguish-drift-from-missing-evidence">Distinguish Drift from Missing Evidence</a></p>
</li>
<li><p><a href="#heading-add-severity-and-persistence">Add Severity and Persistence</a></p>
</li>
<li><p><a href="#heading-detect-directional-drift-before-a-threshold-fails">Detect Directional Drift Before a Threshold Fails</a></p>
</li>
<li><p><a href="#heading-preserve-the-evidence-behind-drift">Preserve the Evidence Behind Drift</a></p>
</li>
<li><p><a href="#heading-a-reusable-drift-evaluator">A Reusable Drift Evaluator</a></p>
</li>
<li><p><a href="#heading-a-small-end-to-end-example">A Small End-to-End Example</a></p>
</li>
<li><p><a href="#heading-how-drift-detection-applies-to-multiple-executors">How Drift Detection Applies to Multiple Executors</a></p>
</li>
<li><p><a href="#heading-how-drift-detection-applies-to-ai-agents">How Drift Detection Applies to AI Agents</a></p>
</li>
<li><p><a href="#heading-when-drift-should-trigger-action">When Drift Should Trigger Action</a></p>
</li>
<li><p><a href="#heading-what-operational-drift-cant-tell-you">What Operational Drift Can't Tell You</a></p>
</li>
<li><p><a href="#heading-a-practical-drift-detection-workflow">A Practical Drift Detection Workflow</a></p>
</li>
<li><p><a href="#heading-from-point-in-time-conformance-to-continuous-verification">From Point-in-Time Conformance to Continuous Verification</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-operational-drift-means">What Operational Drift Means</h2>
<p>Suppose a deployment finishes at 10:00.</p>
<p>The operational specification says:</p>
<pre><code class="language-text">version = v42
available replicas &gt;= 3
error rate &lt;= 1%
p95 latency &lt;= 400 ms
</code></pre>
<p>Immediately after deployment, the evidence says:</p>
<pre><code class="language-text">version = v42
available replicas = 3
error rate = 0.4%
p95 latency = 280 ms
</code></pre>
<p>The system is conformant.</p>
<p>Now suppose that at 14:00 the evidence says:</p>
<pre><code class="language-text">version = v42
available replicas = 3
error rate = 0.8%
p95 latency = 370 ms
</code></pre>
<p>The system is still conformant, but something has changed.</p>
<p>The error rate and latency are moving toward their limits.</p>
<p>At 16:00:</p>
<pre><code class="language-text">version = v42
available replicas = 3
error rate = 1.4%
p95 latency = 460 ms
</code></pre>
<p>Now the system is no longer conformant.</p>
<p>The operational specification didn't change. The executor did not necessarily run again. But reality moved.</p>
<p>That's operational drift.</p>
<p>A useful way to think about it is:</p>
<pre><code class="language-text">Specification
      ↓
Expected operational boundaries
      ↓
Observed state at T0
      ↓
Observed state at T1
      ↓
Observed state at T2
      ↓
Drift evaluation
</code></pre>
<p>The key idea is temporal: conformance is a snapshot, while drift is a relationship across snapshots.</p>
<h2 id="heading-operational-drift-isnt-the-same-as-configuration-drift">Operational Drift Isn't the Same as Configuration Drift</h2>
<p>Configuration drift is likely a familiar infrastructure concept to you.</p>
<p>By <strong>configuration drift</strong>, I mean a difference between the configuration or infrastructure state you declared and the state that actually exists in the environment.</p>
<p>For example:</p>
<pre><code class="language-text">desired replicas = 3
actual replicas = 2
</code></pre>
<p>Here, the declared state says the service should have three replicas, but the running environment only has two. The configuration and the actual state no longer match.</p>
<p>Or:</p>
<pre><code class="language-text">Terraform state != deployed infrastructure
</code></pre>
<p>In that case, infrastructure has changed outside the expected Terraform-managed state, or Terraform's record no longer matches what's actually deployed.</p>
<p>Those are both configuration drift because the mismatch is between a declared configuration and the environment that should reflect it.</p>
<p>That's useful, but it's narrower.</p>
<p>Operational drift can exist even when configuration is unchanged.</p>
<p>Consider this specification:</p>
<pre><code class="language-text">error rate &lt;= 1%
</code></pre>
<p>The deployment configuration may still be identical to yesterday.</p>
<p>Yet traffic mix may have changed, a downstream API may have slowed down, a database index may have degraded, a certificate may be approaching expiration, or a queue backlog may have grown.</p>
<p>So:</p>
<pre><code class="language-text">configuration drift
=
declared configuration != actual configuration
</code></pre>
<p>while:</p>
<pre><code class="language-text">operational drift
=
observed operational state != intended operational boundaries
</code></pre>
<p>The two can overlap, but they're not the same thing.</p>
<p>That distinction matters because production failures are often behavioral before they are configurational.</p>
<h2 id="heading-start-with-a-conformance-baseline">Start with a Conformance Baseline</h2>
<p>Drift needs a reference point. The easiest reference is a known conformant state.</p>
<p>For example:</p>
<pre><code class="language-typescript">type DeploymentEvidence = {
  version?: string;
  replicas?: number;
  errorRate?: number;
  p95LatencyMs?: number;
};
</code></pre>
<p>Suppose the system was evaluated after deployment:</p>
<pre><code class="language-typescript">const baseline: DeploymentEvidence = {
  version: "v42",
  replicas: 3,
  errorRate: 0.004,
  p95LatencyMs: 280,
};
</code></pre>
<p>and all required constraints passed.</p>
<p>That baseline gives you two useful things.</p>
<p>First, you know the system satisfied the specification at that point. And second, you have a concrete evidence snapshot against which later observations can be compared.</p>
<p>This doesn't mean later evidence must match the baseline exactly. That would be too strict.</p>
<p>It means later evidence can be evaluated in two ways:</p>
<pre><code class="language-text">Does it still satisfy the specification?

How is it changing relative to the baseline?
</code></pre>
<p>Those are different questions. Both are useful.</p>
<h2 id="heading-observe-the-same-constraints-over-time">Observe the Same Constraints Over Time</h2>
<p>Suppose you collect evidence every hour:</p>
<pre><code class="language-typescript">type TimedEvidence = {
  timestamp: string;
  evidence: DeploymentEvidence;
};
</code></pre>
<p>For example:</p>
<pre><code class="language-typescript">const observations: TimedEvidence[] = [
  {
    timestamp: "10:00",
    evidence: {
      version: "v42",
      replicas: 3,
      errorRate: 0.004,
      p95LatencyMs: 280,
    },
  },
  {
    timestamp: "12:00",
    evidence: {
      version: "v42",
      replicas: 3,
      errorRate: 0.006,
      p95LatencyMs: 320,
    },
  },
  {
    timestamp: "14:00",
    evidence: {
      version: "v42",
      replicas: 3,
      errorRate: 0.008,
      p95LatencyMs: 370,
    },
  },
];
</code></pre>
<p>Each observation can be evaluated against the same specification.</p>
<p>This gives you a conformance sequence:</p>
<pre><code class="language-text">10:00  PASS
12:00  PASS
14:00  PASS
</code></pre>
<p>At first glance, everything looks healthy. But a closer look reveals that the evidence is moving in one direction.</p>
<p>That's where drift detection becomes more interesting than ordinary threshold monitoring.</p>
<h2 id="heading-model-drift-explicitly">Model Drift Explicitly</h2>
<p>A minimal drift result might look like this:</p>
<pre><code class="language-typescript">type DriftStatus =
  | "STABLE"
  | "DEGRADING"
  | "VIOLATED"
  | "UNKNOWN";
</code></pre>
<p>This gives the evaluator four explicit states. <code>STABLE</code> means the observed behavior remains within the expected range without meaningful deterioration. <code>DEGRADING</code> means the system is still within its allowed boundary but is moving in an unfavorable direction. <code>VIOLATED</code> means the current evidence has crossed the specification's limit. <code>UNKNOWN</code> means there's not enough evidence to classify the state safely.</p>
<p>Then:</p>
<pre><code class="language-typescript">type DriftResult = {
  constraint: string;
  status: DriftStatus;
  baseline?: number;
  current?: number;
  limit?: number;
};
</code></pre>
<p><code>DriftResult</code> keeps the classification together with the values that explain it. <code>baseline</code> is the known-good reference, <code>current</code> is the latest observation, and <code>limit</code> is the boundary defined by the specification. Those fields are optional because some constraints may not be numeric or some evidence may be missing.</p>
<p>For latency:</p>
<pre><code class="language-text">baseline = 280
current = 370
limit = 400
</code></pre>
<p>This example says latency started from a known-good value of 280 ms, is now 370 ms, and must remain at or below 400 ms. The constraint hasn't failed, but the current value is much closer to the boundary.</p>
<p>That may be worth labeling:</p>
<pre><code class="language-text">DEGRADING
</code></pre>
<p>A drift model therefore doesn't replace conformance. It adds another dimension.</p>
<p>You can have:</p>
<pre><code class="language-text">conformance = PASS
drift = DEGRADING
</code></pre>
<p>That combination is often more actionable than either signal alone.</p>
<h2 id="heading-temporary-variation-isnt-always-drift">Temporary Variation Isn't Always Drift</h2>
<p>Production systems are noisy.</p>
<p>One slow request doesn't mean the system is drifting. One missing sample doesn't mean the service is unhealthy. And one temporary spike doesn't necessarily justify intervention.</p>
<p>Suppose latency samples are:</p>
<pre><code class="language-text">280
290
420
285
295
</code></pre>
<p>The <code>420</code> sample crosses the limit. But the system immediately returns to normal.</p>
<p>Compare that with:</p>
<pre><code class="language-text">280
310
340
370
395
</code></pre>
<p>The second sequence may never cross the limit. But it shows persistent degradation.</p>
<p>That's why drift shouldn't be defined as:</p>
<pre><code class="language-text">current != baseline
</code></pre>
<p>or even:</p>
<pre><code class="language-text">one sample crossed threshold
</code></pre>
<p>Drift is more useful when it accounts for persistence and direction.</p>
<h2 id="heading-use-windows-instead-of-single-samples">Use Windows Instead of Single Samples</h2>
<p>A better approach is to evaluate observations over a window.</p>
<p>For example:</p>
<pre><code class="language-typescript">function average(
  values: number[]
): number {
  return (
    values.reduce(
      (sum, value) =&gt; sum + value,
      0
    ) / values.length
  );
}
</code></pre>
<p>Now suppose you evaluate the last five latency observations:</p>
<pre><code class="language-typescript">const latencyWindow = [
  300,
  320,
  340,
  360,
  380,
];

const currentAverage =
  average(latencyWindow);
</code></pre>
<p>This smooths temporary noise.</p>
<p>You can compare that window with an earlier window:</p>
<pre><code class="language-typescript">const baselineWindow = [
  270,
  275,
  280,
  285,
  290,
];

const baselineAverage =
  average(baselineWindow);
</code></pre>
<p>The earlier window gives you a short historical reference for typical behavior. The later window represents the more recent operating state. Comparing the two averages helps you see whether the whole signal has shifted, rather than reacting to one unusual sample.</p>
<p>Then calculate the change:</p>
<pre><code class="language-typescript">const delta =
  currentAverage -
  baselineAverage;
</code></pre>
<p>If <code>baselineAverage</code> is 280 ms and <code>currentAverage</code> is 340 ms, the delta is <code>+60 ms</code>. That doesn't mean the latency constraint has failed, but it does show that recent latency is materially worse than the earlier window.</p>
<p>This doesn't prove a problem exists, but it gives you a more stable signal.</p>
<p>The exact window size should come from the operational context.</p>
<p>A five-minute window may make sense for latency. A one-day window may make more sense for storage growth.</p>
<h2 id="heading-distinguish-drift-from-missing-evidence">Distinguish Drift from Missing Evidence</h2>
<p>This follows the same principle as conformance.</p>
<p>Suppose latency evidence disappears.</p>
<p>That's not the same as:</p>
<pre><code class="language-text">latency is stable
</code></pre>
<p>and it's not the same as:</p>
<pre><code class="language-text">latency violated the threshold
</code></pre>
<p>It is:</p>
<pre><code class="language-text">UNKNOWN
</code></pre>
<p>For example:</p>
<pre><code class="language-typescript">function evaluateLatencyDrift(
  baseline: number | undefined,
  current: number | undefined,
  limit: number
): DriftStatus {
  if (
    baseline === undefined ||
    current === undefined
  ) {
    return "UNKNOWN";
  }

  if (current &gt; limit) {
    return "VIOLATED";
  }

  if (current &gt; baseline) {
    return "DEGRADING";
  }

  return "STABLE";
}
</code></pre>
<p>The function evaluates the cases in a deliberate order. First, if either the baseline or the current observation is missing, it returns <code>UNKNOWN</code> because there's not enough evidence to make a safe comparison. If evidence exists and the current value is already above the allowed limit, it returns <code>VIOLATED</code>. If the value is still below the limit but worse than the baseline, it returns <code>DEGRADING</code>. Only when none of those conditions apply does it return <code>STABLE</code>.</p>
<p>This is still simplistic because any increase over the baseline becomes <code>DEGRADING</code>, even if the change is tiny or temporary. In a real system, you would usually combine this with tolerances, windows, persistence, or trend rules. But it preserves an important rule: absence of evidence isn't evidence of stability.</p>
<p>That becomes especially important when the observability system itself is degraded.</p>
<h2 id="heading-add-severity-and-persistence">Add Severity and Persistence</h2>
<p>Drift becomes more useful when you know how important the constraint is and how long the drift has persisted.</p>
<p>For example:</p>
<pre><code class="language-typescript">type DriftSeverity =
  | "INFO"
  | "WARNING"
  | "CRITICAL";

type DriftObservation = {
  constraint: string;
  status: DriftStatus;
  severity: DriftSeverity;
  consecutiveWindows: number;
};
</code></pre>
<p>Here, <code>severity</code> describes the operational impact of the affected constraint, while <code>consecutiveWindows</code> records how many observation windows have produced the same drift condition in a row. Keeping both matters because one brief warning is different from a warning that has persisted for eight consecutive windows, and a critical replica violation may deserve action even if it has only appeared twice.</p>
<p>Now you can distinguish:</p>
<pre><code class="language-text">latency
DEGRADING
WARNING
1 window
</code></pre>
<p>from:</p>
<pre><code class="language-text">latency
DEGRADING
WARNING
8 windows
</code></pre>
<p>and:</p>
<pre><code class="language-text">replicas
VIOLATED
CRITICAL
2 windows
</code></pre>
<p>The second signal is more persistent, but the third is more severe.</p>
<p>That helps determine what response is appropriate.</p>
<h2 id="heading-detect-directional-drift-before-a-threshold-fails">Detect Directional Drift Before a Threshold Fails</h2>
<p>One of the most useful aspects of drift detection is recognizing directional movement.</p>
<p>Suppose the error-rate limit is:</p>
<pre><code class="language-text">1%
</code></pre>
<p>and observations are:</p>
<pre><code class="language-text">0.2%
0.4%
0.6%
0.8%
</code></pre>
<p>Nothing has failed yet, but the trend is clear.</p>
<p>A simple slope can capture that direction.</p>
<p>For example:</p>
<pre><code class="language-typescript">function slope(
  values: number[]
): number {
  if (values.length &lt; 2) {
    return 0;
  }

  return (
    values[values.length - 1] -
    values[0]
  ) / (values.length - 1);
}
</code></pre>
<p>This helper looks at the first and last values in the series and divides their difference by the number of intervals between them. It's a deliberately simple trend indicator: a positive result means the metric increased across the window, a negative result means it decreased, and a value near zero means it stayed roughly flat.</p>
<p>Then:</p>
<pre><code class="language-typescript">const errorRates = [
  0.002,
  0.004,
  0.006,
  0.008,
];

const trend =
  slope(errorRates);
</code></pre>
<p>Here, the error rate rises from <code>0.002</code> to <code>0.008</code>, so <code>trend</code> is positive. The code isn't trying to predict the future or perform full statistical analysis. It's only making the direction of change explicit so the system can notice sustained movement toward the <code>1%</code> boundary before the boundary is actually crossed.</p>
<p>A positive trend doesn't automatically mean failure, but it tells you the system is moving toward the boundary.</p>
<p>This is where drift detection differs from basic alerting.</p>
<p>Basic threshold alert:</p>
<pre><code class="language-text">tell me when error rate &gt; 1%
</code></pre>
<p>Drift detection:</p>
<pre><code class="language-text">tell me when error rate is persistently moving toward 1%
</code></pre>
<p>The second can give you time to investigate before the system becomes non-conformant.</p>
<h2 id="heading-preserve-the-evidence-behind-drift">Preserve the Evidence Behind Drift</h2>
<p>A drift result should be explainable.</p>
<p>For example:</p>
<pre><code class="language-json">{
  "constraint": "error-rate",
  "status": "DEGRADING",
  "baseline": 0.004,
  "current": 0.008,
  "limit": 0.01,
  "window": "10m",
  "consecutiveWindows": 4,
  "observedAt": "2026-10-13T18:00:00Z"
}
</code></pre>
<p>This is better than:</p>
<pre><code class="language-text">drift detected
</code></pre>
<p>because someone can answer:</p>
<pre><code class="language-text">what changed?
compared with what?
how close are we to the limit?
how long has this been happening?
when was it observed?
</code></pre>
<p>That matters for debugging, as well as for auditability.</p>
<h2 id="heading-a-reusable-drift-evaluator">A Reusable Drift Evaluator</h2>
<p>We can generalize the model.</p>
<p>First define a drift rule:</p>
<pre><code class="language-typescript">type DriftRule&lt;T&gt; = {
  name: string;
  severity: DriftSeverity;
  evaluate(
    baseline: T,
    current: T
  ): DriftStatus;
};
</code></pre>
<p>Then define a drift specification:</p>
<pre><code class="language-typescript">type DriftSpec&lt;T&gt; = {
  name: string;
  rules: DriftRule&lt;T&gt;[];
};
</code></pre>
<p>And an evaluator:</p>
<pre><code class="language-typescript">type DriftRuleResult = {
  name: string;
  status: DriftStatus;
  severity: DriftSeverity;
};

function evaluateDrift&lt;T&gt;(
  spec: DriftSpec&lt;T&gt;,
  baseline: T,
  current: T
): DriftRuleResult[] {
  return spec.rules.map(
    (rule) =&gt; ({
      name: rule.name,
      status:
        rule.evaluate(
          baseline,
          current
        ),
      severity:
        rule.severity,
    })
  );
}
</code></pre>
<p>The evaluator doesn't need to understand latency, error rate, replicas, or certificates.</p>
<p>Each rule owns its own comparison logic. That makes the engine reusable.</p>
<h2 id="heading-a-small-end-to-end-example">A Small End-to-End Example</h2>
<p>Let’s create a drift specification for a deployment.</p>
<p>Start with evidence:</p>
<pre><code class="language-typescript">type DeploymentEvidence = {
  replicas?: number;
  errorRate?: number;
  p95LatencyMs?: number;
};
</code></pre>
<p>Now define rules:</p>
<pre><code class="language-typescript">const driftRules:
  DriftRule&lt;DeploymentEvidence&gt;[] = [
    {
      name: "replicas",
      severity: "CRITICAL",
      evaluate: (
        baseline,
        current
      ) =&gt; {
        if (
          baseline.replicas === undefined ||
          current.replicas === undefined
        ) {
          return "UNKNOWN";
        }

        if (current.replicas &lt; 3) {
          return "VIOLATED";
        }

        if (
          current.replicas &lt;
          baseline.replicas
        ) {
          return "DEGRADING";
        }

        return "STABLE";
      },
    },
    {
      name: "error-rate",
      severity: "CRITICAL",
      evaluate: (
        baseline,
        current
      ) =&gt; {
        if (
          baseline.errorRate === undefined ||
          current.errorRate === undefined
        ) {
          return "UNKNOWN";
        }

        if (current.errorRate &gt; 0.01) {
          return "VIOLATED";
        }

        if (
          current.errorRate &gt;
          baseline.errorRate * 1.5
        ) {
          return "DEGRADING";
        }

        return "STABLE";
      },
    },
    {
      name: "latency",
      severity: "WARNING",
      evaluate: (
        baseline,
        current
      ) =&gt; {
        if (
          baseline.p95LatencyMs === undefined ||
          current.p95LatencyMs === undefined
        ) {
          return "UNKNOWN";
        }

        if (
          current.p95LatencyMs &gt; 400
        ) {
          return "VIOLATED";
        }

        if (
          current.p95LatencyMs &gt;
          baseline.p95LatencyMs * 1.25
        ) {
          return "DEGRADING";
        }

        return "STABLE";
      },
    },
  ];
</code></pre>
<p>Create the drift specification:</p>
<pre><code class="language-typescript">const driftSpec:
  DriftSpec&lt;DeploymentEvidence&gt; = {
    name: "orders-runtime-drift",
    rules: driftRules,
  };
</code></pre>
<p>Now define baseline and current evidence:</p>
<pre><code class="language-typescript">const baseline:
  DeploymentEvidence = {
    replicas: 3,
    errorRate: 0.004,
    p95LatencyMs: 280,
  };

const current:
  DeploymentEvidence = {
    replicas: 3,
    errorRate: 0.007,
    p95LatencyMs: 360,
  };
</code></pre>
<p>Evaluate:</p>
<pre><code class="language-typescript">const drift =
  evaluateDrift(
    driftSpec,
    baseline,
    current
  );
</code></pre>
<p>Possible output:</p>
<pre><code class="language-text">replicas     STABLE      CRITICAL
error-rate   DEGRADING   CRITICAL
latency      DEGRADING   WARNING
</code></pre>
<p>The system may still be conformant, but it's no longer operationally stable relative to its baseline.</p>
<p>That's valuable information.</p>
<h2 id="heading-how-drift-detection-applies-to-multiple-executors">How Drift Detection Applies to Multiple Executors</h2>
<p>Suppose two executors both successfully deployed the same service.</p>
<p>Immediately after deployment:</p>
<pre><code class="language-text">Executor A → conformant
Executor B → conformant
</code></pre>
<p>That doesn't mean their long-term operational behavior is identical.</p>
<p>Over time:</p>
<pre><code class="language-text">Executor A
error rate stable
latency stable
replicas stable

Executor B
error rate rising
latency rising
replicas stable
</code></pre>
<p>Both executors satisfied the initial specification, but their downstream operational behavior is different.</p>
<p>Drift gives you another comparison dimension.</p>
<p>You can ask:</p>
<pre><code class="language-text">Which executor produces more stable outcomes?
Which one drifts more often?
Which one produces more unknown evidence?
Which one needs recovery more frequently?
</code></pre>
<p>This is especially useful when executor choice is dynamic.</p>
<h2 id="heading-how-drift-detection-applies-to-ai-agents">How Drift Detection Applies to AI Agents</h2>
<p>AI agents introduce another kind of variability.</p>
<p>An agent may produce different plans for the same objective.</p>
<p>For example:</p>
<pre><code class="language-text">Run 1:
scale
deploy
observe

Run 2:
deploy canary
observe
expand

Run 3:
create parallel environment
switch traffic
</code></pre>
<p>Each plan may be valid, and each may initially conform.</p>
<p>But the long-term outcomes can differ.</p>
<p>One plan may produce a stable system, while another may create a system that slowly moves toward a boundary.</p>
<p>This gives us a useful separation:</p>
<pre><code class="language-text">agent chooses the execution path
specification defines acceptable boundaries
evidence describes the observed state
conformance evaluates the current state
drift evaluates how that state changes over time
</code></pre>
<p>That allows agent autonomy without making the agent the only judge of its own outcomes.</p>
<h2 id="heading-when-drift-should-trigger-action">When Drift Should Trigger Action</h2>
<p>Not every drift signal should trigger automation.</p>
<p>A useful response model might be:</p>
<pre><code class="language-text">STABLE
→ no action

DEGRADING + low severity
→ observe

DEGRADING + persistent
→ investigate

VIOLATED + warning
→ alert

VIOLATED + critical
→ rollback / remediate / escalate

UNKNOWN
→ collect evidence / block risky automation
</code></pre>
<p>The exact policy depends on the operation.</p>
<p>For example, automatically restarting a stateless service may be acceptable. But automatically rolling back a database migration may not be.</p>
<p>Drift detection should therefore produce evidence and classification. Policy should decide what action is permitted. That keeps observation, evaluation, decision, and execution separate.</p>
<h2 id="heading-what-operational-drift-cant-tell-you">What Operational Drift Can't Tell You</h2>
<p>Operational drift doesn't automatically tell you why the system changed.</p>
<p>Suppose latency rises from:</p>
<pre><code class="language-text">280 ms
</code></pre>
<p>to:</p>
<pre><code class="language-text">390 ms
</code></pre>
<p>Drift detection can tell you:</p>
<pre><code class="language-text">latency is degrading
</code></pre>
<p>It can't automatically prove the cause.</p>
<p>The cause might be traffic growth, database contention, a downstream dependency, network degradation, a code regression, or resource exhaustion.</p>
<p>Drift is therefore a detection mechanism. It's not root-cause analysis.</p>
<p>The baseline can also be wrong.</p>
<p>If the baseline was captured during an unusually good period, normal behavior may look like degradation.</p>
<p>So drift quality depends on good specifications, good evidence, good baselines, appropriate windows, and correct rules.</p>
<h2 id="heading-a-practical-drift-detection-workflow">A Practical Drift Detection Workflow</h2>
<p>If you want to introduce operational drift detection into an existing system, I would use this sequence.</p>
<h3 id="heading-1-start-with-a-conformant-operation">1. Start with a Conformant Operation</h3>
<p>Pick an operation where you already know the system satisfies the specification.</p>
<h3 id="heading-2-preserve-the-baseline-evidence">2. Preserve the Baseline Evidence</h3>
<p>Store the evidence associated with that known-good state.</p>
<h3 id="heading-3-identify-drift-sensitive-constraints">3. Identify Drift-Sensitive Constraints</h3>
<p>Not every constraint needs trend analysis.</p>
<p>Start with things like:</p>
<pre><code class="language-text">latency
error rate
capacity
queue depth
availability
certificate age
backup freshness
</code></pre>
<h3 id="heading-4-define-observation-windows">4. Define Observation Windows</h3>
<p>Choose windows based on operational behavior.</p>
<h3 id="heading-5-define-stable-degrading-violated-and-unknown">5. Define Stable, Degrading, Violated, and Unknown</h3>
<p>Make the states explicit.</p>
<h3 id="heading-6-add-severity">6. Add Severity</h3>
<p>Separate informational drift from critical drift.</p>
<h3 id="heading-7-add-persistence">7. Add Persistence</h3>
<p>Avoid reacting to one noisy sample.</p>
<h3 id="heading-8-preserve-evidence">8. Preserve Evidence</h3>
<p>Store baseline, current state, thresholds, windows, and timestamps.</p>
<h3 id="heading-9-separate-detection-from-response">9. Separate Detection from Response</h3>
<p>Don't make every drift signal trigger an automatic action.</p>
<h3 id="heading-10-revisit-the-model">10. Revisit the Model</h3>
<p>If the system produces constant false positives, improve the rule. If it misses real degradation, improve the evidence or the window.</p>
<p>Don't simply widen thresholds until everything looks stable.</p>
<h2 id="heading-from-point-in-time-conformance-to-continuous-verification">From Point-in-Time Conformance to Continuous Verification</h2>
<p>Conformance gives us this:</p>
<pre><code class="language-text">Specification
+
Evidence
↓
Evaluation
↓
PASS / FAIL / UNKNOWN
</code></pre>
<p>Drift adds time:</p>
<pre><code class="language-text">Specification
+
Evidence at T0
+
Evidence at T1
+
Evidence at T2
↓
Drift Evaluation
↓
STABLE / DEGRADING / VIOLATED / UNKNOWN
</code></pre>
<p>That changes the operational model.</p>
<p>We're no longer asking only:</p>
<blockquote>
<p>Did the operation succeed?</p>
</blockquote>
<p>or:</p>
<blockquote>
<p>Is the system conformant now?</p>
</blockquote>
<p>We can also ask:</p>
<blockquote>
<p>Is the system moving away from the intended state?</p>
</blockquote>
<p>That's a much stronger foundation for operational control, especially when systems become more autonomous.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Operational conformance tells us whether observed evidence satisfies the specification at a point in time.</p>
<p>Operational drift tells us whether that relationship is changing.</p>
<p>A useful drift model should preserve:</p>
<pre><code class="language-text">baseline evidence
current evidence
constraint boundaries
observation windows
direction of change
severity
persistence
unknown states
timestamps
</code></pre>
<p>That gives us a way to distinguish temporary noise, persistent degradation, actual violation, and missing evidence.</p>
<p>The executor doesn't need to know any of this. The executor performs the work. The specification defines the intended boundaries.</p>
<p>Evidence tells us what's happening. Conformance tells us whether the current state is acceptable. Drift tells us whether the system is moving away from that acceptable state.</p>
<p>At this point, the pieces are starting to form a larger operational model:</p>
<pre><code class="language-text">Intent
↓
Specification
↓
Executor
↓
Evidence
↓
Conformance
↓
Drift
↓
Governed response
</code></pre>
<p>The next step is to put those pieces together explicitly.</p>
<p>That's where I want to define <strong>Specification-Driven Operations</strong>, or SDOps, as a complete operating model.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Measure Conformance Between an Operational Specification and Its Execution ]]>
                </title>
                <description>
                    <![CDATA[ In my previous article, I explored executor independence: the idea that different execution mechanisms should be able to satisfy the same operational specification. By executor independence, I mean th ]]>
                </description>
                <link>https://www.freecodecamp.org/news/measure-conformance-operational-specification-execution/</link>
                <guid isPermaLink="false">6acb5b01b19fbe5ad15d6949</guid>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ observability ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ sdops ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hugo Teijiz ]]>
                </dc:creator>
                <pubDate>Sun, 11 Oct 2026 09:46:41 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/55c7ea87-fbb5-4b3f-ae4e-1d1616362579.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In <a href="https://www.freecodecamp.org/news/separate-operational-intent-from-executor/">my previous article</a>, I explored executor independence: the idea that different execution mechanisms should be able to satisfy the same operational specification.</p>
<p>By <strong>executor independence</strong>, I mean that the operational specification remains stable even when the mechanism performing the work changes.</p>
<p>That gives us a useful architecture:</p>
<pre><code class="language-text">Specification
      ↓
Executor
      ↓
Evidence
</code></pre>
<p>But it leaves an important question unanswered: how do we decide whether the evidence produced by an execution actually satisfies the specification?</p>
<p>That's a conformance problem.</p>
<p>A deployment can finish successfully from the executor's point of view and still violate the operational contract.</p>
<p>A remediation agent can complete every action it planned and still leave the system outside acceptable limits.</p>
<p>A restore operation can finish without errors and still fail to restore the required data.</p>
<p>This is why a generic status like:</p>
<pre><code class="language-text">success = true
</code></pre>
<p>isn't enough.</p>
<p>We need a way to compare what should have happened with what was actually observed and produce a result that's explicit, explainable, and useful.</p>
<p>In this article, I'll show you how to model operational conformance, evaluate individual constraints, distinguish pass, fail, and unknown outcomes, handle tolerances, work with missing evidence, assign severity, calculate a useful conformance summary, and avoid turning conformance into a misleading score.</p>
<p>The examples use TypeScript and a deployment scenario, but the same approach applies to infrastructure changes, incident response, backup restoration, data pipelines, and AI-operated systems.</p>
<p>The central idea is this: conformance isn't whether the executor says it succeeded. Conformance is whether observed evidence satisfies the operational specification.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You should be comfortable with:</p>
<ul>
<li><p>key software architecture concepts</p>
</li>
<li><p>TypeScript or a similar language</p>
</li>
<li><p>basic testing</p>
</li>
<li><p>observability</p>
</li>
<li><p>CI/CD concepts</p>
</li>
<li><p>operational specifications</p>
</li>
<li><p>executor independence</p>
</li>
</ul>
<p>You don't need a particular deployment platform, and the examples are deliberately small so the evaluation model stays visible.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-conformance-means-in-this-context">What Conformance Means in This Context</a></p>
</li>
<li><p><a href="#heading-start-with-a-specification-and-evidence">Start with a Specification and Evidence</a></p>
</li>
<li><p><a href="#heading-evaluate-constraints-individually">Evaluate Constraints Individually</a></p>
</li>
<li><p><a href="#heading-dont-reduce-everything-to-pass-or-fail">Don't Reduce Everything to Pass or Fail</a></p>
</li>
<li><p><a href="#heading-treat-missing-evidence-as-unknown">Treat Missing Evidence as Unknown</a></p>
</li>
<li><p><a href="#heading-use-tolerances-where-exact-comparison-is-wrong">Use Tolerances Where Exact Comparison Is Wrong</a></p>
</li>
<li><p><a href="#heading-add-severity-to-constraints">Add Severity to Constraints</a></p>
</li>
<li><p><a href="#heading-separate-hard-constraints-from-advisory-constraints">Separate Hard Constraints from Advisory Constraints</a></p>
</li>
<li><p><a href="#heading-build-a-reusable-conformance-evaluator">Build a Reusable Conformance Evaluator</a></p>
</li>
<li><p><a href="#heading-calculate-a-conformance-summary-carefully">Calculate a Conformance Summary Carefully</a></p>
</li>
<li><p><a href="#heading-why-a-single-score-can-be-dangerous">Why a Single Score Can Be Dangerous</a></p>
</li>
<li><p><a href="#heading-preserve-the-evidence-behind-the-verdict">Preserve the Evidence Behind the Verdict</a></p>
</li>
<li><p><a href="#heading-conformance-should-be-reproducible">Conformance Should Be Reproducible</a></p>
</li>
<li><p><a href="#heading-how-this-applies-to-multiple-executors">How This Applies to Multiple Executors</a></p>
</li>
<li><p><a href="#heading-how-this-applies-to-ai-agents">How This Applies to AI Agents</a></p>
</li>
<li><p><a href="#heading-a-small-end-to-end-example">A Small End-to-End Example</a></p>
</li>
<li><p><a href="#heading-a-practical-conformance-workflow">A Practical Conformance Workflow</a></p>
</li>
<li><p><a href="#heading-what-conformance-cant-prove">What Conformance Can't Prove</a></p>
</li>
<li><p><a href="#heading-from-execution-success-to-verifiable-operations">From Execution Success to Verifiable Operations</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-conformance-means-in-this-context">What Conformance Means in This Context</h2>
<p>In ordinary language, conformance means complying with a rule, standard, or requirement.</p>
<p>Here, I use the term more specifically:</p>
<blockquote>
<p>Operational conformance is the evaluation of whether, and where, observed execution evidence satisfies the requirements defined by an operational specification.</p>
</blockquote>
<p>In this context, <strong>evidence</strong> is the set of observable facts collected from the system that can be compared against the specification. It's not a success verdict. It's the data used to produce that verdict.</p>
<p>That definition has three parts.</p>
<h3 id="heading-1-the-specification">1. The Specification</h3>
<p>This defines what should be true.</p>
<p>For example:</p>
<pre><code class="language-text">version = v42
available replicas &gt;= 3
error rate &lt;= 1%
p95 latency &lt;= 400 ms
</code></pre>
<h3 id="heading-2-the-evidence">2. The Evidence</h3>
<p>This describes what was actually observed.</p>
<p>For example:</p>
<pre><code class="language-text">version = v42
available replicas = 3
error rate = 0.4%
p95 latency = 280 ms
</code></pre>
<h3 id="heading-3-the-evaluation">3. The Evaluation</h3>
<p>This compares the evidence with the specification.</p>
<p>For example:</p>
<pre><code class="language-text">version             PASS
available replicas  PASS
error rate           PASS
p95 latency          PASS
</code></pre>
<p>Conformance is therefore not an executor property. It's the result of evaluating evidence against a specification.</p>
<p>Conceptually:</p>
<pre><code class="language-text">Specification
      +
Evidence
      ↓
Conformance Evaluation
      ↓
Result
</code></pre>
<h2 id="heading-start-with-a-specification-and-evidence">Start with a Specification and Evidence</h2>
<p>Suppose we have this deployment specification:</p>
<pre><code class="language-typescript">type DeploymentSpec = {
  version: string;
  minReplicas: number;
  maxErrorRate: number;
  maxP95LatencyMs: number;
};

const spec: DeploymentSpec = {
  version: "v42",
  minReplicas: 3,
  maxErrorRate: 0.01,
  maxP95LatencyMs: 400,
};
</code></pre>
<p>Now define the evidence:</p>
<pre><code class="language-typescript">type DeploymentEvidence = {
  version: string;
  replicas: number;
  errorRate: number;
  p95LatencyMs: number;
};

const evidence: DeploymentEvidence = {
  version: "v42",
  replicas: 3,
  errorRate: 0.004,
  p95LatencyMs: 280,
};
</code></pre>
<p>The simplest possible evaluator might return a boolean:</p>
<pre><code class="language-typescript">function conforms(
  spec: DeploymentSpec,
  evidence: DeploymentEvidence
): boolean {
  return (
    evidence.version === spec.version &amp;&amp;
    evidence.replicas &gt;= spec.minReplicas &amp;&amp;
    evidence.errorRate &lt;= spec.maxErrorRate &amp;&amp;
    evidence.p95LatencyMs &lt;= spec.maxP95LatencyMs
  );
}
</code></pre>
<p>This works, but it has a problem.</p>
<p>If the result is:</p>
<pre><code class="language-text">false
</code></pre>
<p>you don't know why. That's not enough for operational work.</p>
<h2 id="heading-evaluate-constraints-individually">Evaluate Constraints Individually</h2>
<p>Instead of one boolean, evaluate each constraint separately.</p>
<pre><code class="language-typescript">type ConstraintResult = {
  name: string;
  passed: boolean;
};

function evaluateDeployment(
  spec: DeploymentSpec,
  evidence: DeploymentEvidence
): ConstraintResult[] {
  return [
    {
      name: "version",
      passed:
        evidence.version === spec.version,
    },
    {
      name: "replicas",
      passed:
        evidence.replicas &gt;= spec.minReplicas,
    },
    {
      name: "error-rate",
      passed:
        evidence.errorRate &lt;= spec.maxErrorRate,
    },
    {
      name: "latency",
      passed:
        evidence.p95LatencyMs &lt;= spec.maxP95LatencyMs,
    },
  ];
}
</code></pre>
<p><code>ConstraintResult</code> gives each check a name and a boolean result. The <code>evaluateDeployment()</code> function compares one observed value at a time against the corresponding rule in the specification. It checks whether the deployed version matches exactly, whether the replica count is high enough, and whether error rate and latency stay below their limits.</p>
<p>Instead of collapsing all of those checks into one <code>true</code> or <code>false</code>, the function returns one result per constraint. That lets the caller see exactly which requirement passed or failed.</p>
<p>Now the output can be:</p>
<pre><code class="language-text">version       PASS
replicas      PASS
error-rate    PASS
latency       PASS
</code></pre>
<p>or:</p>
<pre><code class="language-text">version       PASS
replicas      FAIL
error-rate    PASS
latency       FAIL
</code></pre>
<p>This is much more useful. A failed operation becomes explainable.</p>
<h2 id="heading-dont-reduce-everything-to-pass-or-fail">Don't Reduce Everything to Pass or Fail</h2>
<p>Binary evaluation is still sometimes too simple.</p>
<p>Suppose the specification requires:</p>
<pre><code class="language-text">error rate &lt;= 1%
</code></pre>
<p>but your observability system is unavailable.</p>
<p>What should the result be?</p>
<p><code>PASS</code> is clearly wrong.</p>
<p><code>FAIL</code> may also be misleading, because that mixes two different situations:</p>
<pre><code class="language-text">the system violated the constraint
</code></pre>
<p>and:</p>
<pre><code class="language-text">we do not know whether the system violated the constraint
</code></pre>
<p>A better model uses at least three states:</p>
<pre><code class="language-text">PASS
FAIL
UNKNOWN
</code></pre>
<p>For example:</p>
<pre><code class="language-typescript">type ConstraintStatus =
  | "PASS"
  | "FAIL"
  | "UNKNOWN";
</code></pre>
<p>Unknown evidence should never silently become success.</p>
<h2 id="heading-treat-missing-evidence-as-unknown">Treat Missing Evidence as Unknown</h2>
<p>Suppose evidence becomes partial. In other words, the system may return some observations but not all of them. A deployment record might tell us which version is running and how many replicas are available, while the error-rate metric is temporarily unavailable.</p>
<p>We can represent that by making each evidence field optional:</p>
<pre><code class="language-typescript">type DeploymentEvidence = {
  version?: string;
  replicas?: number;
  errorRate?: number;
  p95LatencyMs?: number;
};
</code></pre>
<p>The evaluator then needs to distinguish between a real violation and a missing observation:</p>
<pre><code class="language-typescript">function evaluateErrorRate(
  spec: DeploymentSpec,
  evidence: DeploymentEvidence
): ConstraintStatus {
  if (
    evidence.errorRate === undefined
  ) {
    return "UNKNOWN";
  }

  return (
    evidence.errorRate &lt;= spec.maxErrorRate
  )
    ? "PASS"
    : "FAIL";
}
</code></pre>
<p>The first branch asks whether an error-rate value was observed at all. If it was not, the evaluator returns <code>UNKNOWN</code> instead of guessing. If the value exists, the function compares it with the maximum allowed error rate and returns <code>PASS</code> or <code>FAIL</code>.</p>
<p>This produces:</p>
<pre><code class="language-text">errorRate = 0.004 → PASS
errorRate = 0.03  → FAIL
errorRate missing → UNKNOWN
</code></pre>
<p>That distinction matters. <strong>Missing telemetry</strong>, here, means that a metric or operational signal we expected to observe wasn't available. For example, because the monitoring system didn't report an error-rate value for that execution window. If missing telemetry is treated as success, conformance becomes dangerously optimistic.</p>
<h2 id="heading-use-tolerances-where-exact-comparison-is-wrong">Use Tolerances Where Exact Comparison Is Wrong</h2>
<p>Some constraints should use exact equality.</p>
<p>For example:</p>
<pre><code class="language-text">deployed version must equal v42
</code></pre>
<p>Others should not.</p>
<p>Numerical systems may need tolerances because measured or computed values can differ by tiny amounts even when they're operationally equivalent. Floating-point arithmetic, sampling, rounding, and measurement noise can all produce values that shouldn't fail a constraint just because they're not exactly equal.</p>
<pre><code class="language-typescript">function withinTolerance(
  observed: number,
  expected: number,
  tolerance: number
): boolean {
  return (
    Math.abs(observed - expected)
    &lt;= tolerance
  );
}
</code></pre>
<p>Then:</p>
<pre><code class="language-typescript">withinTolerance(
  1.9999999,
  2,
  0.0001
);
</code></pre>
<p>returns <code>true</code>.</p>
<p>The function computes the absolute difference between the observed and expected values, then checks whether that difference stays within the allowed tolerance. This is useful when tiny numeric differences are expected and exact equality would create false failures.</p>
<p>But tolerances should come from the operational domain. They shouldn't be introduced simply to make failures disappear.</p>
<h2 id="heading-add-severity-to-constraints">Add Severity to Constraints</h2>
<p>Not every constraint has the same operational importance.</p>
<p>Suppose two constraints fail:</p>
<pre><code class="language-text">p95 latency = 405 ms
replicas = 0
</code></pre>
<p>Both are failures, but they're not equivalent.</p>
<p>A small latency miss may deserve attention without requiring an immediate rollback. Zero available replicas, on the other hand, may mean the service is unavailable and should trigger urgent recovery. Severity lets the evaluation preserve that operational difference instead of treating every failure as equally important.</p>
<pre><code class="language-typescript">type Severity =
  | "INFO"
  | "WARNING"
  | "CRITICAL";

type ConstraintResult = {
  name: string;
  status: ConstraintStatus;
  severity: Severity;
};
</code></pre>
<p>Here, <code>status</code> tells us whether the constraint passed, failed, or couldn't be evaluated. <code>severity</code> tells us how important that result is operationally.</p>
<p>The two dimensions are separate: a constraint can fail with <code>WARNING</code> severity or fail with <code>CRITICAL</code> severity.</p>
<p>Then:</p>
<pre><code class="language-text">latency
FAIL
WARNING

replicas
FAIL
CRITICAL
</code></pre>
<p>This matters when deciding whether to continue, pause, rollback, or escalate.</p>
<h2 id="heading-separate-hard-constraints-from-advisory-constraints">Separate Hard Constraints from Advisory Constraints</h2>
<p>Some requirements define whether the operation is acceptable at all. Others are useful signals but don't necessarily block execution.</p>
<p>This is different from severity. Severity describes the impact of a result, while constraint mode describes whether that rule participates in the final conformance decision. A <code>REQUIRED</code> constraint must pass for the operation to be conformant. An <code>ADVISORY</code> constraint can fail and still leave the operation conformant overall, although the failure should still be reported.</p>
<pre><code class="language-typescript">type ConstraintMode =
  | "REQUIRED"
  | "ADVISORY";
</code></pre>
<p>A result can now include:</p>
<pre><code class="language-typescript">type ConstraintResult = {
  name: string;
  status: ConstraintStatus;
  severity: Severity;
  mode: ConstraintMode;
  expected?: unknown;
  observed?: unknown;
};
</code></pre>
<p>For example:</p>
<pre><code class="language-text">version
REQUIRED
PASS

latency-target
ADVISORY
FAIL
</code></pre>
<p>The operation may still be conformant overall, but with a warning. That's more expressive than one global boolean.</p>
<h2 id="heading-build-a-reusable-conformance-evaluator">Build a Reusable Conformance Evaluator</h2>
<p>So far, the examples have been tied to one deployment and a handful of hard-coded checks. The next step is to separate the evaluation mechanism from the individual rules so the same engine can evaluate different operational specifications.</p>
<p>To do that, we represent each rule as a <code>Constraint&lt;T&gt;</code>. Each constraint carries its own name, severity, mode, and evaluation function. An <code>OperationalSpec&lt;T&gt;</code> then becomes a collection of those constraints.</p>
<pre><code class="language-typescript">type Constraint&lt;T&gt; = {
  name: string;
  severity: Severity;
  mode: ConstraintMode;
  evaluate(
    evidence: T
  ): ConstraintStatus;
};

type OperationalSpec&lt;T&gt; = {
  name: string;
  constraints: Constraint&lt;T&gt;[];
};
</code></pre>
<p>Then:</p>
<pre><code class="language-typescript">function evaluateConformance&lt;T&gt;(
  spec: OperationalSpec&lt;T&gt;,
  evidence: T
): ConstraintResult[] {
  return spec.constraints.map(
    (constraint) =&gt; ({
      name: constraint.name,
      status:
        constraint.evaluate(evidence),
      severity:
        constraint.severity,
      mode:
        constraint.mode,
    })
  );
}
</code></pre>
<p>The specification owns what should be evaluated.</p>
<p>The evaluator collects the results. It doesn't need to know the meaning of each constraint. It simply executes every constraint's evaluation function and preserves the resulting status, severity, and mode. That keeps the evaluation engine generic while the specification remains responsible for the operational rules.</p>
<p>The <code>expected</code> and <code>observed</code> fields are optional in this minimal example, but in a production system I would populate them, or store references to the underlying evidence, so the final verdict remains explainable and auditable.</p>
<h2 id="heading-calculate-a-conformance-summary-carefully">Calculate a Conformance Summary Carefully</h2>
<p>Individual results are useful, but sometimes you also need a summary.</p>
<pre><code class="language-typescript">type ConformanceSummary = {
  total: number;
  passed: number;
  failed: number;
  unknown: number;
  conformant: boolean;
};
</code></pre>
<p>Then:</p>
<pre><code class="language-typescript">function summarize(
  results: ConstraintResult[]
): ConformanceSummary {
  const required =
    results.filter(
      (result) =&gt;
        result.mode === "REQUIRED"
    );

  const passed =
    results.filter(
      (result) =&gt;
        result.status === "PASS"
    ).length;

  const failed =
    results.filter(
      (result) =&gt;
        result.status === "FAIL"
    ).length;

  const unknown =
    results.filter(
      (result) =&gt;
        result.status === "UNKNOWN"
    ).length;

  const conformant =
    required.every(
      (result) =&gt;
        result.status === "PASS"
    );

  return {
    total: results.length,
    passed,
    failed,
    unknown,
    conformant,
  };
}
</code></pre>
<p>Overall conformance now requires all required constraints to pass.</p>
<p>Notice what that means for <code>UNKNOWN</code>: because <code>every()</code> requires a required constraint to have status <code>PASS</code>, a required constraint with missing evidence makes the overall result non-conformant rather than silently passing.</p>
<p>Advisory constraints still appear in the report. They just don't determine the final verdict.</p>
<h2 id="heading-why-a-single-score-can-be-dangerous">Why a Single Score Can Be Dangerous</h2>
<p>It's tempting to create one single score:</p>
<pre><code class="language-text">conformance = 92%
</code></pre>
<p>But that can be misleading.</p>
<p>Suppose:</p>
<pre><code class="language-text">9 low-risk constraints pass
1 critical security constraint fails
</code></pre>
<p>A naïve score says:</p>
<pre><code class="language-text">90% conformant
</code></pre>
<p>Operationally, that may be completely unacceptable.</p>
<p>If you calculate a score, you should preserve:</p>
<pre><code class="language-text">constraint-level results
severity
required/advisory status
unknown evidence
failure reasons
</code></pre>
<p>The score can summarize, but it shouldn't erase the evidence.</p>
<h2 id="heading-preserve-the-evidence-behind-the-verdict">Preserve the Evidence Behind the Verdict</h2>
<p>A conformance result shouldn't only say <code>FAIL</code>. It should preserve what produced that verdict.</p>
<pre><code class="language-json">{
  "constraint": "minimum-replicas",
  "expected": "&gt;= 3",
  "observed": 2,
  "status": "FAIL",
  "severity": "CRITICAL"
}
</code></pre>
<p>This lets someone answer:</p>
<pre><code class="language-text">What did we expect?
What did we observe?
Which rule was applied?
Why did it fail?
</code></pre>
<p>That is especially important when conformance decisions trigger automated recovery.</p>
<h2 id="heading-conformance-should-be-reproducible">Conformance Should Be Reproducible</h2>
<p>Suppose an operation executes at 10:00.</p>
<p>At 10:01, the error rate is:</p>
<pre><code class="language-text">0.6%
</code></pre>
<p>At 14:00, the dashboard shows:</p>
<pre><code class="language-text">2.1%
</code></pre>
<p>If you reevaluate the old operation using current telemetry, you may get a different result.</p>
<p>Conformance should therefore be evaluated against evidence associated with the execution window. By <strong>execution window</strong>, I mean the period during which the operation ran and its immediate effects were being verified. For a deployment that finished at 10:00, for example, you might evaluate health using the telemetry collected between 10:00 and 10:05 rather than whatever the dashboard happens to show several hours later.</p>
<p>The point is to bind the verdict to the evidence that was available for that specific execution. Otherwise, later changes in the system can retroactively change the apparent result of an operation that already happened.</p>
<p>A reproducible record might preserve:</p>
<pre><code class="language-text">specification version
execution ID
evidence snapshot
evaluation rules
timestamp
</code></pre>
<p>For example:</p>
<pre><code class="language-json">{
  "executionId": "deploy-812",
  "specVersion": "3",
  "evaluatedAt": "2026-10-06T10:01:00Z",
  "evidence": {
    "version": "v42",
    "replicas": 3,
    "errorRate": 0.006,
    "p95LatencyMs": 290
  }
}
</code></pre>
<p>Now the decision can be reconstructed later.</p>
<h2 id="heading-how-this-applies-to-multiple-executors">How This Applies to Multiple Executors</h2>
<p>Suppose two executors run the same specification.</p>
<p>Executor A:</p>
<pre><code class="language-text">version      PASS
replicas     PASS
error-rate   PASS
latency      PASS
</code></pre>
<p>Executor B:</p>
<pre><code class="language-text">version      PASS
replicas     FAIL
error-rate   PASS
latency      PASS
</code></pre>
<p>The question is no longer:</p>
<blockquote>
<p>Which executor is better?</p>
</blockquote>
<p>The immediate question is:</p>
<blockquote>
<p>Which execution satisfied the specification?</p>
</blockquote>
<p>You answer that by running the same conformance evaluator against the evidence produced by each executor. The specification and evaluation rules stay the same; only the evidence changes.</p>
<p>In the example above, Executor A satisfies the specification because every required constraint evaluates to <code>PASS</code>. Executor B does not, because its replica constraint evaluates to <code>FAIL</code>. That gives us a common basis for comparison without requiring the two executors to use the same internal steps.</p>
<p>Over time, executor performance can be compared using conformance rate, failure categories, latency, cost, recovery frequency, and unknown evidence rate. But conformance remains the contract-level evaluation.</p>
<h2 id="heading-how-this-applies-to-ai-agents">How This Applies to AI Agents</h2>
<p>AI agents make explicit conformance especially important.</p>
<p>An agent may choose different plans for the same objective.</p>
<pre><code class="language-text">Agent execution 1:
scale
deploy
observe
route

Agent execution 2:
deploy canary
observe
expand

Agent execution 3:
create parallel environment
switch traffic
</code></pre>
<p>Because the agent can choose a different plan each time, comparing executions step by step isn't very useful. One run might use a canary rollout while another creates a parallel environment, and both can still be valid ways to reach the same objective.</p>
<p>What stays stable is the specification: the objective, constraints, and evidence requirements don't change just because the agent chooses a different path.</p>
<p>After each run, the system collects evidence about the resulting state. The conformance evaluator then compares that evidence with the same specification used for every other run. This lets the agent vary the execution plan while keeping the definition of success outside the agent itself.</p>
<pre><code class="language-text">Specification
      ↓
AI Agent
      ↓
Dynamic Plan
      ↓
Execution
      ↓
Evidence
      ↓
Conformance
</code></pre>
<p>The agent can propose actions. It shouldn't be the only authority deciding whether those actions achieved the intended outcome.</p>
<h2 id="heading-a-small-end-to-end-example">A Small End-to-End Example</h2>
<p>Start by defining the evidence:</p>
<pre><code class="language-typescript">type DeploymentEvidence = {
  version?: string;
  replicas?: number;
  errorRate?: number;
  p95LatencyMs?: number;
};
</code></pre>
<p>Now define the constraints:</p>
<pre><code class="language-typescript">const constraints:
  Constraint&lt;DeploymentEvidence&gt;[] = [
    {
      name: "version",
      severity: "CRITICAL",
      mode: "REQUIRED",
      evaluate: (evidence) =&gt; {
        if (!evidence.version) {
          return "UNKNOWN";
        }

        return (
          evidence.version === "v42"
        )
          ? "PASS"
          : "FAIL";
      },
    },
    {
      name: "replicas",
      severity: "CRITICAL",
      mode: "REQUIRED",
      evaluate: (evidence) =&gt; {
        if (
          evidence.replicas === undefined
        ) {
          return "UNKNOWN";
        }

        return (
          evidence.replicas &gt;= 3
        )
          ? "PASS"
          : "FAIL";
      },
    },
    {
      name: "error-rate",
      severity: "CRITICAL",
      mode: "REQUIRED",
      evaluate: (evidence) =&gt; {
        if (
          evidence.errorRate === undefined
        ) {
          return "UNKNOWN";
        }

        return (
          evidence.errorRate &lt;= 0.01
        )
          ? "PASS"
          : "FAIL";
      },
    },
    {
      name: "latency-target",
      severity: "WARNING",
      mode: "ADVISORY",
      evaluate: (evidence) =&gt; {
        if (
          evidence.p95LatencyMs === undefined
        ) {
          return "UNKNOWN";
        }

        return (
          evidence.p95LatencyMs &lt;= 350
        )
          ? "PASS"
          : "FAIL";
      },
    },
  ];
</code></pre>
<p>Create the specification:</p>
<pre><code class="language-typescript">const deploymentSpec:
  OperationalSpec&lt;DeploymentEvidence&gt; = {
    name: "deploy-orders-v42",
    constraints,
  };
</code></pre>
<p>Evaluate:</p>
<pre><code class="language-typescript">const evidence:
  DeploymentEvidence = {
    version: "v42",
    replicas: 3,
    errorRate: 0.004,
    p95LatencyMs: 370,
  };

const results =
  evaluateConformance(
    deploymentSpec,
    evidence
  );

const summary =
  summarize(results);
</code></pre>
<p>The result might be:</p>
<pre><code class="language-text">version          PASS     REQUIRED
replicas         PASS     REQUIRED
error-rate       PASS     REQUIRED
latency-target   FAIL     ADVISORY
</code></pre>
<p>And:</p>
<pre><code class="language-text">conformant = true
</code></pre>
<p>Every required constraint passed.</p>
<p>The advisory latency target failed, so the operation should still surface a warning.</p>
<h2 id="heading-a-practical-conformance-workflow">A Practical Conformance Workflow</h2>
<p>If you want to introduce conformance evaluation into an existing system:</p>
<ol>
<li><p>Pick one operation.</p>
</li>
<li><p>Define the specification.</p>
</li>
<li><p>Classify constraints as required or advisory.</p>
</li>
<li><p>Assign severity.</p>
</li>
<li><p>Define the evidence model.</p>
</li>
<li><p>Decide how missing evidence becomes <code>UNKNOWN</code>.</p>
</li>
<li><p>Evaluate constraints individually.</p>
</li>
<li><p>Produce a summary.</p>
</li>
<li><p>Preserve the evidence snapshot.</p>
</li>
<li><p>Version the specification.</p>
</li>
<li><p>Revisit rules that produce unexpected failures or unknowns.</p>
</li>
</ol>
<p>Don't simply weaken the specification to make results pass.</p>
<h2 id="heading-what-conformance-cant-prove">What Conformance Can't Prove</h2>
<p>Conformance can tell you:</p>
<blockquote>
<p>The observed evidence satisfies the specification.</p>
</blockquote>
<p>It can't automatically tell you:</p>
<blockquote>
<p>The specification is correct.</p>
</blockquote>
<p>The specification may be wrong, and the evidence may also be wrong.</p>
<p>So conformance depends on:</p>
<pre><code class="language-text">specification quality
evidence quality
evaluation correctness
</code></pre>
<p>It's a verification mechanism, not absolute truth.</p>
<h2 id="heading-from-execution-success-to-verifiable-operations">From Execution Success to Verifiable Operations</h2>
<p>Traditional automation often ends here:</p>
<pre><code class="language-text">execute
↓
success / failure
</code></pre>
<p>A conformance-based model adds more structure:</p>
<pre><code class="language-text">Specification
↓
Executor
↓
Execution
↓
Evidence
↓
Constraint Evaluation
↓
Conformance
</code></pre>
<p>Now you can ask:</p>
<blockquote>
<p>Which requirements were satisfied?</p>
</blockquote>
<blockquote>
<p>Which failed?</p>
</blockquote>
<blockquote>
<p>Which could not be evaluated?</p>
</blockquote>
<blockquote>
<p>What evidence supports the verdict?</p>
</blockquote>
<p>If the system is conformant immediately after execution, you now have a baseline.</p>
<p>From that baseline, you can keep observing the system over time.</p>
<p>And then ask:</p>
<blockquote>
<p>What happens when reality starts to diverge from the specification after the operation has already succeeded?</p>
</blockquote>
<p>That's operational drift.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>A successful executor isn't enough. Neither is a green pipeline or a completed agent plan.</p>
<p>What matters is whether the resulting system satisfies the operational specification.</p>
<p>That requires three things:</p>
<pre><code class="language-text">Specification
Evidence
Evaluation
</code></pre>
<p>A useful conformance model should preserve:</p>
<pre><code class="language-text">constraint-level results
PASS / FAIL / UNKNOWN
severity
required vs advisory rules
observed values
expected values
evidence snapshots
specification version
</code></pre>
<p>The executor performs the work, evidence describes what happened, and the specification defines what should have happened.</p>
<p>Then the conformance evaluator compares the two.</p>
<p>That gives us a stronger foundation for traditional automation and for increasingly autonomous systems.</p>
<p>But conformance at one point in time isn't the end of the story. A system can conform now and drift later.</p>
<p>The next question is:</p>
<blockquote>
<p>How do we detect when observed reality stops matching operational intent?</p>
</blockquote>
<p>That's where I want to go next.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Separate Operational Intent from the Executor ]]>
                </title>
                <description>
                    <![CDATA[ In my previous article, I argued that software automation needs a layer between operational intent and execution. The reason is simple: the specification should describe what success means, and the ex ]]>
                </description>
                <link>https://www.freecodecamp.org/news/separate-operational-intent-from-executor/</link>
                <guid isPermaLink="false">6ac242565423a0b19b9f5c6d</guid>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ sdops ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hugo Teijiz ]]>
                </dc:creator>
                <pubDate>Sun, 04 Oct 2026 12:11:02 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/8b78496e-2ca5-4585-8cfa-f25cb56b56c7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In <a href="https://www.freecodecamp.org/news/software-automation-intent-execution-layer/">my previous article</a>, I argued that software automation needs a layer between operational intent and execution.</p>
<p>The reason is simple: the specification should describe what success means, and the executor should decide how to achieve it.</p>
<p>That separation creates a useful possibility.</p>
<p>If the operational specification is independent from the execution mechanism, then more than one executor should be able to satisfy the same specification.</p>
<p>That sounds straightforward. But in practice, it raises several difficult questions:</p>
<pre><code class="language-text">Can two different executors achieve the same operational outcome?

How do we compare them?

What must remain stable when the executor changes?

Which differences are acceptable?

What evidence should each executor produce?

How do we know whether executor independence is real or just theoretical?
</code></pre>
<p>These questions matter because modern software systems rarely keep one execution mechanism forever.</p>
<p>Teams change:</p>
<pre><code class="language-text">CI/CD platforms
cloud providers
deployment systems
infrastructure tools
orchestration engines
incident automation
AI agents
</code></pre>
<p>If changing the executor also changes the meaning of the operation, then the system isn't really specification-driven. The executor still owns too much of the intent.</p>
<p>In this article, I’ll show you how to separate an operational specification from its executors, run the same specification through two different implementations, collect evidence, compare outcomes, and identify where executor independence breaks down.</p>
<p>The goal isn't to prove that two executors behave identically internally.</p>
<p>The goal is to determine whether they can satisfy the same operational contract.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You should be comfortable with:</p>
<ul>
<li><p>software architecture</p>
</li>
<li><p>interfaces and dependency inversion</p>
</li>
<li><p>TypeScript or a similar language</p>
</li>
<li><p>CI/CD and deployment concepts</p>
</li>
<li><p>observability</p>
</li>
<li><p>basic testing</p>
</li>
<li><p>operational specifications</p>
</li>
</ul>
<p>You don't need Kubernetes, Terraform, or any specific cloud platform. The examples are intentionally small and in-memory so the architecture stays visible.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-executor-independence-actually-means">What Executor Independence Actually Means</a></p>
</li>
<li><p><a href="#heading-why-executor-independence-matters">Why Executor Independence Matters</a></p>
</li>
<li><p><a href="#heading-start-with-one-stable-operational-specification">Start with One Stable Operational Specification</a></p>
</li>
<li><p><a href="#heading-define-an-executor-contract">Define an Executor Contract</a></p>
</li>
<li><p><a href="#heading-build-a-first-executor">Build a First Executor</a></p>
</li>
<li><p><a href="#heading-build-a-second-executor">Build a Second Executor</a></p>
</li>
<li><p><a href="#heading-run-the-same-specification-through-both-executors">Run the Same Specification Through Both Executors</a></p>
</li>
<li><p><a href="#heading-compare-evidence-not-internal-steps">Compare Evidence, Not Internal Steps</a></p>
</li>
<li><p><a href="#heading-normalize-executor-specific-evidence">Normalize Executor-Specific Evidence</a></p>
</li>
<li><p><a href="#heading-separate-execution-evidence-from-conformance">Separate Execution Evidence from Conformance</a></p>
</li>
<li><p><a href="#heading-what-counts-as-equivalent-execution">What Counts as Equivalent Execution?</a></p>
</li>
<li><p><a href="#heading-where-executor-independence-usually-breaks">Where Executor Independence Usually Breaks</a></p>
</li>
<li><p><a href="#heading-how-to-test-executor-portability">How to Test Executor Portability</a></p>
</li>
<li><p><a href="#heading-why-this-matters-for-ai-agents">Why This Matters for AI Agents</a></p>
</li>
<li><p><a href="#heading-a-small-end-to-end-example">A Small End-to-End Example</a></p>
</li>
<li><p><a href="#heading-a-practical-workflow">A Practical Workflow</a></p>
</li>
<li><p><a href="#heading-what-executor-independence-does-not-mean">What Executor Independence Does Not Mean</a></p>
</li>
<li><p><a href="#heading-from-replaceable-tools-to-stable-operational-intent">From Replaceable Tools to Stable Operational Intent</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-executor-independence-actually-means">What Executor Independence Actually Means</h2>
<p>Executor independence means that the operational specification remains valid even if the mechanism that performs the work changes.</p>
<p>For example, suppose the specification says:</p>
<pre><code class="language-text">Deploy Orders version v42.

Constraints:
- at least 3 replicas available
- error rate &lt;= 1%
- p95 latency &lt;= 400 ms
- rollback must remain possible
</code></pre>
<p>One executor may implement that using:</p>
<pre><code class="language-text">Kubernetes rolling deployment
</code></pre>
<p>Another may use:</p>
<pre><code class="language-text">blue/green deployment
</code></pre>
<p>Another may use:</p>
<pre><code class="language-text">a managed cloud deployment service
</code></pre>
<p>And later, an AI agent may generate its own execution plan.</p>
<p>If every executor can be evaluated against the same specification, then the specification is doing its job.</p>
<p>Conceptually:</p>
<pre><code class="language-text">                    ┌── Executor A
Operational Spec ───┼── Executor B
                    ├── Executor C
                    └── AI Agent
</code></pre>
<p>The specification stays stable while the execution mechanism changes.</p>
<p>That's executor independence.</p>
<h2 id="heading-why-executor-independence-matters">Why Executor Independence Matters</h2>
<p>Software teams replace tools constantly. A deployment workflow might move from:</p>
<pre><code class="language-text">Jenkins
↓
GitHub Actions
↓
Argo CD
↓
Kubernetes operator
</code></pre>
<p>If every migration requires rediscovering:</p>
<pre><code class="language-text">what success means
what constraints matter
what evidence is required
when rollback is allowed
</code></pre>
<p>then the operational meaning was never truly independent from the old tool.</p>
<p>This creates several risks.</p>
<ol>
<li><p>Tool lock-in: The system may be technically portable while the operational rules are not.</p>
</li>
<li><p>Hidden behavior changes: A new executor may preserve the same deployment steps but lose an important constraint.</p>
</li>
<li><p>Reimplementation drift: Teams may recreate the old behavior approximately rather than exactly.</p>
</li>
<li><p>Audit gaps: It becomes difficult to prove that the new executor preserves the same operational contract.</p>
</li>
</ol>
<p>Executor independence gives you a stronger migration target: preserve the specification, replace the mechanism.</p>
<h2 id="heading-start-with-one-stable-operational-specification">Start with One Stable Operational Specification</h2>
<p>To test executor independence, first define something stable.</p>
<p>For example:</p>
<pre><code class="language-typescript">type DeploymentSpec = {
  service: string;
  version: string;
  minReplicas: number;
  maxErrorRate: number;
  maxP95LatencyMs: number;
};
</code></pre>
<p>Then:</p>
<pre><code class="language-typescript">const spec: DeploymentSpec = {
  service: "orders",
  version: "v42",
  minReplicas: 3,
  maxErrorRate: 0.01,
  maxP95LatencyMs: 400,
};
</code></pre>
<p>This specification shouldn't contain:</p>
<pre><code class="language-text">kubectl
helm
terraform
AWS
Azure
Argo
GitHub Actions
</code></pre>
<p>Those belong to executors.</p>
<p>The specification describes the operational contract.</p>
<h2 id="heading-define-an-executor-contract">Define an Executor Contract</h2>
<p>Now define the minimum interface an executor must satisfy.</p>
<p>For example:</p>
<pre><code class="language-typescript">type ExecutionEvidence = {
  deployedVersion: string;
  availableReplicas: number;
  errorRate: number;
  p95LatencyMs: number;
};

interface DeploymentExecutor {
  execute(
    spec: DeploymentSpec
  ): Promise&lt;ExecutionEvidence&gt;;
}
</code></pre>
<p>This interface doesn't say how deployment happens.</p>
<p>It only says:</p>
<pre><code class="language-text">given a specification,
perform the operation,
return evidence
</code></pre>
<p>In this article, <strong>evidence</strong> means the observable facts produced or collected after execution that let us evaluate what actually happened. For a deployment, that might include the version that is running, the number of available replicas, the measured error rate, and p95 latency.</p>
<p>Evidence isn't the executor's opinion that the operation succeeded. It's the data we can compare against the specification.</p>
<p>That's important. If the executor interface includes tool-specific concepts, portability starts leaking.</p>
<p>For example, this would be more coupled:</p>
<pre><code class="language-typescript">interface DeploymentExecutor {
  executeKubectlCommand(
    namespace: string,
    manifestPath: string
  ): Promise&lt;void&gt;;
}
</code></pre>
<p>Now the interface already assumes Kubernetes.</p>
<p>That's not executor-independent.</p>
<h2 id="heading-build-a-first-executor">Build a First Executor</h2>
<p>Let’s create a simple in-memory executor.</p>
<pre><code class="language-typescript">class RollingDeploymentExecutor
  implements DeploymentExecutor {
  async execute(
    spec: DeploymentSpec
  ): Promise&lt;ExecutionEvidence&gt; {
    return {
      deployedVersion: spec.version,
      availableReplicas:
        spec.minReplicas,
      errorRate: 0.004,
      p95LatencyMs: 280,
    };
  }
}
</code></pre>
<p>This executor simulates a rolling deployment.</p>
<p>Internally, you can imagine that it:</p>
<pre><code class="language-text">starts new replicas
waits for health
gradually replaces old replicas
</code></pre>
<p>But none of that appears in the specification. The executor owns the mechanism.</p>
<h2 id="heading-build-a-second-executor">Build a Second Executor</h2>
<p>Now create a different strategy.</p>
<pre><code class="language-typescript">class BlueGreenExecutor
  implements DeploymentExecutor {
  async execute(
    spec: DeploymentSpec
  ): Promise&lt;ExecutionEvidence&gt; {
    return {
      deployedVersion: spec.version,
      availableReplicas:
        spec.minReplicas + 2,
      errorRate: 0.003,
      p95LatencyMs: 260,
    };
  }
}
</code></pre>
<p>This executor may conceptually:</p>
<pre><code class="language-text">create a parallel environment
verify it
switch traffic
keep the old environment available
</code></pre>
<p>Its internal process is different, but its evidence shape is the same. That means both can be evaluated against the same operational specification.</p>
<h2 id="heading-run-the-same-specification-through-both-executors">Run the Same Specification Through Both Executors</h2>
<p>Now execute both.</p>
<pre><code class="language-typescript">const rolling =
  new RollingDeploymentExecutor();

const blueGreen =
  new BlueGreenExecutor();

const rollingEvidence =
  await rolling.execute(spec);

const blueGreenEvidence =
  await blueGreen.execute(spec);
</code></pre>
<p>At this point, we have:</p>
<pre><code class="language-text">same specification
different executors
different internal behavior
different evidence values
</code></pre>
<p>The important question isn't whether they performed the same steps. They didn't. The question is if they both satisfied the same operational contract.</p>
<h2 id="heading-compare-evidence-not-internal-steps">Compare Evidence, Not Internal Steps</h2>
<p>Executor independence depends on comparing outcomes rather than implementation details.</p>
<p>Suppose:</p>
<pre><code class="language-text">Rolling deployment:
replicas = 3
error rate = 0.4%
p95 = 280 ms

Blue/green:
replicas = 5
error rate = 0.3%
p95 = 260 ms
</code></pre>
<p>Those outputs aren't identical, but both may be conformant. That matters.</p>
<p>Executor independence doesn't require:</p>
<pre><code class="language-text">same commands
same number of steps
same topology
same timing
same infrastructure
</code></pre>
<p>It requires:</p>
<pre><code class="language-text">same operational intent
satisfied constraints
required evidence
acceptable outcome
</code></pre>
<p>This is similar to interface-based programming. Two implementations can behave differently internally while satisfying the same contract.</p>
<h2 id="heading-normalize-executor-specific-evidence">Normalize Executor-Specific Evidence</h2>
<p>Real executors often return different evidence formats.</p>
<p>Suppose executor A returns:</p>
<pre><code class="language-json">{
  "readyReplicas": 3,
  "image": "orders:v42",
  "latencyP95": 280
}
</code></pre>
<p>Executor B returns:</p>
<pre><code class="language-json">{
  "instancesHealthy": 5,
  "releaseVersion": "v42",
  "p95Ms": 260
}
</code></pre>
<p>These can't be compared directly. You need adapters.</p>
<p>For example:</p>
<pre><code class="language-typescript">type CanonicalEvidence = {
  version: string;
  availableReplicas: number;
  p95LatencyMs: number;
};
</code></pre>
<p>Adapter A:</p>
<pre><code class="language-typescript">function normalizeRolling(
  raw: {
    readyReplicas: number;
    image: string;
    latencyP95: number;
  }
): CanonicalEvidence {
  return {
    version:
      raw.image.split(":")[1],
    availableReplicas:
      raw.readyReplicas,
    p95LatencyMs:
      raw.latencyP95,
  };
}
</code></pre>
<p>This adapter translates the rolling executor's native output into the canonical evidence model. It extracts the version from the image tag, maps <code>readyReplicas</code> to <code>availableReplicas</code>, and renames <code>latencyP95</code> to <code>p95LatencyMs</code>.</p>
<p>The important point is that the adapter doesn't change the operational meaning. It only converts executor-specific field names and formats into the shared representation expected by the specification layer.</p>
<p>Adapter B:</p>
<pre><code class="language-typescript">function normalizeBlueGreen(
  raw: {
    instancesHealthy: number;
    releaseVersion: string;
    p95Ms: number;
  }
): CanonicalEvidence {
  return {
    version:
      raw.releaseVersion,
    availableReplicas:
      raw.instancesHealthy,
    p95LatencyMs:
      raw.p95Ms,
  };
}
</code></pre>
<p>This adapter does the same job for the blue/green executor. Its raw output uses different names, but those values represent the same operational concepts: version, available capacity, and p95 latency.</p>
<p>With both adapters in place, the rest of the system no longer needs to understand the executor-specific shapes. It can evaluate both results using the same canonical model.</p>
<p>Now both executors produce evidence that can be evaluated using the same model.</p>
<p>This is an important architectural boundary. Executor-specific evidence stays near the executor, while canonical evidence belongs to the specification layer.</p>
<h2 id="heading-separate-execution-evidence-from-conformance">Separate Execution Evidence from Conformance</h2>
<p>The executor should produce evidence. It shouldn't decide whether the operation was successful from the perspective of the specification.</p>
<p>That distinction prevents another form of coupling.</p>
<p>For example, avoid:</p>
<pre><code class="language-typescript">return {
  success: true,
};
</code></pre>
<p>A generic success flag tells you very little.</p>
<p>Instead, return evidence:</p>
<pre><code class="language-typescript">return {
  deployedVersion: "v42",
  availableReplicas: 3,
  errorRate: 0.004,
  p95LatencyMs: 280,
};
</code></pre>
<p>Then evaluate it separately.</p>
<pre><code class="language-typescript">type ConformanceResult = {
  conformant: boolean;
  failures: string[];
};

function evaluateConformance(
  spec: DeploymentSpec,
  evidence: ExecutionEvidence
): ConformanceResult {
  const failures: string[] = [];

  if (
    evidence.deployedVersion !==
    spec.version
  ) {
    failures.push(
      "wrong-version"
    );
  }

  if (
    evidence.availableReplicas &lt;
    spec.minReplicas
  ) {
    failures.push(
      "insufficient-replicas"
    );
  }

  if (
    evidence.errorRate &gt;
    spec.maxErrorRate
  ) {
    failures.push(
      "error-rate-too-high"
    );
  }

  if (
    evidence.p95LatencyMs &gt;
    spec.maxP95LatencyMs
  ) {
    failures.push(
      "latency-too-high"
    );
  }

  return {
    conformant:
      failures.length === 0,
    failures,
  };
}
</code></pre>
<p>The evaluator receives two things: the specification that defines what should be true, and the evidence that describes what was actually observed.</p>
<p>It checks each constraint independently. A wrong version adds <code>wrong-version</code>, too few replicas adds <code>insufficient-replicas</code>, and the error-rate and latency checks work the same way.</p>
<p>At the end, <code>conformant</code> is <code>true</code> only when no failures were recorded. Returning the individual failure names is useful because it explains <em>why</em> an execution didn't conform instead of collapsing everything into a generic <code>false</code>.</p>
<p>This is why the executor should return facts rather than a final verdict. The same evidence can be reevaluated later if the specification changes, if an audit needs to reconstruct the decision, or if you want to compare multiple executors using exactly the same rules.</p>
<p>Now:</p>
<pre><code class="language-text">executor → evidence

specification + evidence → conformance
</code></pre>
<p>This separation will become important later.</p>
<h2 id="heading-what-counts-as-equivalent-execution">What Counts as Equivalent Execution?</h2>
<p>Two executors don't need to produce identical evidence. They need to produce evidence that satisfies the same specification.</p>
<p>Suppose:</p>
<pre><code class="language-text">Executor A
replicas: 3
error rate: 0.4%
latency: 280 ms

Executor B
replicas: 5
error rate: 0.3%
latency: 260 ms
</code></pre>
<p>Both may pass.</p>
<p>Now suppose executor B produces:</p>
<pre><code class="language-text">replicas: 2
error rate: 0.3%
latency: 260 ms
</code></pre>
<p>It fails one constraint.</p>
<p>That means:</p>
<pre><code class="language-text">Executor A:
conformant

Executor B:
non-conformant
</code></pre>
<p>The executors are still independent implementations, but only one satisfied the specification for this execution.</p>
<p>That distinction matters.</p>
<p>Executor independence doesn't guarantee executor correctness. It only gives you a stable contract against which correctness can be evaluated.</p>
<h2 id="heading-where-executor-independence-usually-breaks">Where Executor Independence Usually Breaks</h2>
<p>There are several common failure modes.</p>
<h3 id="heading-tool-specific-fields-in-the-specification">Tool-Specific Fields in the Specification</h3>
<p>For example:</p>
<pre><code class="language-yaml">kubernetesNamespace: production
helmChart: orders
</code></pre>
<p>If those are truly implementation details, they shouldn't live in the operational specification.</p>
<h3 id="heading-executor-owned-success-rules">Executor-Owned Success Rules</h3>
<p>If each executor defines its own thresholds, the specification is no longer authoritative.</p>
<h3 id="heading-missing-evidence-normalization">Missing Evidence Normalization</h3>
<p>Different executors may expose different concepts that are never mapped into a common model.</p>
<h3 id="heading-hidden-preconditions">Hidden Preconditions</h3>
<p>Executor A may require an approval. Executor B may not.</p>
<p>If approval is part of operational intent, that rule shouldn't live only inside one executor.</p>
<h3 id="heading-hidden-recovery-behavior">Hidden Recovery Behavior</h3>
<p>One executor may rollback automatically, while another may leave the failed state running.</p>
<p>If recovery behavior matters, the specification should express that expectation.</p>
<h3 id="heading-semantic-mismatch">Semantic Mismatch</h3>
<p>Two tools may use the same words differently.</p>
<p>For example:</p>
<pre><code class="language-text">healthy
ready
available
running
</code></pre>
<p>Those concepts need explicit definitions. Otherwise executor portability is superficial.</p>
<h2 id="heading-how-to-test-executor-portability">How to Test Executor Portability</h2>
<p>You can test executor independence explicitly.</p>
<p>Start with a suite of specifications.</p>
<p>For example:</p>
<pre><code class="language-typescript">const cases: DeploymentSpec[] = [
  {
    service: "orders",
    version: "v42",
    minReplicas: 3,
    maxErrorRate: 0.01,
    maxP95LatencyMs: 400,
  },
  {
    service: "payments",
    version: "v18",
    minReplicas: 5,
    maxErrorRate: 0.005,
    maxP95LatencyMs: 250,
  },
];
</code></pre>
<p>Then run each specification against every executor.</p>
<pre><code class="language-typescript">const executors:
  DeploymentExecutor[] = [
    new RollingDeploymentExecutor(),
    new BlueGreenExecutor(),
  ];

for (const spec of cases) {
  for (const executor of executors) {
    const evidence =
      await executor.execute(spec);

    const result =
      evaluateConformance(
        spec,
        evidence
      );

    console.log({
      spec: spec.service,
      executor:
        executor.constructor.name,
      result,
    });
  }
}
</code></pre>
<p>This produces a useful matrix:</p>
<pre><code class="language-text">               Rolling    BlueGreen
Orders v42     PASS       PASS
Payments v18   PASS       FAIL
</code></pre>
<p>Now you have evidence about executor portability.</p>
<p>That is much stronger than assuming that both tools support deployments, so they're equivalent.</p>
<h2 id="heading-why-this-matters-for-ai-agents">Why This Matters for AI Agents</h2>
<p>AI agents make executor independence even more interesting.</p>
<p>An AI agent may generate a new plan every time.</p>
<p>For example:</p>
<pre><code class="language-text">Execution 1:
scale
deploy
verify
route

Execution 2:
create parallel environment
verify
switch traffic

Execution 3:
deploy canary
observe
expand rollout
</code></pre>
<p>The plans differ, and the executor behavior is dynamic. That makes step-by-step equivalence unrealistic.</p>
<p>But the specification can still remain stable.</p>
<p>For example:</p>
<pre><code class="language-text">Deploy Orders v42.

Constraints:
replicas &gt;= 3
error rate &lt;= 1%
latency &lt;= 400 ms

Evidence:
running version
replicas
error rate
latency
</code></pre>
<p>The AI agent can choose any acceptable plan, and its result is still evaluated using the same contract.</p>
<p>This creates a powerful boundary:</p>
<pre><code class="language-text">agent autonomy
inside
operational constraints
</code></pre>
<p>The agent can optimize execution. It can't silently redefine success.</p>
<h2 id="heading-a-small-end-to-end-example">A Small End-to-End Example</h2>
<p>Let’s put the pieces together.</p>
<p>Start with the specification:</p>
<pre><code class="language-typescript">const spec: DeploymentSpec = {
  service: "orders",
  version: "v42",
  minReplicas: 3,
  maxErrorRate: 0.01,
  maxP95LatencyMs: 400,
};
</code></pre>
<p>Create two executors:</p>
<pre><code class="language-typescript">const executors:
  DeploymentExecutor[] = [
    new RollingDeploymentExecutor(),
    new BlueGreenExecutor(),
  ];
</code></pre>
<p>Run them:</p>
<pre><code class="language-typescript">for (const executor of executors) {
  const evidence =
    await executor.execute(spec);

  const conformance =
    evaluateConformance(
      spec,
      evidence
    );

  console.log(
    executor.constructor.name,
    evidence,
    conformance
  );
}
</code></pre>
<p>Possible result:</p>
<pre><code class="language-text">RollingDeploymentExecutor

evidence:
version = v42
replicas = 3
error rate = 0.004
p95 = 280

conformance:
PASS
</code></pre>
<p>and:</p>
<pre><code class="language-text">BlueGreenExecutor

evidence:
version = v42
replicas = 5
error rate = 0.003
p95 = 260

conformance:
PASS
</code></pre>
<p>The executors didn't behave identically, and they didn't need to. They satisfied the same operational specification.</p>
<p>Now imagine the second executor reports:</p>
<pre><code class="language-text">replicas = 2
</code></pre>
<p>The result becomes:</p>
<pre><code class="language-text">BlueGreenExecutor

conformance:
FAIL

reason:
insufficient-replicas
</code></pre>
<p>That's exactly what we want: the specification remains stable, and the executor changes.</p>
<p>Evidence reveals whether the execution satisfies the contract.</p>
<h2 id="heading-a-practical-workflow">A Practical Workflow</h2>
<p>If you want to test executor independence in a real system, I would use this sequence.</p>
<h3 id="heading-1-pick-one-operation">1. Pick One Operation</h3>
<p>For example:</p>
<pre><code class="language-text">deploy service
restore backup
rotate certificate
scale worker pool
</code></pre>
<h3 id="heading-2-extract-the-operational-specification">2. Extract the Operational Specification</h3>
<p>Define:</p>
<pre><code class="language-text">objective
constraints
evidence requirements
recovery expectations
</code></pre>
<h3 id="heading-3-remove-tool-specific-language">3. Remove Tool-Specific Language</h3>
<p>Look for:</p>
<pre><code class="language-text">kubectl
Terraform
GitHub Actions
AWS CLI
specific resource IDs
</code></pre>
<p>Keep only what is truly part of the operational intent.</p>
<h3 id="heading-4-define-an-executor-interface">4. Define an Executor Interface</h3>
<p>Make the executor responsible for:</p>
<pre><code class="language-text">performing the operation
producing evidence
</code></pre>
<h3 id="heading-5-build-the-first-adapter">5. Build the First Adapter</h3>
<p>Wrap the current implementation, don't rewrite it.</p>
<h3 id="heading-6-build-a-second-executor">6. Build a Second Executor</h3>
<p>Use:</p>
<pre><code class="language-text">another tool
another deployment strategy
a simulator
a test double
an AI agent
</code></pre>
<h3 id="heading-7-normalize-evidence">7. Normalize Evidence</h3>
<p>Map executor-specific observations into a canonical evidence model.</p>
<h3 id="heading-8-evaluate-conformance-separately">8. Evaluate Conformance Separately</h3>
<p>Don't let the executor decide whether it passed.</p>
<h3 id="heading-9-compare-portability">9. Compare Portability</h3>
<p>Run the same specifications through multiple executors.</p>
<h3 id="heading-10-investigate-differences">10. Investigate Differences</h3>
<p>Ask:</p>
<pre><code class="language-text">Is the executor wrong?

Is the specification incomplete?

Is evidence missing?

Are the concepts actually equivalent?
</code></pre>
<p>Those differences are useful. They expose hidden coupling.</p>
<h2 id="heading-what-executor-independence-does-not-mean">What Executor Independence Does Not Mean</h2>
<p>Executor independence doesn't mean that all tools are interchangeable.</p>
<p>Different executors may have different:</p>
<pre><code class="language-text">capabilities
costs
latency
failure modes
security models
operational complexity
</code></pre>
<p>A specification may also require a capability that one executor simply can't provide.</p>
<p>For example:</p>
<pre><code class="language-text">zero-downtime deployment
</code></pre>
<p>may be feasible in one platform and impossible in another.</p>
<p>That's not a failure of the specification. It's useful information. The executor can't satisfy the contract.</p>
<p>Executor independence also doesn't mean ignoring implementation details. Implementation details still matter for:</p>
<pre><code class="language-text">performance
security
cost
reliability
maintainability
</code></pre>
<p>The point is narrower: the operational meaning shouldn't depend on one specific execution mechanism.</p>
<h2 id="heading-from-replaceable-tools-to-stable-operational-intent">From Replaceable Tools to Stable Operational Intent</h2>
<p>Software infrastructure changes constantly.</p>
<p>Tools come and go, execution strategies evolve, cloud platforms change, and agents become more capable.</p>
<p>If operational intent is embedded inside each executor, every change risks becoming a semantic migration.</p>
<p>But if intent is represented independently:</p>
<pre><code class="language-text">Operational specification
          ↓
     stable contract
          ↓
   replaceable executor
</code></pre>
<p>then the system becomes easier to evolve.</p>
<p>This also gives us something else:</p>
<pre><code class="language-text">evidence from multiple executors
evaluated against the same specification
</code></pre>
<p>At that point, we can stop asking:</p>
<blockquote>
<p>Did the tool finish?</p>
</blockquote>
<p>and start asking:</p>
<blockquote>
<p>To what degree did this execution conform to the specification?</p>
</blockquote>
<p>That's the next problem.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Executor independence isn't about pretending every tool is the same. It's about protecting operational intent from implementation churn.</p>
<p>A specification describes:</p>
<pre><code class="language-text">what should happen
what constraints must hold
what evidence is required
</code></pre>
<p>An executor decides:</p>
<pre><code class="language-text">how to make it happen
</code></pre>
<p>Then execution produces evidence. And evidence can be evaluated independently.</p>
<p>That gives us:</p>
<pre><code class="language-text">Specification
      ↓
Executor A ──→ Evidence A
Executor B ──→ Evidence B
      ↓
Evaluation
</code></pre>
<p>Two executors can use completely different strategies and still satisfy the same operational contract.</p>
<p>That's a useful property for ordinary automation.</p>
<p>It becomes even more important when executors are autonomous agents whose internal plans may change from one run to the next.</p>
<p>But once multiple executors can operate against the same specification, a new question becomes unavoidable:</p>
<blockquote>
<p>How should we measure conformance between the specification and what actually happened?</p>
</blockquote>
<p>That's where I want to go next.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why Software Automation Needs a Layer Between Intent and Execution ]]>
                </title>
                <description>
                    <![CDATA[ Software automation is good at turning instructions into actions. A pipeline can deploy a release, Terraform can provision infrastructure, a runbook can restart a service, or an agent can inspect tele ]]>
                </description>
                <link>https://www.freecodecamp.org/news/software-automation-intent-execution-layer/</link>
                <guid isPermaLink="false">6abb57d97eb0e171af50644b</guid>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ sdops ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hugo Teijiz ]]>
                </dc:creator>
                <pubDate>Tue, 29 Sep 2026 06:16:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/eb21a403-1e0d-46a4-a8b5-2dbb98095cdd.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Software automation is good at turning instructions into actions.</p>
<p>A pipeline can deploy a release, Terraform can provision infrastructure, a runbook can restart a service, or an agent can inspect telemetry and choose a remediation step.</p>
<p>But all of those systems share the same weakness: they're usually better at executing instructions than at representing the intent behind those instructions.</p>
<p>That gap is easy to ignore while automation is simple. If a script only runs one command, the script itself may be enough to explain what should happen.</p>
<p>But as systems become more distributed, more policy-driven, and more autonomous, execution logic starts carrying responsibilities it was never designed to own.</p>
<p>A deployment workflow may now encode:</p>
<pre><code class="language-text">what should be deployed
where it may run
which constraints must hold
which evidence must be checked
who may approve the change
when rollback is required
which failures are acceptable
</code></pre>
<p>At that point, the automation is no longer just executing. It's also becoming the place where operational intent is defined.</p>
<p>And that's a problem.</p>
<p>Because when intent and execution are fused together, changing the executor can also change the meaning of the operation.</p>
<p>In <a href="https://www.freecodecamp.org/news/executable-operational-specifications-software-automation/">my previous article</a>, I introduced the idea of an executable operational specification: a machine-readable description of what an operation is supposed to achieve and how its outcome can be evaluated.</p>
<p>In this article, I want to go one level deeper.</p>
<p>The central question is:</p>
<blockquote>
<p>What should exist between human intent and the system that executes it?</p>
</blockquote>
<p>I will explore:</p>
<ul>
<li><p>why automation tools often become accidental specifications</p>
</li>
<li><p>why intent and execution evolve at different speeds</p>
</li>
<li><p>what gets lost when intent is embedded in pipelines and scripts</p>
</li>
<li><p>why desired state is useful but still incomplete</p>
</li>
<li><p>how to introduce an explicit operational intent layer</p>
</li>
<li><p>how preconditions, constraints, evidence, and recovery rules fit into that layer</p>
</li>
<li><p>how execution plans differ from operational specifications</p>
</li>
<li><p>how this structure helps with AI agents</p>
</li>
<li><p>and what this separation still can't solve</p>
</li>
</ul>
<p>The goal isn't to add another abstraction for its own sake. The goal is to make operational decisions easier to understand, evaluate, and change without rewriting the meaning of the operation every time the execution mechanism changes.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You should be comfortable with:</p>
<ul>
<li><p>key software architecture concepts</p>
</li>
<li><p>CI/CD</p>
</li>
<li><p>infrastructure automation</p>
</li>
<li><p>observability</p>
</li>
<li><p>distributed systems</p>
</li>
<li><p>basic policy concepts</p>
</li>
<li><p>TypeScript or a similar language</p>
</li>
</ul>
<p>You don't need a specific orchestration tool or cloud platform. The examples are deliberately small and tool-independent.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-automation-tools-often-become-accidental-specifications">Automation Tools Often Become Accidental Specifications</a></p>
</li>
<li><p><a href="#heading-intent-and-execution-change-at-different-speeds">Intent and Execution Change at Different Speeds</a></p>
</li>
<li><p><a href="#heading-what-gets-lost-when-intent-lives-inside-the-executor">What Gets Lost When Intent Lives Inside the Executor</a></p>
</li>
<li><p><a href="#heading-desired-state-helps-but-its-not-the-whole-intent">Desired State Helps, But It's Not the Whole Intent</a></p>
</li>
<li><p><a href="#heading-the-missing-layer">The Missing Layer</a></p>
</li>
<li><p><a href="#heading-a-small-deployment-example">A Small Deployment Example</a></p>
</li>
<li><p><a href="#heading-separate-preconditions-from-execution-steps">Separate Preconditions from Execution Steps</a></p>
</li>
<li><p><a href="#heading-separate-constraints-from-the-mechanism">Separate Constraints from the Mechanism</a></p>
</li>
<li><p><a href="#heading-make-evidence-requirements-explicit">Make Evidence Requirements Explicit</a></p>
</li>
<li><p><a href="#heading-define-recovery-before-execution-starts">Define Recovery Before Execution Starts</a></p>
</li>
<li><p><a href="#heading-operational-specification-vs-execution-plan">Operational Specification vs Execution Plan</a></p>
</li>
<li><p><a href="#heading-why-this-matters-for-ai-agents">Why This Matters for AI Agents</a></p>
</li>
<li><p><a href="#heading-a-minimal-typescript-model">A Minimal TypeScript Model</a></p>
</li>
<li><p><a href="#heading-a-practical-workflow">A Practical Workflow</a></p>
</li>
<li><p><a href="#heading-what-this-separation-doesnt-solve">What This Separation Doesn't Solve</a></p>
</li>
<li><p><a href="#heading-from-automation-logic-to-operational-contracts">From Automation Logic to Operational Contracts</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-automation-tools-often-become-accidental-specifications">Automation Tools Often Become Accidental Specifications</h2>
<p>Consider a deployment workflow. It may begin as something simple:</p>
<pre><code class="language-yaml">steps:
  - build
  - test
  - deploy
</code></pre>
<p>Then production requirements appear.</p>
<p>You add:</p>
<pre><code class="language-text">approval
region validation
health checks
canary percentage
rollback logic
error-rate thresholds
latency thresholds
artifact verification
</code></pre>
<p>Eventually, the pipeline becomes the only place where the operation is fully described.</p>
<p>That creates an accidental architecture:</p>
<pre><code class="language-text">Operational intent
      ↓
pipeline configuration
      ↓
execution
</code></pre>
<p>The pipeline is now doing two jobs: it describes <strong>what the operation means</strong> and <strong>how the operation is executed</strong>.</p>
<p>Those responsibilities are related, but they're not identical.</p>
<p>Suppose this workflow says:</p>
<pre><code class="language-yaml">deploy:
  image: orders:v42

verify:
  errorRateBelow: 0.01

rollback:
  whenVerificationFails: true
</code></pre>
<p>That may work perfectly. But now imagine moving the deployment to another platform.</p>
<p>If the operational rules only exist inside this pipeline, you must rediscover and reimplement them while moving the executor.</p>
<p>The real migration becomes:</p>
<pre><code class="language-text">move execution mechanism
+
reconstruct operational intent
</code></pre>
<p>That's much more dangerous than just changing tooling.</p>
<h2 id="heading-intent-and-execution-change-at-different-speeds">Intent and Execution Change at Different Speeds</h2>
<p>Operational intent is often relatively stable.</p>
<p>For example:</p>
<pre><code class="language-text">Deploy the approved version.

Maintain availability.

Do not exceed the error-rate threshold.

Keep rollback possible.

Do not deploy outside the approved region.
</code></pre>
<p>Those expectations may remain valid for years. But execution mechanisms change much more frequently.</p>
<p>A team may move through:</p>
<pre><code class="language-text">shell scripts
Jenkins
GitHub Actions
Argo CD
Kubernetes operators
cloud-native deployment services
AI agents
</code></pre>
<p>If intent is tightly coupled to those mechanisms, every tooling change risks becoming an operational redesign.</p>
<p>That creates unnecessary churn.</p>
<p>You want:</p>
<pre><code class="language-text">stable intent
     ↓
replaceable execution
</code></pre>
<p>not:</p>
<pre><code class="language-text">new executor
     ↓
rediscover the meaning of the operation
</code></pre>
<p>This separation becomes more important as automation becomes more sophisticated.</p>
<p>The more intelligence the executor contains, the easier it becomes for intent to disappear inside implementation details.</p>
<h2 id="heading-what-gets-lost-when-intent-lives-inside-the-executor">What Gets Lost When Intent Lives Inside the Executor</h2>
<p>When operational intent is embedded directly in scripts and pipelines, several things become harder.</p>
<h3 id="heading-1-review">1. Review</h3>
<p>A reviewer may see:</p>
<pre><code class="language-yaml">if: error_rate &lt; 0.01
</code></pre>
<p>but not know whether that threshold is:</p>
<pre><code class="language-text">a business requirement
an SRE policy
a temporary rollout rule
a historical workaround
</code></pre>
<p>The value is executable. Its meaning isn't explicit.</p>
<h3 id="heading-2-auditability">2. Auditability</h3>
<p>Six months later, somebody asks:</p>
<blockquote>
<p>Why was this deployment allowed?</p>
</blockquote>
<p>The answer may require reconstructing:</p>
<pre><code class="language-text">pipeline version
feature flags
runtime variables
dashboards
approvals
operator decisions
</code></pre>
<h3 id="heading-3-portability">3. Portability</h3>
<p>Changing tools means reimplementing rules.</p>
<h3 id="heading-4-testing">4. Testing</h3>
<p>You can test whether the pipeline works. But it's harder to test whether the operational intent itself is complete.</p>
<h3 id="heading-5-governance">5. Governance</h3>
<p>Authorization rules may become buried in workflow code.</p>
<p>For example:</p>
<pre><code class="language-text">production changes require approval
</code></pre>
<p>may be implemented as a platform-specific branch protection rule instead of being represented as part of the operation.</p>
<h3 id="heading-6-ai-execution">6. AI Execution</h3>
<p>An AI agent may generate a valid sequence of commands while violating an unstated operational constraint.</p>
<p>The problem isn't that the executor is weak. It's that the intent isn't independently represented.</p>
<h2 id="heading-desired-state-helps-but-its-not-the-whole-intent">Desired State Helps, But It's Not the Whole Intent</h2>
<p>Many modern systems already use desired-state models.</p>
<p>For example:</p>
<pre><code class="language-yaml">replicas: 3
image: orders:v42
</code></pre>
<p>That's useful.</p>
<p>The system can compare:</p>
<pre><code class="language-text">desired state
vs.
observed state
</code></pre>
<p>and reconcile the difference.</p>
<p>But operational intent often includes more than final configuration.</p>
<p>Suppose the desired state is:</p>
<pre><code class="language-text">orders:v42
replicas: 3
</code></pre>
<p>The operation may also require:</p>
<pre><code class="language-text">do not reduce availability below 99.9%
do not deploy if payment dependency is degraded
keep error rate below 1%
require approval for production
rollback if latency exceeds 400 ms
</code></pre>
<p>Those aren't simply target-state properties.</p>
<p>Some are:</p>
<pre><code class="language-text">preconditions
runtime constraints
evidence requirements
governance rules
recovery rules
</code></pre>
<p>Desired state is part of operational intent, but it's not always the entire operational intent.</p>
<h2 id="heading-the-missing-layer">The Missing Layer</h2>
<p>A more explicit architecture looks like this:</p>
<pre><code class="language-text">Human / organizational intent
             ↓
Operational specification
             ↓
Execution planning / binding
             ↓
Executor
             ↓
Actions
             ↓
Evidence
             ↓
Evaluation
</code></pre>
<p>The operational specification is the missing layer.</p>
<p>Its job is not to execute commands. Its job is to preserve the meaning of the operation independently from the mechanism performing it.</p>
<p>That layer can contain:</p>
<pre><code class="language-text">objective
scope
preconditions
constraints
required evidence
authorization rules
recovery conditions
</code></pre>
<p>The executor then answers a different question:</p>
<blockquote>
<p>How can I satisfy this specification using the tools available to me?</p>
</blockquote>
<p>That's a cleaner separation.</p>
<h2 id="heading-a-small-deployment-example">A Small Deployment Example</h2>
<p>Suppose we want to deploy version <code>v42</code> of the Orders service.</p>
<p>The operational objective is:</p>
<pre><code class="language-text">Deploy Orders v42 to production.
</code></pre>
<p>But that's not enough.</p>
<p>We also know:</p>
<pre><code class="language-text">the payment dependency must be healthy
at least 3 replicas must remain available
error rate must remain below 1%
p95 latency must remain below 400 ms
production deployment requires approval
rollback must remain possible
</code></pre>
<p>We can represent those concerns separately.</p>
<pre><code class="language-typescript">type DeploymentIntent = {
  service: string;
  version: string;
  environment: string;
  preconditions: {
    paymentDependencyHealthy: boolean;
    approved: boolean;
  };
  constraints: {
    minAvailableReplicas: number;
    maxErrorRate: number;
    maxP95LatencyMs: number;
  };
  recovery: {
    rollbackOnViolation: boolean;
  };
};
</code></pre>
<p>Then:</p>
<pre><code class="language-typescript">const intent: DeploymentIntent = {
  service: "orders",
  version: "v42",
  environment: "production",
  preconditions: {
    paymentDependencyHealthy: true,
    approved: true,
  },
  constraints: {
    minAvailableReplicas: 3,
    maxErrorRate: 0.01,
    maxP95LatencyMs: 400,
  },
  recovery: {
    rollbackOnViolation: true,
  },
};
</code></pre>
<p>Notice what's missing. There's no:</p>
<pre><code class="language-text">kubectl
Terraform
GitHub Actions
Argo
cloud API
</code></pre>
<p>The intent doesn't yet care how the deployment happens.</p>
<p>That's deliberate.</p>
<h2 id="heading-separate-preconditions-from-execution-steps">Separate Preconditions from Execution Steps</h2>
<p>A precondition answers:</p>
<blockquote>
<p>Is this operation allowed to start?</p>
</blockquote>
<p>That's different from:</p>
<blockquote>
<p>Which step should run first?</p>
</blockquote>
<p>For example:</p>
<pre><code class="language-text">payment dependency must be healthy
production approval must exist
artifact must be signed
maintenance window must be open
</code></pre>
<p>These aren't execution instructions. They're conditions that should be true before execution begins.</p>
<p>A simple evaluator might look like this:</p>
<pre><code class="language-typescript">type Preconditions = {
  paymentDependencyHealthy: boolean;
  approved: boolean;
};

function canStart(
  preconditions: Preconditions
): boolean {
  return (
    preconditions.paymentDependencyHealthy &amp;&amp;
    preconditions.approved
  );
}
</code></pre>
<p>If:</p>
<pre><code class="language-typescript">canStart({
  paymentDependencyHealthy: false,
  approved: true,
});
</code></pre>
<p>returns:</p>
<pre><code class="language-text">false
</code></pre>
<p>the operation shouldn't begin.</p>
<p>That decision shouldn't depend on whether the executor is Kubernetes, a script, or an AI agent. The precondition belongs to the operational intent.</p>
<h2 id="heading-separate-constraints-from-the-mechanism">Separate Constraints from the Mechanism</h2>
<p>Constraints answer what must remain true while or after this operation runs.</p>
<p>Examples:</p>
<pre><code class="language-text">replicas &gt;= 3
error rate &lt;= 1%
latency &lt;= 400 ms
cost increase &lt;= 20%
region must remain us-east-1
</code></pre>
<p>An executor may use many different strategies to satisfy them.</p>
<p>For example, to maintain three replicas:</p>
<pre><code class="language-text">Kubernetes may use a rolling deployment
another platform may use blue/green deployment
an agent may temporarily scale capacity before switching traffic
</code></pre>
<p>The mechanism differs, but the constraint does not.</p>
<p>That's exactly why the constraint should live outside the executor.</p>
<h2 id="heading-make-evidence-requirements-explicit">Make Evidence Requirements Explicit</h2>
<p>A constraint is only useful if you can evaluate it.</p>
<p>Suppose you define:</p>
<pre><code class="language-text">error rate &lt;= 1%
</code></pre>
<p>Then you need a source of evidence.</p>
<p>For example:</p>
<pre><code class="language-text">Prometheus
Datadog
CloudWatch
OpenTelemetry
custom application metrics
</code></pre>
<p>The specification shouldn't necessarily hard-code one vendor. But it should say that evidence is required.</p>
<p>For example:</p>
<pre><code class="language-typescript">type EvidenceRequirement = {
  name: string;
  metric: string;
};

const evidenceRequirements:
  EvidenceRequirement[] = [
    {
      name: "availability",
      metric: "availableReplicas",
    },
    {
      name: "errors",
      metric: "errorRate",
    },
    {
      name: "latency",
      metric: "p95LatencyMs",
    },
  ];
</code></pre>
<p>The executor or evidence collector can decide where those values come from. The specification defines what must be known. That distinction is important.</p>
<h2 id="heading-define-recovery-before-execution-starts">Define Recovery Before Execution Starts</h2>
<p>Many operational workflows define rollback only after something goes wrong. But that's too late. Recovery is part of operational intent.</p>
<p>For example:</p>
<pre><code class="language-text">If error rate exceeds 1%, stop rollout.

If p95 latency exceeds 400 ms, restore previous version.

If evidence is unavailable, require human review.
</code></pre>
<p>These aren't just implementation details. They express acceptable operational behavior.</p>
<p>A simple representation might be:</p>
<pre><code class="language-typescript">type RecoveryPolicy = {
  rollbackOnConstraintViolation: boolean;
  requireHumanReviewWhenEvidenceMissing:
    boolean;
};
</code></pre>
<p>The executor can implement rollback differently. The policy still belongs to the operation.</p>
<h2 id="heading-operational-specification-vs-execution-plan">Operational Specification vs Execution Plan</h2>
<p>This distinction is important.</p>
<p>An operational specification answers:</p>
<pre><code class="language-text">What should happen?

What must be true?

What evidence is required?

What happens if constraints fail?
</code></pre>
<p>An execution plan answers:</p>
<pre><code class="language-text">Which actions will we perform?

In what order?

Using which tools?
</code></pre>
<p>For example:</p>
<h3 id="heading-specification">Specification</h3>
<pre><code class="language-text">Deploy Orders v42.

Keep at least 3 replicas available.

Error rate &lt;= 1%.

Rollback if the constraint is violated.
</code></pre>
<h3 id="heading-execution-plan">Execution plan</h3>
<pre><code class="language-text">1. Scale Orders to 6 replicas.
2. Deploy v42 to 1 replica.
3. Route 10% of traffic.
4. Wait 5 minutes.
5. Check metrics.
6. Increase traffic.
7. Remove old replicas.
</code></pre>
<p>The plan is one possible way to satisfy the specification.</p>
<p>Another executor might produce a different plan. That's useful. It means the same operational intent can survive changes in tooling and execution strategy.</p>
<h2 id="heading-why-this-matters-for-ai-agents">Why This Matters for AI Agents</h2>
<p>This separation becomes especially important when the executor is an AI agent.</p>
<p>A traditional script usually has a predictable path while an agent may decide dynamically.</p>
<p>For the same incident, it might choose:</p>
<pre><code class="language-text">restart
scale
reroute
disable feature
change configuration
</code></pre>
<p>If correctness is defined by following one exact sequence, dynamic execution becomes difficult to verify.</p>
<p>But if correctness is defined by an operational specification, the agent has flexibility.</p>
<p>For example:</p>
<pre><code class="language-text">Objective:
restore checkout availability

Preconditions:
payment database reachable

Constraints:
authentication remains enabled
no payment duplication
error rate &lt;= 1%
cost increase &lt;= 20%

Evidence:
checkout success rate
duplicate payment count
error rate
cost delta

Recovery:
escalate if constraints cannot be satisfied
</code></pre>
<p>The agent can choose different actions, but its behavior is still bounded.</p>
<p>That gives us:</p>
<pre><code class="language-text">Intent
↓
Specification
↓
Agent-generated plan
↓
Execution
↓
Evidence
↓
Evaluation
</code></pre>
<p>The agent is free to decide <strong>how</strong>. It's not free to redefine <strong>what success means</strong>.</p>
<p>That distinction will become increasingly important as autonomous systems gain more operational authority.</p>
<h2 id="heading-a-minimal-typescript-model">A Minimal TypeScript Model</h2>
<p>We can combine these ideas into a small generic model.</p>
<pre><code class="language-typescript">type OperationalSpec&lt;TContext, TEvidence&gt; = {
  name: string;

  canStart(
    context: TContext
  ): boolean;

  evaluate(
    evidence: TEvidence
  ): EvaluationResult;

  recovery: RecoveryPolicy;
};

type EvaluationResult = {
  conformant: boolean;
  failures: string[];
};

type RecoveryPolicy = {
  rollbackOnConstraintViolation: boolean;
};
</code></pre>
<p>For a deployment:</p>
<pre><code class="language-typescript">type DeploymentContext = {
  paymentDependencyHealthy: boolean;
  approved: boolean;
};

type DeploymentEvidence = {
  version: string;
  replicas: number;
  errorRate: number;
  p95LatencyMs: number;
};
</code></pre>
<p>Then:</p>
<pre><code class="language-typescript">const deploymentSpec:
  OperationalSpec&lt;
    DeploymentContext,
    DeploymentEvidence
  &gt; = {
    name: "deploy-orders-v42",

    canStart: (context) =&gt;
      context.paymentDependencyHealthy &amp;&amp;
      context.approved,

    evaluate: (evidence) =&gt; {
      const failures: string[] = [];

      if (evidence.version !== "v42") {
        failures.push(
          "wrong-version"
        );
      }

      if (evidence.replicas &lt; 3) {
        failures.push(
          "insufficient-replicas"
        );
      }

      if (evidence.errorRate &gt; 0.01) {
        failures.push(
          "error-rate-too-high"
        );
      }

      if (
        evidence.p95LatencyMs &gt; 400
      ) {
        failures.push(
          "latency-too-high"
        );
      }

      return {
        conformant:
          failures.length === 0,
        failures,
      };
    },

    recovery: {
      rollbackOnConstraintViolation:
        true,
    },
  };
</code></pre>
<p>Now execution can be handled elsewhere.</p>
<p>For example:</p>
<pre><code class="language-typescript">interface DeploymentExecutor {
  execute(
    spec: typeof deploymentSpec
  ): Promise&lt;DeploymentEvidence&gt;;
}
</code></pre>
<p>That executor could be backed by:</p>
<pre><code class="language-text">Kubernetes
a cloud platform
a test harness
a local simulator
an AI agent
</code></pre>
<p>The specification remains the reference point.</p>
<h2 id="heading-a-practical-workflow">A Practical Workflow</h2>
<p>If you want to introduce this separation into an existing automation system, start with one operation.</p>
<h3 id="heading-1-pick-a-repeated-operation">1. Pick a Repeated Operation</h3>
<p>For example:</p>
<pre><code class="language-text">deploy service
rotate certificate
scale worker pool
restore backup
</code></pre>
<h3 id="heading-2-extract-the-objective">2. Extract the Objective</h3>
<p>Write down what the operation is trying to achieve. Avoid commands.</p>
<h3 id="heading-3-extract-preconditions">3. Extract Preconditions</h3>
<p>Ask:</p>
<blockquote>
<p>What must already be true before this operation can begin?</p>
</blockquote>
<h3 id="heading-4-extract-constraints">4. Extract Constraints</h3>
<p>Ask:</p>
<blockquote>
<p>What must remain true during or after execution?</p>
</blockquote>
<h3 id="heading-5-define-evidence">5. Define Evidence</h3>
<p>For every constraint, identify the observation required to evaluate it.</p>
<h3 id="heading-6-define-recovery">6. Define Recovery</h3>
<p>Decide what should happen when evidence shows that a constraint was violated.</p>
<h3 id="heading-7-leave-execution-separate">7. Leave Execution Separate</h3>
<p>Let the current automation tool remain the executor. Don't rewrite everything at once.</p>
<h3 id="heading-8-evaluate-the-existing-executor">8. Evaluate the Existing Executor</h3>
<p>Ask whether the current script, pipeline, or agent satisfies the extracted specification.</p>
<p>This is useful because the separation can be introduced incrementally. You don't need a new operations platform to begin.</p>
<h2 id="heading-what-this-separation-doesnt-solve">What This Separation Doesn't Solve</h2>
<p>Separating intent from execution doesn't automatically make operations correct.</p>
<p>You can still have:</p>
<pre><code class="language-text">wrong objectives
bad thresholds
missing evidence
misleading metrics
conflicting constraints
unsafe executors
security bugs
incorrect recovery policies
</code></pre>
<p>The specification itself can be wrong. That matters.</p>
<p>A formal representation doesn't make an assumption true. It only makes the assumption easier to inspect and evaluate.</p>
<p>There's also a cost. More explicit operational intent means more artifacts to maintain.</p>
<p>If specifications aren't versioned and reviewed, they can become stale. If evidence definitions are weak, conformance results can become misleading. And some operational decisions remain difficult to formalize.</p>
<p>Human judgment doesn't disappear. The value comes from making more of that judgment explicit.</p>
<h2 id="heading-from-automation-logic-to-operational-contracts">From Automation Logic to Operational Contracts</h2>
<p>There's another way to think about this separation.</p>
<p>A mature operational specification starts to behave like a contract.</p>
<p>It says:</p>
<pre><code class="language-text">This operation may begin under these conditions.

It must preserve these constraints.

It must produce this evidence.

Its outcome will be evaluated using these rules.

If it fails, these recovery conditions apply.
</code></pre>
<p>That's much stronger than:</p>
<pre><code class="language-text">run this workflow
</code></pre>
<p>The workflow is an implementation. The operational contract describes what that implementation is responsible for achieving.</p>
<p>This gives us a structure like:</p>
<pre><code class="language-text">Intent
↓
Operational contract
↓
Execution plan
↓
Executor
↓
Evidence
↓
Evaluation
</code></pre>
<p>That separation creates room for something important:</p>
<pre><code class="language-text">multiple execution strategies
for the same operational intent
</code></pre>
<p>And once that's possible, another question appears:</p>
<blockquote>
<p>Can two different executors satisfy the same operational specification?</p>
</blockquote>
<p>That's the question I want to explore next.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Automation has made software operations faster and more repeatable.</p>
<p>But execution is only one part of an operation. We still need to know:</p>
<pre><code class="language-text">why the operation is allowed
what outcome is intended
what constraints must hold
what evidence is required
what happens when the constraints fail
</code></pre>
<p>When all of that logic lives inside a pipeline, script, or agent, the executor becomes the accidental owner of operational meaning.</p>
<p>That coupling makes systems harder to review, migrate, audit, and verify.</p>
<p>A better separation is:</p>
<pre><code class="language-text">Intent
↓
Operational specification
↓
Execution plan
↓
Executor
↓
Evidence
↓
Evaluation
</code></pre>
<p>The specification describes <strong>what success means</strong>. The executor decides <strong>how to achieve it</strong>.</p>
<p>That distinction becomes increasingly valuable as execution mechanisms become more dynamic. Especially when those executors are AI agents.</p>
<p>Because the more freedom we give a system to decide how work gets done, the more explicit we need to be about what the work is supposed to achieve.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
