---
title: 'Configuration Models'
source: 'https://academia.sh/en/courses/network-operations/configuration-models'
course: 'Network Operations and Automation'
language: en
updated: '2026-08-17T18:07:14+00:00'
license: 'CC BY-SA 4.0'
---

# Configuration Models

When device configuration is defined by a data model, as the model widens, covered fields climb from 80 to 113 and partially represented devices fall from 30 to 9; the remaining nine fields enter no model at all, because their value source is the device itself.

The previous lesson built its template on an assumption: that forty devices' configuration can
be described with the same fields. The measurement also showed where that assumption did not
hold — the last row in the field list, the local rule a special device wanted, and no template
carried it. Where that row went was left open.

This lesson's question is exactly that. When a device's configuration stops being text and is
defined by a **data model**, what can the model represent? Which device does not fit it? And
does the field left outside the model disappear, or does it merely vanish from view? What is
measured is not what the model covers, it is **what it does not**.

## A Model Is a Contract

Manual configuration's output is text, and the text's meaning lives inside the device that reads
it. Two different management tools can interpret the same text two different ways; there is
nowhere to say which one is right.

A **configuration model** closes this gap. The model declares, in a place independent of any
device, what fields configuration consists of, each field's type, under what condition it is
required, and what range of values it accepts. What declarative configuration calls the "desired
end state" can only be written with such a model; without a model, there is no grammar in which
to write the desired state at all.

The model does three jobs. **Validation:** a configuration that does not conform to the model is
rejected before it reaches the device. **Comparison:** two configurations can be compared field
by field, because both are written with the same field set — configuration drift can only be
measured through this comparison. **Generation:** the same model can generate different values
for different devices.

```text
# taught model schema, not run

device:
  interface   : text        , required                      , shared value
  address     : address      , required                      , per-device
  priority    : number 0-7  , required if class interactive  , shared value
  scheduler   : number (ms) , required above latency thresh. , per-device
  aggregation : list        , required above capacity thresh., shared value
  # local_rule : has no counterpart in the model
```

The schema's last row sits inside a comment, and that is what this lesson measures.

## The Value's Source: Three Classes

A field's presence in the model does not say where that field's value comes from. Three sources
can be distinguished, and the distinction sets the model's boundary.

**Shared value.** The field is the same across every device. The template carries the value once
and writes it to forty devices. This is where the lever works most efficiently.

**Per-device value.** The field exists in the model, but its value derives from the device's own
attribute — the address comes from the inventory, the scheduler is computed from latency. Here
the template carries not a value but a **rule**; the rule runs forty separate times. The lever is
still singular, its output is forty separate values.

**Unrepresentable field.** The field has no counterpart in the model and cannot have one. Adding
a rule that belongs to only one device would push the model into forty separate shapes; at that
point the model would stop being a contract and become the sum of forty separate configurations.
A field left outside the model is not a deficiency, it is **the consequence of the model's own
definition**.

The assumptions the measurement rests on:

- **AC7** — The forty-device subject set is the course's constant and is not changed; field
  derivation is the same rule as the previous lesson.
- **AC8** — A model is a set of field names. A device is **fully represented** only if every
  field it requires is present in the model; if a single field is missing, it is **partially
  represented**.
- **AC9** — The three model widths are nested: each adds fields on top of the previous one, none
  removes a field. The width order is core, extended, full.
- **AC10** — `local_rule` is in no model. This is not a flaw but a decision of the setup; its
  rationale is the third class above.
- **AC11** — Every field has a single value source: shared, per-device, or unrepresentable. The
  source depends on the field's name, not on the device.
- **AC12** — The template is written for a device class, and the report is built on the
  interactive class; a special device whose class does not match deviates, and a deviation in
  another class never appears in the report.

## The Measurement

