---
title: 'Repository-Defined Desired State'
source: 'https://academia.sh/en/courses/ci-cd/repository-based-desired-state'
course: 'Continuous Integration and Delivery'
language: en
updated: '2026-09-13T05:08:19+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Runs the same change flow in two arrangements: manual application and committing to the repository'
    - "Compares the unrecorded-change rate and the number of drifted items at period's end across both arrangements"
    - "Measures the review gate's marginal value, its wrongful holds, and the time it adds"
---

# Repository-Defined Desired State

The same change flow runs in two arrangements — manual application and committing to the repository — and across 180 changes the two arrangements' traceability, unrecorded-change rate, end-of-period drift, and steps per change are counted. The review gate's marginal value — the defect it alone catches, the clean change it wrongly holds, and the 796 hours it adds — is measured separately.

The previous topics established how a release is decided: which metric promotion looks at, at what
threshold a percentage rollout stops, how many steps a rollback plan has. All of these decisions
shared one thing — in the end, someone pressed a button in an interface or typed a shell command.
The decision itself was on record; its **application** was not.

This lesson measures that gap. The question is: does the desired state the cluster is running exist
**in writing** somewhere, or only in the running system's memory? The **repository-defined desired
state** principle is a single sentence — the definition of what runs in an environment lives in a
repository, and it changes through no path outside that repository. The principle itself is short;
its bill is long. This lesson draws up the bill.

The measurement is over the **regional measurement network** and is **fictional**: the meter-reading
collector, verifier, billing, and work-order services, across three environments. What runs is a
**model** built with `node`, not a real system.

**RD1.** The desired state is 60 items: four services × three environments × five fields (replica
count, threshold, version, network rule, schedule).
**RD2.** 180 changes are generated over ninety days; the generator is self-written, seed 20260801 is
visible.
**RD3.** A portion of the changes are **urgent** (off-shift intervention); 20% carry a known defect:
format, threshold, environment, runtime.
**RD4.** In the manual arrangement, recording is voluntary — the person applying it writes it down
somewhere if they choose to. In the repository arrangement, the commit record mandatorily carries
who, when, and rationale fields.
**RD5.** In the repository arrangement too, a portion of the urgent changes bypass the repository;
the principle is not applied 100%.
**RD6.** There are two gates and both are only on the repository path: the schema validator
(automated) and review (human).

## The Same Change, Two Paths

```js
// flow.mjs — a 90-day change flow modeled over the (fictional) regional measurement
// network. The same flow runs in two arrangements: manual application and committing
// to the repository. What runs is a model, not a real system; seed is visible.
export const generator = (t) => { let s = t >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };

const SERVICE = ["collector", "verifier", "billing", "workorder"];
const ENV = ["development", "staging", "production"];
const FIELD = ["replica", "threshold", "version", "network-rule", "schedule"];
export const ITEM = SERVICE.flatMap((s) => ENV.flatMap((e) => FIELD.map((f) => `${s}/${e}/${f}`)));

export function flow(seed = 20260801, count = 180) {          // 90-day change flow
  const r = generator(seed), list = [];
  for (let i = 0; i < count; i++) {
    const item = ITEM[Math.floor(r() * ITEM.length)];
    const urgent = r() < 0.22;                                  // off-shift intervention
    const p = r();                                              // known defect set: 20%
    const defect = p < 0.06 ? "format" : p < 0.12 ? "threshold" : p < 0.16 ? "environment"
                : p < 0.20 ? "runtime" : null;
    list.push({ i, day: Math.floor(r() * 90), item, urgent, defect });
  }
  return list;
}

// repo=false: every change is applied manually, recording is voluntary.
// repo=true : the change is committed to the repository; some urgent ones still skip it.
export function arrange(list, { repo, seed = 7717 }) {
  const r = generator(seed);
  const actual = new Map(), ledger = new Map();                // item -> version number
  let who = 0, rationale = 0, unrecorded = 0, skipped = 0, step = 0, reviewed = 0;
  for (const d of list) {
    const repoPath = repo && !(d.urgent && r() < 0.35);
    const s = (actual.get(d.item) ?? 0) + 1;
    actual.set(d.item, s);
    if (repoPath) {                                             // branch, change, commit,
      ledger.set(d.item, s);                                    // proposal, review,
      who++; rationale++; reviewed++; step += 7;                // merge, apply
    } else {
      skipped++; step += 2;                                     // write command, apply
      const records = r() < (d.urgent ? 0.25 : 0.7);
      if (records) { who++; if (r() < 0.45) rationale++; } else unrecorded++;
      if (r() < (d.urgent ? 0.1 : 0.3)) ledger.set(d.item, s);   // logged later into the ledger
    }
  }
  let drifted = 0;
  for (const [k, s] of actual) if (ledger.get(k) !== s) drifted++;
  return { n: list.length, who, rationale, unrecorded, skipped, reviewed, drifted,
    touched: actual.size, step: step / list.length };
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const list = flow();
  const urgent = list.filter((d) => d.urgent).length;
  const defective = list.filter((d) => d.defect).length;
  console.log(`seed 20260801 | ${list.length} changes / 90 days | ${ITEM.length} items`);
  console.log(`urgent: ${urgent} | carrying a known defect: ${defective}`);
  const HEAD = ["arrangement", "who", "rationale", "unrecorded", "skipped", "reviewed", "drifted item", "step"];
  const W = [21, 5, 11, 12, 9, 10, 14, 6];
  const row = (h) => h.map((v, j) => j ? String(v).padStart(W[j]) : String(v).padEnd(W[0])).join("");
  console.log("\n" + row(HEAD));
  for (const repo of [false, true]) {
    const x = arrange(list, { repo });
    console.log(row([repo ? "repository commit" : "manual application", x.who, x.rationale, x.unrecorded,
      x.skipped, x.reviewed, `${x.drifted}/${x.touched}`, x.step.toFixed(2)]));
  }
}
```

