---
title: 'Programmable Interfaces'
source: 'https://academia.sh/en/courses/network-operations/programmable-interfaces'
course: 'Network Operations and Automation'
language: en
updated: '2026-08-17T18:07:15+00:00'
license: 'CC BY-SA 4.0'
---

# Programmable Interfaces

When the same change is applied through three request forms, the append form changes state on a second application in all forty of forty devices; when nine devices with no response are retried, append breaks five of them, the two idempotent forms break neither, but full replace erases nine devices' out-of-model field.

The previous lesson measured what a configuration model can say, and assumed the model stays
where it is written. 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.

The most important one comes down to a single question: **what does sending the same request
twice do?** The question looks theoretical, but it grows out of operations' most common
situation — the response never arrives, and the operator cannot know whether the request was
applied. What this lesson measures is not the interface's speed or elegance, it is the answer to
this question.

## What the Interface Provides

The oldest way to send configuration to a device is opening a shell session and typing commands.
This path has three flaws from automation's point of view, and all three come from the same
root: **the output is formatted for a human.**

A script can only learn whether a command succeeded by parsing the output text. If the text's
format changes, the script silently reads it wrong. What comes back on failure is not an error
code, it is a sentence. And there is no concept of multiple commands applying together — if the
third command is rejected, the first two are already in effect.

A **programmable interface** closes all three at once. Both the request and the response are
structured according to the previous lesson's model; fields arrive by name, and there is no text
to parse. An error returns as a processable structure: it states which field violated which
constraint. And the change can be wrapped in a **transaction boundary** — the request is first
accumulated as a **candidate configuration**, validated, then committed in a single step.

```text
# taught request/response transcript, not run

request
  target  : device/34
  op      : merge
  body    : { priority: 3 , rule: [ base , template_rule ] }

response
  status  : accepted
  changed : [ priority , rule ]
  txn     : 7f
```

The response's `changed` list is the interface's most useful field: it tells you what actually
changed on the device. If the list returns empty, the request was applied but changed nothing —
the device was already in the desired state.

## Three Request Forms

The same change can be requested in three separate forms, and the three do not leave the same
result on the device.

**Append.** The request says "add this rule to the list." The device puts one more row in the
list. Sent twice, the list gets two rows.

**Merge.** The request says "these fields have this value." The device matches fields by key and
overwrites them; it does not repeat the same entry in a list. Sent twice, the second does
nothing.

**Full replace.** The request gives the entire configuration. The device discards what it has
and sets up exactly what the request says. Sent twice, the second does nothing — but the first
sending has also erased every field not present in the request.

This is what makes an interface **idempotent**: if applying the request once and applying it
twice leave the same state, the interface is idempotent. Idempotence is not a property of speed
or correctness; it is a property of **retryability**.

## Response Loss and Retry

Why idempotence is an operational problem shows up in a single setup. The request is sent, the
device receives and applies it, but the response is lost on the way back. The operator has no
information: the request may never have reached the device, or it may have been applied. The two
are indistinguishable from outside.

In this situation, the only thing to do is retry. On an idempotent interface, retrying is free —
if the request was applied, the second one does nothing, and if it was not, it applies it. On an
interface that is not idempotent, retrying **breaks the devices that already succeeded**. And
what breaks are exactly the devices where the request worked.

The assumptions the measurement rests on:

- **AC13** — The forty-device subject set is the course's constant. Each device's state before
  the change is derived from its attributes: interface, address, and a base rule; on a special
  device, additionally a local rule the model does not carry.
- **AC14** — The three request forms carry the **same** change: adding a rule and setting
  priority. The difference is only in the request's form, not its content.
- **AC15** — The idempotence criterion is a comparison of states: if applying the request once
  and applying it twice leave the same state, the interface is idempotent. The measurement does
  not look at the interface's own claim.
- **AC16** — Response loss is the setup's parameter: one in five devices gets no response, and
  roughly half of those with no response have actually had the request applied. This is not a
  measurement; if the rate changes, every row scales in the same direction.
- **AC17** — The retry sends the same request again to every device that got no response. The
  operator cannot know which ones were applied; if they could, there would be no need to retry.
- **AC18** — 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
"""Programmable interface: what sending the same request twice does.

Part 1 - three request forms, the difference a second application leaves.
Part 2 - when the response is lost, what a retry breaks.
Part 3 - the tail: what falls outside the template and what the report cannot see.
"""
SEED = 20260812
EXTRA = "template_rule"
FORMS = ("append", "merge", "full replace")


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 state(o):
    """The device's configuration before the change."""
    d = {"interface": "main", "address": f"a{o['no']}", "rule": ["base"]}
    if o["special"]:
        d["local_rule"] = f"k{o['no']}"
    return d


