diff --git a/README.fr.md b/README.fr.md index c79b4f41..2a93196c 100644 --- a/README.fr.md +++ b/README.fr.md @@ -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). > diff --git a/README.md b/README.md index 42defa62..268f1521 100644 --- a/README.md +++ b/README.md @@ -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). > diff --git a/docs/limits-acks.json b/docs/limits-acks.json index d84788e1..b9b0ec50 100644 --- a/docs/limits-acks.json +++ b/docs/limits-acks.json @@ -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", diff --git a/docs/limits.md b/docs/limits.md index 50d6e8a2..1aa09f0f 100644 --- a/docs/limits.md +++ b/docs/limits.md @@ -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 diff --git a/docs/roadmap.md b/docs/roadmap.md index 6bee6944..be10ae31 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -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.