```
seed 20260801 | 180 changes / 90 days | 60 items
urgent: 35 | carrying a known defect: 36

arrangement            who  rationale  unrecorded  skipped  reviewed  drifted item  step
manual application     117         54          63      180         0         45/56  2.00
repository commit      175        172           5        9       171          1/56  6.75
```

The traceability columns diverge in the expected direction. In the manual arrangement, 117 of the
180 changes have who-and-when information landing somewhere, but only 54 carry a rationale; 63
changes — 35% of the flow — were applied leaving no trace at all. In the repository arrangement,
rationale climbs to 172, because a commit record cannot be left empty: the who, when, and why
fields are the change's own carrier, not a separately filled-out form.

The real number sits three columns from the end. Over ninety days, 56 of the 60 items were touched;
in the manual arrangement, at period's end, **45** of those 56 items differ from the repository's
value. In other words, the document that describes the system is wrong for four-fifths of it. In
the repository arrangement, the drifted-item count is 1, and that one item is left over from the
nine urgent changes that bypassed the repository.

The operational weight of this gap is heavier than a stale document. In the manual arrangement, the
only place that knows an item's correct value is the running system itself; when an environment is
rebuilt, or an item is reset, there is no source to fall back to. If the billing service's
production threshold is changed by hand one night and no one writes it down, that threshold is no
longer anyone's decision — it is an accident. The first payoff of repository-defined desired state
is not auditing, it is **reversibility**: for 55 of the 56 items, the repository's value is the
system's last known correct state.

## What the Review Gate Holds Only By Itself