def request(d, form):
    """The same change is applied through three separate request forms."""
    y = {k: (list(v) if isinstance(v, list) else v) for k, v in d.items()}
    if form == "append":
        y["rule"] = y["rule"] + [EXTRA]
    elif form == "merge":
        y["rule"] = [k for k in y["rule"] if k != EXTRA] + [EXTRA]
    elif form == "full replace":
        y = {"interface": "main", "address": d["address"], "rule": ["base", EXTRA]}
    y["priority"] = "3"
    return y


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()
print(f"subjects {len(subj)} | devices carrying an out-of-model field "
      f"{sum(o['special'] for o in subj)} | request forms {len(FORMS)}")
print()
print(f"{'request form':<14s} {'second application changes':>27s} {'idempotent':>10s} "
      f"{'out-of-model field erased':>26s}")
for b in FORMS:
    changed = erased = 0
    for o in subj:
        d = state(o)
        first = request(d, b)
        second = request(first, b)
        if second != first:
            changed += 1
        if "local_rule" in d and "local_rule" not in first:
            erased += 1
    print(f"{b:<14s} {changed:27d} {'no' if changed else 'yes':>10s} "
          f"{erased:26d}")

print()
r = make_rng(SEED + 11)
no_response, actually_applied = [], set()
for o in subj:
    if r(5) == 0:
        no_response.append(o["no"])
        if r(2) == 0:
            actually_applied.add(o["no"])
print(f"devices with no response {len(no_response)} | of these, request actually "
      f"applied {len(actually_applied)}")
print()
print(f"{'request form':<14s} {'retried':>7s} {'reached correct state':>22s} "
      f"{'broken':>7s}")
for b in FORMS:
    correct = broken = 0
    for no in no_response:
        o = subj[no - 1]
        d = state(o)
        target = request(d, b)
        before = target if no in actually_applied else d
        after = request(before, b)
        if after == target:
            correct += 1
        else:
            broken += 1
    print(f"{b:<14s} {len(no_response):7d} {correct:22d} {broken:7d}")

print()
print(f"{'template class':<15s} {'matched':>7s} {'deviated':>8s} "
      f"{'hidden from report':>19s} {'accepted template, carries out-of-model field':>47s}")
for c in ("interactive", "batch", "standby"):
    m, d, h = apply_template(subj, c)
    risky = sum(1 for o in subj if o["special"] and o["class"] == c)
    print(f"{c:<15s} {m:7d} {d:8d} {h:19d} {risky:47d}")
```

```
subjects 40 | devices carrying an out-of-model field 9 | request forms 3

request form    second application changes idempotent  out-of-model field erased
append                                  40         no                          0
merge                                    0        yes                          0
full replace                             0        yes                          9

devices with no response 9 | of these, request actually applied 5

request form   retried  reached correct state  broken
append               9                      4       5
merge                9                      9       0
full replace         9                      9       0