```python
"""Configuration model: the device the model cannot represent.

Part 1 - three model widths: covered fields, uncovered fields, and devices.
Part 2 - a field's value source: shared, per-device, unrepresentable.
Part 3 - the tail: what falls outside the template and what the report cannot see.
"""
SEED = 20260812
SOURCE = {"interface": "shared", "address": "per-device", "priority": "shared",
          "scheduler": "per-device", "aggregation": "shared",
          "local_rule": "unrepresentable"}
MODELS = {
    "core": ("interface", "address"),
    "extended": ("interface", "address", "priority", "scheduler"),
    "full": ("interface", "address", "priority", "scheduler", "aggregation"),
}


def make_rng(seed):
    d = seed % 2147483646 + 1

    def r(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def subjects(count=40, seed=SEED):
    r, out = make_rng(seed), []
    for i in range(count):
        out.append({"no": i + 1, "capacity": 20 + r(81),
                    "latency": 5 + r(45),
                    "class": ("interactive", "batch", "standby")[r(3)],
                    "special": r(9) == 0})
    return out


def fields(o):
    """The fields a device's configuration requires."""
    f = ["interface", "address"]
    if o["class"] == "interactive":
        f.append("priority")
    if o["latency"] > 30:
        f.append("scheduler")
    if o["capacity"] >= 80:
        f.append("aggregation")
    if o["special"]:
        f.append("local_rule")
    return f


def measure(subj, model):
    """Fields and devices the model covers and leaves outside."""
    covered = uncovered = full = partial = 0
    for o in subj:
        d = [a for a in fields(o) if a not in model]
        covered += len(fields(o)) - len(d)
        uncovered += len(d)
        if d:
            partial += 1
        else:
            full += 1
    return covered, uncovered, full, partial


def apply_template(subj, template_class):
    """A single template is applied to forty devices; special devices deviate."""
    matched = deviated = hidden = 0
    for o in subj:
        if o["class"] == template_class or not o["special"]:
            matched += 1
        else:
            deviated += 1
            if o["class"] != "interactive":
                hidden += 1
    return matched, deviated, hidden


subj = subjects()
total = sum(len(fields(o)) for o in subj)
print(f"subjects {len(subj)} | total field instances {total} | distinct field names "
      f"{len(SOURCE)} | special devices {sum(o['special'] for o in subj)}")
print()
print(f"{'model':<10s} {'field names':>11s} {'covered':>9s} {'uncovered':>9s} "
      f"{'full rep.':>9s} {'partial rep.':>12s}")
for name, model in MODELS.items():
    c, u, f, p = measure(subj, model)
    print(f"{name:<10s} {len(model):11d} {c:9d} {u:9d} {f:9d} {p:12d}")

print()
print(f"{'field':<12s} {'devices':>7s} {'value source':>16s} "
      f"{'first covering model':>21s}")
for field, source in SOURCE.items():
    count = sum(1 for o in subj if field in fields(o))
    first = next((name for name, m in MODELS.items() if field in m), "none")
    print(f"{field:<12s} {count:7d} {source:>16s} {first:>21s}")

print()
for source in ("shared", "per-device", "unrepresentable"):
    count = sum(1 for o in subj for a in fields(o) if SOURCE[a] == source)
    print(f"{source:<17s} field instances {count:4d}  {count / total:.3f} of total")

print()
print(f"{'template class':<15s} {'matched':>7s} {'deviated':>8s} "
      f"{'hidden from report':>19s}")
for c in ("interactive", "batch", "standby"):
    m, d, h = apply_template(subj, c)
    print(f"{c:<15s} {m:7d} {d:8d} {h:19d}")
```

```
subjects 40 | total field instances 122 | distinct field names 6 | special devices 9

model      field names   covered uncovered full rep. partial rep.
core                 2        80        42        10           30
extended             4       105        17        23           17
full                 5       113         9        31            9

field        devices     value source  first covering model
interface         40           shared                  core
address           40       per-device                  core
priority          11           shared              extended
scheduler         14       per-device              extended
aggregation        8           shared                  full
local_rule         9  unrepresentable                  none

shared            field instances   59  0.484 of total
per-device        field instances   54  0.443 of total
unrepresentable   field instances    9  0.074 of total

template class  matched deviated  hidden from report
interactive          34        6                   6
batch                34        6                   3
standby              34        6                   3
```

## What the Model Can Close

The top table gives what widening the model buys. The two-field **core** model fully represents
only **10** of the forty devices; at least one field of **30** devices falls outside it, and the
uncovered field instances number **42**. When the model grows to four fields, uncovered fields
drop to **17**, and partially represented devices to **17**. In the five-field **full** model,
uncovered fields are **9**, partially represented devices **9**.

The shape of the gain is instructive. Going from two fields to four brings uncovered fields down
from **42** to **17** — a gain of twenty-five fields for two fields added. Going from four to
five buys only **8** fields. Each new field concerns fewer devices than the last, because fields
are not distributed evenly: interface and address are needed on all forty devices, scheduler on
**14**, priority on **11**, aggregation on **8**.

This gives the practical rule of model design. The decision to widen a model is not made by field
count, it is made by **how many devices that field removes from partial representation** — and
this number shrinks at every step.

## What the Model Cannot Close

In the last row, uncovered fields hold at **9**, and widening the model further cannot bring this
number down. The middle table gives the reason: the `local_rule` field's first covering model is
**none**. These nine field instances belong to the forty devices' **9** special devices, and each
is meaningful only to its own device.

The bottom table shows the same thing converted into shares. Of the one hundred twenty-two field
instances, **59** carry a shared value — **0.484** of the total. **54** are per-device, fields
defined in the model but with a value computed separately forty times; their share is **0.443**.
The remaining **9** field instances, **0.074** of the total, enter no model at all.