```js
// gate.mjs — ledger of the repository-path review gate: what it holds, what it holds
// ONLY BY ITSELF, what it misses, wrongly holds, and the time it adds. Same flow, new seed.
import { flow, generator } from "./flow.mjs";
const SCHEMA = { format: 1, threshold: 0, environment: 0, runtime: 0 };   // schema validator
const REVIEW = { format: 0.85, threshold: 0.55, environment: 0.75, runtime: 0.05 };

function run(list, { escape, seed = 4231 }) {
  const r = generator(seed), actual = new Map(), ledger = new Map();
  const byEnv = {}, byDefect = {};
  let skipped = 0, gated = 0, schemaOnly = 0, marginal = 0, both = 0,
    passed = 0, wrong = 0, minutes = 0, triggered = false;
  const apply = (d, withRepo) => { const s = (actual.get(d.item) ?? 0) + 1;
    actual.set(d.item, s); if (withRepo) ledger.set(d.item, s); return s; };
  for (const d of list) {
    const e = (byEnv[d.item.split("/")[1]] ??= { gated: 0, marginal: 0, wrong: 0, min: 0 });
    const k = d.defect ? (byDefect[d.defect] ??= { n: 0, review: 0, schema: 0, passed: 0 }) : null;
    if (k) k.n++;
    const skip = r() < (d.urgent ? 0.35 : (escape && triggered ? 0.5 : 0));
    triggered = false;
    if (skip) {                                                     // ungated path
      skipped++; const s = apply(d, false);
      if (k) { passed++; k.passed++; }
      if (r() < 0.2) ledger.set(d.item, s);
      continue;
    }
    gated++; e.gated++;
    const wait = d.urgent ? 5 + r() * 55 : 25 + r() * 575;            // review wait time
    minutes += wait; e.min += wait;
    const schema = d.defect ? r() < SCHEMA[d.defect] : false;
    const review = d.defect ? r() < REVIEW[d.defect] : false;
    if (k && (schema || review)) {                                   // held, not applied
      if (schema) k.schema++;
      if (review) k.review++;
      if (schema && review) both++; else if (schema) schemaOnly++; else { marginal++; e.marginal++; }
      continue;
    }
    if (!k && r() < 0.06) {                                          // clean but held
      wrong++; e.wrong++; triggered = true;
      minutes += wait * 0.6; e.min += wait * 0.6;                    // second round
    }
    apply(d, true);
    if (k) { passed++; k.passed++; }
  }
  let drifted = 0; for (const [a, s] of actual) if (ledger.get(a) !== s) drifted++;
  return { skipped, gated, schemaOnly, marginal, both, passed, wrong, drifted,
    min: minutes / gated, hours: minutes / 60, byEnv, byDefect };
}

const row = (w, cells) => cells.map((v, j) => j ? String(v).padStart(w[j]) : String(v).padEnd(w[0])).join("");

if (import.meta.url === `file://${process.argv[1]}`) {
  const list = flow(), defective = list.filter((d) => d.defect).length;
  const W_MAIN = [8, 9, 7, 13, 13, 6, 8, 7, 9, 12];
  console.log(`${list.length} changes, ${defective} known defect; two gates: schema + review`);
  console.log("\n" + row(W_MAIN, ["escape", "skipped", "gated", "schema only", "review only",
    "both", "passed", "wrong", "drifted", "min/review"]));
  for (const escape of [false, true]) {
    const x = run(list, { escape });
    console.log(row(W_MAIN, [escape ? "yes" : "no", x.skipped, x.gated, x.schemaOnly, x.marginal,
      x.both, x.passed, x.wrong, x.drifted, x.min.toFixed(0)]));
  }
  const x = run(list, { escape: false });
  const W_DEFECT = [13, 7, 8, 8, 8];
  console.log("\n" + row(W_DEFECT, ["defect", "count", "schema", "review", "passed"]));
  for (const [ad, k] of Object.entries(x.byDefect))
    console.log(row(W_DEFECT, [ad, k.n, k.schema, k.review, k.passed]));
  const W_ENV = [13, 7, 13, 14, 13, 16];
  console.log("\n" + row(W_ENV, ["environment", "gated", "review only", "wrongly held",
    "total hours", "hours/marginal"]));
  for (const [ad, e] of Object.entries(x.byEnv))
    console.log(row(W_ENV, [ad, e.gated, e.marginal, e.wrong, (e.min / 60).toFixed(1),
      e.marginal ? (e.min / 60 / e.marginal).toFixed(1) : "-"]));
  console.log(`\ntotal review load: ${x.hours.toFixed(0)} hours / 90 days`);
}
```

```
180 changes, 36 known defect; two gates: schema + review

escape    skipped  gated  schema only  review only  both  passed  wrong  drifted  min/review
no             13    167            1           13    11      11      7        4         286
yes            20    160            1           15     9      11      9        7         270

defect         count  schema  review  passed
threshold          8       0       3       5
format            12      12      11       0
environment       11       0       9       2
runtime            5       0       1       4

environment    gated  review only  wrongly held  total hours  hours/marginal
staging           67            6             2        312.5            52.1
development       44            1             2        193.7           193.7
production        56            6             3        289.9            48.3

