Milestones
List view
Two sequences of API calls that describe the same cloud must leave the same runtime behind them. Today they do not, and the record proves it: a gateway's `push_default_route` reached a machine when set at creation and not when set afterwards (#678); a machine lost its routes to the peered subnets across a `poweroff`/`poweron` while its never-restarted neighbour kept them (#549); an ordinary `CreateSubnet` severed an accepted peering because two reconcilers wrote one runtime state from two truths (#508); a machine holding a public address gets its private NIC managed by nobody (#684) and, once managed, a dead on-link `/32` that kills every reply to the station (#660, #672, #695). Each was fixed where it was found. None of them was a bug in the sense of a wrong line: each is the runtime reasoning "when this event arrives, perform these gestures" where the cloud it emulates reasons "this is the topology that must exist now". 0.12.0 closed the door between the packs and the runtime: no pack can name Incus or OVN, and `Plan` executes one order for three providers. What is behind that door has no written model. Measured on 2026-09-10: `internal/core/machine` is 45 non-test files and 16,113 lines, `incus_ovn.go` alone is 1,897, and its comments are the only place where the sequences of peering, lease, resolver and reboot are recorded; `internal/core/network` is 312 lines of IPAM; `docs/adr/` and `docs/runtime/` do not exist. Those figures were 44, 15,843 and 1,812 when this milestone opened: the undocumented driver grew by 270 lines while the milestone to document it was open, which is the argument for the freeze rather than an aside. The capabilities have started encoding topologies the model cannot express: `FirewallPublicOnly`, `FirewallPublicWhenJoined`, a `Balancing` claim bounded by a paragraph of shapes. This milestone writes the contract down and makes it executable, in that order, and adds no runtime capability while it does so (#732). **The invariant it establishes:** the desired final topology is authoritative over the history of events that produced it. Creation order, hot attach, stop/start, reboot and a Day-2 update are all supposed to converge on the same observable runtime, and a suite that drives the same desired state in several orders is the fitness function that says whether they do. **Two halves, and the second one is the one that was missing.** A contract that defines only the desired topology cannot be tested: something has to say what a witness reads, what is normalised away before two readings are compared, and when a reading has been stable long enough to be a verdict. #754 owns that half. `deny`, `did-not-converge` and `unreadable` are three different answers, and a suite that collapses them reports the weather as the subject. A fixed `sleep N` is never the contract. **What ships:** `docs/runtime/model.md`, versioned `Contract version: 1`, with one normative section per effect; `docs/adr/` with the structuring decisions and every rejected alternative, measured, superseded rather than rewritten; the normalisation and settling model of #754; `tools/runtime-conformance/`, which knows no provider and drives topologies through their permutations and their whole lifecycle; a bounded runtime smoke on every pull request, shipped **advisory** and promoted to a required check only on its measured cost and false-red rate (#730, then #125), because promising a gate before measuring it is the thing this repository refuses to let a provider do; and the release gate of #755, itself falsified before it is trusted. **Exit rule.** Each row of #735's table carries all seven cells: normative section, provider mapping, runtime-mode verdict, ADR where a decision was taken, nominal test, Day-2 test, normalised witness. Thirteen rows: VPC, subnet, NIC, peering, routing, Internet, public IP, DNS, firewall, load balancer, compute lifecycle, cleanup, block storage. `unsupported`, `not applicable` and `control-plane only` are answers; an unmeasured cell is not, and no Incus or OVN behaviour is invented to fill one. Until all thirteen rows are full, the contract is not v1 and no new runtime behaviour lands. **The instruments come first.** #737 (the falsify suite concludes rather than being cancelled by its own timeout), #740 (a runtime feint itself reported broken cannot pass a night) and #679 (the runtime leg can enter a machine it booted) precede every claim below them: twelve ADRs written on a suite that does not conclude are twelve decisions nobody can check. The full order is in #735. **This milestone has failed if:** an ADR is written and the code it describes has no test that fails without it; a rejected alternative is recorded without the measurement that rejected it; a scenario proves a property once, after creation, and never again; a witness reports `deny` for something it could not read; a duration becomes the definition of settled; a capability is published that points at no contract effect and no executable witness; the package tree is reorganised before the behaviour it must protect is written; or a pull request touching the runtime can merge with the runtime contract unmeasured.
No due date•5/24 issues closedThe runtime can be a machine you are not on. The protocol is the 0.12 contract over HTTP — the closed driver surface (#511), spoken by runtime-blind packs (#516), proven expressible by a pack that names no runtime at all (#517). It is never the Incus API, and the control plane on the far side is another repository: #195 drew that boundary when it replaced a cluster feature request with a sharper question, and this milestone is its implementation, not its renegotiation. What must hold: no Go type shared between the two repositories; no provider name on the wire; the single-host path unchanged — same tests, same suites, no new branch in any pack; a capability the remote plane does not declare counts as absent, and a check that needs it skips with the reason rather than asserting what nobody promised. This milestone has failed if: the boundary leaks (a shared package, a provider name, an Incus verb in the protocol); feint grows a scheduler, an IPAM or an authentication store for the remote plane; the single-host path pays anything; or "authenticated" is allowed to read as a security boundary — 0.17 already wrote the honest limit, and distance does not repeal it.
No due date•0/1 issues closedA volume holds bytes, and the bytes survive the host rebooting. Losing them is made hard in the provider's own words: `protected` (Scaleway), `DeletionProtection` (Outscale) and `termination-protection` (Exoscale) are honored, and a refused quota answers in the dialect the real cloud uses — `quotas_exceeded` is a named error type in `scw/errors.go`, and `ReadQuotas` and `list-quotas` are surface the other two publish. Two facts gate this milestone: - **The 0.12 contract has no storage intent.** `plan.go` and `machine.go` name no volume and no disk: a feint volume today is bookkeeping with no bytes behind it. The first contract extension since 0.12 is the work, and the fourth provider (#517) is where it proves neutral. - **#196 is the decision this milestone takes.** "Does feint ever manage a resource whose loss would matter?" — answering yes implicitly, by shipping, is exactly what that issue warns against. The milestone does not open until the decision is written in `docs/limits.md`, and every guard it requires is an issue filed before the fact, not discovered after. Fidelity is not suspended in the homelab: every refusal an official client can see must be one the real cloud could send. Anything the controller refuses beyond the provider's own vocabulary lives outside the API path — `/_feint`, the exit code, the operations journal — and is documented as a divergence in `docs/limits.md`, with the personality named. This milestone has failed if: a "durable" volume loses bytes across a reboot; an official client receives an error shape no real cloud could produce; `feint clean` — a lab gesture — can sweep a protected volume; or protection is a feint invention instead of the three fields the SDKs already carry.
No due date•0/2 issues closedThe store stops being assumed true. On start and on demand, the controller observes what the runtime actually holds, converges it toward what was declared, and publishes only what it observed. "The state published is what the effect produced" is already the per-operation rule; this milestone makes it hold across a restart, and behind the operator's back. The defects that motivate it are measured: a machine stopped and started loses its routes to the peered subnets and nothing says so (#549); the reboot action restarts nothing and the API answers "running" (#547). Both are divergences between the record and reality that only observation can catch. Two rules are not negotiable, and both have history: - **Nothing that does not carry feint's own label is ever adopted, counted as drift, or deleted.** An audit obtained `network delete incusbr0` on the host's default bridge; `mustOwn` exists because of it. Adoption is an explicit, per-object act that writes the label before the store learns the name — never a sweep. - **An observation failure is not an absence.** A reader with two outcomes reports *error* as *not found* — measured here when a live account was reported empty for forty minutes. A reconciler with that defect converges by deleting the record, or the object; either way it destroys precisely when it is blind. This milestone has failed if: an unlabeled host object is ever named in a destructive command; a runtime error is read as "gone"; a reconcile undoes what a concurrent operation just did (the `Put`-resurrection lesson, this time between two loops); or the emulator personality starts reconciling — it must keep answering instantly from its store, because that is what an emulator is.
No due date•0/2 issues closedA resource the API acknowledged outlives the process that acknowledged it. `terraform apply`, `kill -9`, restart, `terraform plan` — and the plan is empty; then `destroy`, another restart, and the resource is really gone, not resurrected. Today `docs/limits.md` says a restart starts empty, and it is honest. `Resource.Runtime` documents itself as "must survive a restart", and nothing makes that true except a manual `snapshot save` — a round-trip that already changes what it carries (#542: a stored number comes back a float64). This milestone makes the sentence true, and makes it true for exactly one personality. Two boundaries, both deliberate: - **`feint serve` keeps starting empty.** The emulator is fast, disposable and permissive, and every CI leg depends on a fresh one (`shapes --check` reads an emulator where only the catalogue answers — the #389 lesson). Persistence is the controller personality's promise. A flag that quietly gives the emulator a memory would hand a stale population to every gate that assumes a fresh one. - **What is restored is an input, not a fact.** The snapshot format is designed to travel between instances, and a crafted one has already produced `incus delete --force` on an operator's own instance. The persistent store loads through the same validation a request gets, or it does not load. Implementation is held to rule 6: `CGO_ENABLED=0` is enforced by the release build (`release.yml`, `Dockerfile`, `mise.toml`), so any SQL engine means either cgo or a transpiled dependency tree, and neither is justified before a measured need. The standard library writes an append-only journal and an atomic rename; the store's mutex is already the transaction, and the keyed map is already the uniqueness constraint. This milestone has failed if: a write the API acknowledged is missing after `kill -9`; a deleted resource comes back after a restart; a restored number has changed type; or the emulator personality pays for any of it — in startup time, in a file it did not ask for, or in a CI leg that stopped being fresh.
No due date•0/2 issues closedFeint tells apart who is calling, in which scope, and why an operation is allowed or refused. Authentication compatibility, resource ownership and authorization are measured **independently** — and serving an IAM API never implies its policies are enforced. That last sentence is the whole milestone. This repository already knows the failure it names: `capabilities.balancing: true` was true about the runtime and said nothing about whether a pack delivered it, and a consumer following this project's own advice would have asserted a distribution nobody promised (#481). `CreatePolicy` served must never read as `CreatePolicy` enforced. **The order is deliberate, and it is the opposite of the tempting one.** Serving `CreatePolicy`, `CreateAPIKey` and `ListApplications` would buy API coverage in an afternoon and leave a policy that changes nothing about what `CreateServer` does. So: observe what the real clients send → a provider-neutral Principal → resources belong to a scope → authentication refusals, opt-in → one allowed and one denied path per provider → the evidence axes that publish it → the IAM APIs real clients actually demand Measurement first, generalisation second — the method that has served this project throughout. **Two boundaries that must be written before any code.** The core carries the pipeline and the decision; each pack keeps its own dialect, because a provider-neutral policy language would be feint inventing a cloud rather than emulating three. And permissive stays the default: a beginner running `terraform apply` against a local emulator must not pay for an authorization engine they did not ask for. Enforcement is something you turn on. **And the honest limit, stated in the documentation rather than discovered:** feint's authentication is a compatibility and authorization-testing mechanism, not a security boundary protecting the emulator from a hostile user on the same machine. Fake credentials remain fake. A milestone that let anyone believe otherwise would have shipped a false claim about safety, which is worse than shipping none. It comes after 0.13.0 because an authorization layer built over undifferentiated errors reports the wrong refusal in the right status, and after 0.16.0 because the machinery that makes a claim inspectable is what will make `authn` and `authz` readable rather than two more numbers.
No due date•0/4 issues closedThe proofs feint already holds become directly usable by a person, by CI, and by the runtime. Nothing here adds an API: it makes what is measured legible, enforceable, and comparable between releases.
No due date•1/10 issues closedA Feint environment stops being shell history. feint.yaml declares how the local cloud is brought up; feint up and down reproduce it; the same declaration runs locally and in CI without silently weakening its guarantees. feint exec scopes real clients to the environment, feint verify proves declared reachability, and controlled mutations let Terraform see drift it did not create. Terraform/OpenTofu remain the source of truth for infrastructure. Feint describes and verifies the lab around them; it does not become another IaC language.
No due date•0/10 issues closed