The sum of these three shares describes what the model actually is. The portion where the lever
truly carries a single value does not even reach half the total. The second share is where the
lever carries a rule and produces forty separate results — the model works here, but without the
simplicity of writing one value to forty devices. The third share is where the lever does not
reach at all.

The bottom table's last rows give this the template side's counterpart: whichever class the
template targets, matched devices are **34**, deviated **6**. All six deviated devices are
special, meaning all carry `local_rule`. But what deserves attention is the others: **three** of
the nine special devices match the template's class every time and **accept** the template —
even while carrying a field the model cannot represent. Deviations hidden from the report are
again **6, 3,** and **3**.

## The Model's Two-Way Limit

The measurement counts one direction of the model's limit: a field the device wants is missing
from the model. There is a second direction, and it is not measured in this setup — **the device
does not accept a field the model declares.** The model defines five fields, the device
recognizes four and rejects the fifth; or it recognizes the field but does not accept the full
range the model permits.

The two directions do not produce the same outcome. The gap in the first direction is clear at
**write time**: while the model is being written, the field's absence is seen, and the decision
is made deliberately. The gap in the second direction surfaces at **apply time**, and not on all
forty devices at once — the devices that accept the field vary, the ones that do not stay fixed.
The result is one of the error modes counted in the previous lesson: order-dependent partial
application. Part of the network is left with the new configuration, part with the old, and the
two behave inconsistently.

This is why validating a model is two-staged. The first stage checks whether the model is
internally consistent, and never reaches the device. The second stage asks whether the model is
applicable on a specific device, and this can only be learned by asking the device. Doing the
second stage before applying the change is what later lessons measure as **dry run**; the
model's own validation does not stand in for it.

## Where a Field Outside the Model Goes

A field outside the model does not vanish; it keeps sitting on the device. Two policies exist,
and both open a surface.

**Hands-off policy.** The management tool never touches a field the model does not recognize.
The device's configuration splits in two: the part managed by the model and the part managed by
hand. In this setup, what stays manual is **9** field instances. Configuration drift lives
exactly in this region, because comparison can only be done on fields the model knows — the
model cannot see a field it does not know has changed.

**Free-form field policy.** The model keeps a field with no structure to carry configuration it
does not recognize, and writes its content to the device as-is. The field is now carried but
**not validated**: type checking, range checking, and comparison do not operate on it. Nine field
instances appear inside the model and receive none of its guarantees.

The counted surfaces and their narrowing follow.

**First surface — unvalidated configuration text.** A free-form field is a path that can reach
forty devices without passing through validation; an error in its content catches on none of the
model's checks. **Narrowing:** the free-form field is named per device and made valid only on
that device; its content becomes part of the device's inventory record, not the template's. This
way, an error in one place stays on one device.

**Second surface — the region the model cannot see.** Under the hands-off policy, part of the
device never enters any comparison, and configuration drift in that region is silent.
**Narrowing:** the unmanaged region's boundary is declared explicitly in the model, and that
region's existence is turned into a line in the report — a line reading "this device has this
many fields outside management" does not show the drift itself, but shows **where it can be**.

**Third surface — secret information entering the model.** Credentials, keys, and access tokens
are not carried inside the configuration model. The model sits in a repository, gets compared,
copied, and reviewed; none of these operations are appropriate for a secret value.
**Narrowing:** the model carries only a **reference**, and the value itself sits somewhere else;
where that somewhere is, is a separate operational decision and not the subject of this course.

## Summary

- A configuration model declares fields' names, types, and requirements independent of any
  device; validation, comparison, and generation can only be done through this declaration.
- As the model widens, covered fields go **80 → 105 → 113**, and partially represented devices
  **30 → 17 → 9**; each new field's gain is smaller than the last.
- Fields' value sources split into three: shared **59** instances (**0.484**), per-device **54**
  instances (**0.443**), unrepresentable **9** instances (**0.074**).
- Nine fields enter no model, because their value is meaningful only to their own device;
  widening the model that far would turn it into the sum of forty separate configurations.
- On the template side, matched devices are **34**, deviated **6**, hidden from the report
  **6 / 3 / 3**; three of the nine special devices accept the template and keep carrying a field
  the model cannot represent.

## Next Step

This lesson measured what the model can say, and assumed the model stays where it is written.
But for a model to be of any use, it has to reach the device: a request goes out from somewhere,
the device receives it, applies it, and returns a response. This path has its own rules, and the
most important one is this — what does sending the same request twice do? The next lesson
measures the programmable interfaces that provide access to configuration, and what a device
being **idempotent** or not changes across forty devices.
