Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
>
> **Prouvé** : 384 des 409 opérations montées sont pilotées par un vrai client, à chaque pull request. `scw`, `octl`, `exo`, Terraform et OpenTofu tournent contre l'émulateur en CI, et les machines démarrent réellement : connexion ssh sur le compte par défaut de chaque provider, subnets isolés, pare-feu qui filtre. La chaîne complète est décrite dans [docs/conformance.md](docs/conformance.md).
>
> **Pas prouvé** : quotas, prix, capacité réelle, validation des identifiants, authentification, cohérence à terme. Les 60 sections de [docs/limits.md](docs/limits.md) disent chacune ce qu'elle coûte. Un émulateur avec un seul compte implicite et aucune grille tarifaire devrait inventer ces chiffres, et quelqu'un agirait dessus.
> **Pas prouvé** : quotas, prix, capacité réelle, validation des identifiants, authentification, cohérence à terme. Les 61 sections de [docs/limits.md](docs/limits.md) disent chacune ce qu'elle coûte. Un émulateur avec un seul compte implicite et aucune grille tarifaire devrait inventer ces chiffres, et quelqu'un agirait dessus.
>
> **Inconnu** : 25 opérations sont montées et n'ont jamais été pilotées par un client. Chacune dit pourquoi aucun client officiel ne l'atteint, à la route et dans [docs/routes.md](docs/routes.md). Elles sont comptées plutôt qu'escamotées, une par une, dans [coverage/evidence.json](coverage/evidence.json).
>
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
>
> **Proven**: 384 of the 409 mounted operations are driven by a real client, on every pull request: each pack's own official CLI, and an infrastructure engine wherever a pack admits one, all of them running against the emulator in CI, and machines really boot: an ssh login on each provider's own default account, isolated subnets, a firewall that filters. The whole chain is described in [docs/conformance.md](docs/conformance.md).
>
> **Not proven**: quotas, prices, real capacity, identifier validation, authentication, eventual consistency. The 60 sections of [docs/limits.md](docs/limits.md) each say what one costs. An emulator with a single implicit account and no price list would have to invent those figures, and somebody would act on them.
> **Not proven**: quotas, prices, real capacity, identifier validation, authentication, eventual consistency. The 61 sections of [docs/limits.md](docs/limits.md) each say what one costs. An emulator with a single implicit account and no price list would have to invent those figures, and somebody would act on them.
>
> **Unknown**: 25 operations are mounted and have never been driven by a client. Every one of them states why no official client reaches it, at the route and in [docs/routes.md](docs/routes.md). They are counted rather than glossed, one by one, in [coverage/evidence.json](coverage/evidence.json).
>
Expand Down
1 change: 1 addition & 0 deletions docs/limits-acks.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"The cost of DNS/TLS interception, measured (#76)": "2026-11-11",
"The firewall enforces, within stated bounds": "2026-08-28",
"The guest's DHCP client is not how a published address reaches a machine (#587)": "2026-11-11",
"The lab is disposable, and that is the boundary (#196, #733)": "2026-11-11",
"The per-parameter half: 18 Scaleway list operations, 72 parameters, each served or refused (#277)": "2026-08-29",
"The station reaches an OVN private address only via the network's router, and the posted uplink routes do not go there (#496)": "2026-08-27",
"The three packs hand their security groups to the runtime, within two measured bounds": "2026-08-28",
Expand Down
69 changes: 69 additions & 0 deletions docs/limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,75 @@ this change: an instance snapshot of a block volume is typed `unified`, never
`block/v1alpha1`, and an image cut from a snapshot that broke that promise made
`scw instance image list` fail for the whole zone.

## The lab is disposable, and that is the boundary (#196, #733)

**feint never manages a resource whose loss would matter.** It materialises a
lab on one host, and everything in that lab is rebuilt rather than recovered.
A durable resource — a volume holding bytes somebody wants back, a reconciler
that owns the host's state, quotas over real capacity — belongs to a different
product, and `docs/roadmap.md` has said so from the start: *"a small IaaS above
Incus answers a different question from the one this project answers, and
answering both in one binary is how a repository acquires two products and does
neither well."*

Decided 2026-11-11. It had been filed as #196 and unwritten since, while three
milestones planned the opposite — which is how a reader of either text would
have been right and wrong at once. The sentence above is what stands; what
follows is what it does and does not forbid, because that distinction is the
whole content of the decision.

### Why this is a safety property and not a scope preference

The argument that makes feint safe to run on an operator's own station is one
sentence: **it only touches what it made, and what it made is disposable.**
Both halves carry weight. The first is enforced — every object is labelled, the
driver answers ownership before shape, and `feint clean` sweeps exactly its own
work. The second is what makes the first affordable: a sweep that could destroy
something irreplaceable would need a confirmation, a backup story and a recovery
path, and an emulator that grows those is one nobody dares point at a real
directory.

An audit has already turned a crafted snapshot into `incus delete --force` and
`network delete incusbr0` on a host. Those were refused by ownership checks. The
reason that refusal is enough, rather than merely necessary, is that nothing
feint owns was worth keeping.

### What persistence IS allowed, and why it is not the same thing

The record of a lab may survive a restart. That is #551's subject and it is in
scope: an `apply`, a `kill -9` and a restart that leaves the plan empty is a
convenience for the person using the lab, not a promise about anybody's bytes.

Three properties keep it on this side of the boundary, and a change that breaks
any one of them is a change of product rather than a feature:

- **the record is explicitly rebuildable.** Losing it costs a re-apply, never
data. `feint snapshot save` already writes one on request and
`PUT /_feint/state` restores it;
- **it is never authoritative.** What the runtime holds decides; the record
describes. A restored record naming a machine is an untrusted input, revalidated
at the boundary — see the sections on ownership above;
- **nothing in it is a user's content.** Object Storage bytes are the one place
that could become so, and they are bounded by a byte budget, held under a
directory feint owns, and swept with the rest.

### What this refuses, by name

- a reconciler that observes the host and converges it toward a declaration,
publishing what it observed (#553, #554): that is the other project's control
loop, and it is the point at which feint would start owning state rather than
describing it;
- volumes that hold bytes surviving a host reboot, with protection in each
provider's vocabulary and quotas over them (#555): that is a resource whose
loss would matter, which is the thing this section refuses;
- multi-host anything. `docs/roadmap.md` already puts the scheduler, the IPAM
and the authentication of such a project outside this repository, and #195
draws the HTTP boundary a second backend would speak across.

The refusal is of the work landing **here**, not of the work. A homelab that
materialises durable resources is a legitimate product; it is a different one,
and the line between them is this section.

## What survives a dead emulator, in one table

The store is memory: a dead process loses every emulated resource, and that is
Expand Down
8 changes: 8 additions & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,14 @@ unkeepable promise costs.

### And then the runtime is somebody else's project

> **Clarified 2026-11-11, and it stands.** `docs/limits.md`, *"The lab is
> disposable, and that is the boundary"*, writes the decision #196 had been
> holding open and refuses #553, #554 and #555. One nuance it adds: the
> boundary is a resource whose **loss would matter**, not persistence as such.
> The emulator's own record may survive a restart — it is rebuildable, never
> authoritative, and holds nobody's content — which is why #551 stays in scope
> while durable volumes do not.

The obvious next step is to teach this driver about clusters. It is the wrong
one, and saying why is the most useful thing on this page.

Expand Down
Loading