template class  matched deviated  hidden from report   accepted template, carries out-of-model field
interactive          34        6                   6                                               3
batch                34        6                   3                                               3
standby              34        6                   3                                               3
```

## The Measure of Idempotence

The top table separates the three forms. In the **append** form, the second application changes
state in all forty of forty devices; the interface is not idempotent. In the **merge** and
**full replace** forms, the second application changes nothing on any device; both are
idempotent.

All forty of forty is notable. A violation of idempotence is not a minority event: in the append
form, **every** device breaks on the second application, because the cause of breaking is not a
device attribute, it is the request's form. This is the opposite of the other tails measured in
this course — here there is no tail, the lever misserves all forty of forty the same way.

The right-hand column gives the second distinction, and shows that idempotence alone is not
enough. **Full replace** is idempotent, but it erases **9** devices' out-of-model field. Since
the request does not carry that field, the device treats it not as missing but as **extra**, and
discards it. The previous lesson's nine `local_rule` fields vanish here — and the interface
reports no fault at all, because this is exactly what the requested operation is defined to do.

So idempotence and safety are two separate properties. An interface that is not idempotent
breaks on retry; an interface that is idempotent but uses full replace erases on the **first**
attempt. The form standing at the intersection of the two is **merge**: it is both retryable and
preserves the fields it does not touch.

## What Retrying Costs

The middle table runs the setup. **9** of the forty devices get no response, and **5** of those
actually had the request applied. The operator has no information to make this distinction; all
nine of the nine have to be retried.

In the append form, devices reaching the correct state after retry are **4**, broken **5**. The
five broken devices are exactly the ones where the request worked the first time. The shape of
this result is the worst possible from an operational standpoint: the procedure does damage
exactly where it succeeded, and the damage scales with the success.

In the merge and full replace forms, all nine of the nine devices reach the correct state, broken
**0**. This is exactly what idempotence buys, and nothing more: it makes retrying **safe to do
without thinking**.

In a set of forty subjects, the smallest measurable difference is 1/40 = 0.025. Five broken
devices are a share of **0.125**, comfortably inside the band.

## What Idempotence Does Not Protect Against

Idempotence protects the operator against **their own** repetition. There is a situation it does
not protect against, and it arises from the interface's read side.

A script's typical loop is three steps: read the device's state, compute the change on top of
it, write the result. Reading is idempotent, writing can be idempotent; but the three together
are not a single unit. If two operators run this loop on the same device at the same time, the
second writes after the first has read, and overwrites the first's write without ever seeing it.
The result is not the sum of two changes, it is the second one's result alone; and no request
returns an error, because each request is valid on its own.

Idempotence does nothing here. Sending the same request twice is not the problem; the problem is
**two different requests interleaving**. The interface has two tools against this. The first is
the transaction boundary: while the change accumulates in the candidate configuration, the
device holds a lock, and the second operator's write is rejected. The second is a **stamp**:
every configuration gets a stamp, the request says "I saw this stamp, I am writing on top of
it," and if the stamp has changed, the request is rejected. Neither solves the problem, both
**make it visible** — a rejected request is not an error, it is a notification.

## Surfaces the Interface Opens and Their Narrowing

A programmable interface is a path that can change a device's configuration; by definition, this
makes it a surface. The counted surfaces and their narrowing follow.

**First surface — the management path itself.** If the interface is open, the device's
configuration can be changed over the network. **Narrowing:** the interface connects to a
separate management network and is unreachable from the path data traffic takes; access is
restricted by source address; read authority and write authority are granted separately. These
three narrowings do not substitute for one another, they stack.

**Second surface — where the credential sits.** A script uses an identity to connect to the
device, and that identity sits somewhere. Credentials, keys, and access tokens are not written
in any example in this course. **Narrowing:** the value does not sit inside the script, the
template, or the configuration repository; the script carries only a reference and gets the
value from its runtime environment.

**Third surface — the field full replace erases.** In the measurement, **9** devices lose a
field the request does not carry. This is not an attack, it is the interface's defined behavior,
and that is exactly why it is dangerous — it produces no warning. **Narrowing:** full replace is
used only when the entire configuration is genuinely known; when it is not, merge is used.
Devices carrying an out-of-model field are listed in advance, and the operation is halted when
full replace's scope intersects that list.

**Fourth surface — a change that cuts off its own access.** A request that shuts down the
management interface itself, changes the management network's address, or filters access makes
the device unreachable the moment it is applied. Such a request's command is not written in
runnable form in this course; what can be said is that it is irreversible. **Narrowing:** the
change is first written as a candidate configuration, its outcome is computed with a dry run,
then it is committed with a **rollback timer** — if the operator does not confirm within a set
period, the device automatically reverts to the previous configuration. The timer is the only
tool left in the hands of an operator who has lost access.

## Summary

- A programmable interface provides structured requests and responses, processable errors, and
  a transaction boundary; it closes all three flaws of a script parsing shell output at once.
- The append form is not idempotent: a second application changes state in **40** of forty
  devices. Merge and full replace are idempotent, **0** in both.
- **5** of the **9** devices with no response actually had the request applied; on retry, the
  append form breaks these **5**, the two idempotent forms break **0**.
- Idempotence and safety are separate: full replace is idempotent but erases **9** devices'
  out-of-model field on the first application, and reports no fault.
- On the template side, matched devices are **34**, deviated **6**, hidden from the report
  **6 / 3 / 3**; **3** of the devices accepting the template carry an out-of-model field, and
  they are exactly the ones that would lose it under full replace.

## Next Step

This lesson measured what a single request leaves on a single device. In operations, a request
is never singular: a script connects to forty devices in sequence, applies the same template to
each, and writes a report at the end. The next lesson measures that script itself — a bulk
change precomputed with a dry run, spread in batches, and rolled back. And it is there that we
answer the real question: when the template is applied, how many devices fall outside it, and
how many of those devices appear in the report that gets written.