total review load: 796 hours / 90 days
```

Both gates do work, but their ledgers do not resemble each other. The schema validator held 12
format defects; review would have caught 11 of those too, so the number the **schema alone** holds
is 1. Review held 24 defects and caught 13 of them by itself — these are the defects that would
pass whether or not the other gate existed. The marginal-value ranking is clear: schema gate 1,
review 13.

The defect-class table shows why. All 12 format defects were held, none got through; both gates see
this class, and the schema gate does it in seconds. Threshold and environment defects are invisible
to the schema: the value is valid, the field is correct, only the number or the target is wrong.
Only a human reading catches these — 3 of 8 threshold defects, 9 of 11 environment defects, were
held.

The 11 defects the gate misses also read by class: 5 threshold, 2 environment, 4 runtime. The
runtime class let four of its five defects through, because this class cannot be seen by reading
the written text — the value only turns out wrong once the service meets real data. This is a
boundary standing in front of a static gate, and extending the review's duration does not push that
boundary back.

The wrongly-held column is the gate's other face: 7 clean changes were stopped, and each one
produced a second round. The average wait per review is 286 minutes; over ninety days that is **796
hours** total, roughly a nine-hour wait load per day. This time is not a labor cost — it is lead
time itself: after a change is written, it goes nowhere for an average of four hours and forty-six
minutes.

## The Measured Trace of Escaping the Gate

The table's second row models the behavior wrongful holding produces in the team: after a change is
stopped for no reason, the change that follows it becomes more likely to go through the ungated
path, even if it is not urgent. The change skipping the gate rises from 13 to 20, and the drifted
item at period's end rises from 4 to 7. The number of defects that reach production is 11 in both
runs; at the scale of 180 changes, this difference sits under the generator's noise and should be
presented that way. The measurable effect is not in the defect count — it is in the **drift the
gate produces itself**: when trust in a gate falls, the loss shows up first in traceability.

The environment table, in turn, points directly at the gate that should be removed. 44 changes
headed to the development environment were reviewed, those reviews totaled 193.7 hours, and **only
1** defect was caught marginally — 193.7 hours per defect. The same measure is 48.3 hours in
production, 52.1 in staging. What is more, the 2 wrongful holds in the development environment are
twice the marginal catch: here the gate stops more for nothing than it holds.

The decision that follows does not touch the principle, it touches the gate: development-environment
changes keep being committed to the repository, the review gate comes off. This distinction is the
most practical conclusion this lesson carries — **committing to the repository and review are not
the same thing.** The traceability, rationale, and reversibility payoff comes from the commit
record; the only thing the gate contributes is the defect it holds, and that is priced very
differently by environment.

## The Principle's Price: Extra Steps Per Change

The first table's last column is the principle's direct bill. Manual application is 2 steps per
change: a command is written, applied. The repository path is 7 steps — a branch is opened, the
file is changed, committed, a proposal is opened, review is waited on, merged, applied. The mixed
average is 6.75; the difference is 4.75 steps per change, 855 extra steps over ninety days.

Not all of these steps carry equal weight. Six of them are the writer's own minutes; only one — the
review wait — depends on someone else's calendar, and the entire measured 286 minutes comes from
there. This is why the urgent path continues to exist: 13 of the 35 urgent changes skipped the
gate, because a four-hour wait at midnight extends the outage. Applying the principle 100% brings
the measured unrecorded-change rate down from 5 to 0, but adds four hours to the urgent
intervention's duration; whichever side is chosen cannot be defended without measuring it.

## Summary

- In the manual arrangement, 63 of the 180 changes (35%) were applied leaving no trace at all, and
  rationale existed for only 54 changes; in the repository arrangement, unrecorded changes fell to
  5 and unjustified changes to 8.
- Of the 56 items touched at period's end, 45 had drifted from the repository's value in the manual
  arrangement, versus 1 in the repository arrangement; the principle's first payoff is not
  auditing, it is reversibility.
- The schema gate held 12 defects but caught only 1 of them by itself; review held 24, catching 13
  by itself — the marginal-value ranking is 1 against 13.
- The gate missed 11 defects (5 threshold, 2 environment, 4 runtime) and wrongly held 7 clean
  changes for nothing; it added 286 minutes per review, 796 hours over ninety days.
- In the development environment, the gate caught 1 marginal defect in 193.7 hours and stopped 2
  clean changes; the same measure is 48.3 hours in production. This gate is removed in the
  development environment; the commit record stays.
- The principle's step cost rises from 2 to 6.75 per change; only one of the extra steps depends on
  someone else's calendar, and the entire measured wait comes from there.

## Next Step

This lesson measured **where the desired state is written** and showed the value of the
repository's writing through the drifted-item count. The question left open is the next lesson's
subject: who carries that writing in the repository over to the cluster? In the arrangement so far,
the carrier is the pipeline — the pipeline connects to the cluster and writes. There is an
alternative: an agent running inside the cluster reads the repository and closes the gap itself.
The items to measure are set: the pipeline's authority surface over the cluster, the detection
delay of a manual change, the direction of the network connection, and what happens when the
pipeline goes down.
