diff --git a/.github/ISSUE_TEMPLATE/breaking_configuration.yml b/.github/ISSUE_TEMPLATE/breaking_configuration.yml index c37f05f6..b5254229 100644 --- a/.github/ISSUE_TEMPLATE/breaking_configuration.yml +++ b/.github/ISSUE_TEMPLATE/breaking_configuration.yml @@ -59,7 +59,7 @@ body: attributes: label: Versions description: Feint, the client, and the provider — `feint version`, `terraform version`. - placeholder: "feint 0.15.0, terraform 1.13.3, scaleway/scaleway 2.81.0" + placeholder: "feint 0.16.0, terraform 1.13.3, scaleway/scaleway 2.81.0" validations: required: true diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md index 31d42d9b..affbeb11 100644 --- a/CHANGELOG.fr.md +++ b/CHANGELOG.fr.md @@ -17,8 +17,25 @@ change ni l'un ni l'autre a sa place dans `git log`. ## [Unreleased] +## [0.16.0] - 2026-11-11 + ### Ajouté +- **`feint checkpoint` et `feint reset`** (#853) : un point de reprise + enregistre le plan de contrôle et les octets sous un seul nom, et un reset y + ramène le labo. Il détruit ce que le point de reprise ne contient pas, + redémarre ce que l'hôte ne détient plus, et réapplique les jeux de règles de + pare-feu de chaque pack sur le runtime. Le verdict n'est pas celui de + l'opération : il compare ce qu'un client peut observer, le plan de contrôle, + les octets relus à travers les portes et ce que l'hôte détient, et il répond + « did not converge » au lieu de rapporter l'intention. +- **Une route d'installation Linux dans le démarrage rapide** (#843), sous + celle de Homebrew et générée depuis la même version que la page + d'installation. Six lignes, et elles vérifient : les octets sont contrôlés + contre le `checksums.txt` publié avant toute installation. Ce que cela ne + prouve pas est dit plutôt que sous-entendu : cela prouve que les octets + correspondent à la liste, pas qui a écrit la liste, et la signature de la + liste reste dans `docs/install.md`. - **`feint run` possède son laboratoire** (#848) : sans `--endpoint`, il démarre un émulateur, l'attend, résout l'environnement du provider comme le fait `feint env`, lance la commande et arrête tout ce qu'il a démarré. @@ -37,6 +54,28 @@ change ni l'un ni l'autre a sa place dans `git log`. ### Modifié +- **Le chiffre de couverture nomme la population qu'il a comptée.** Il disait + « Of the 548 operations upstream declares » alors que le scan Scaleway couvre + 11 des 56 produits de ce SDK : un tiers du fournisseur était imprimé comme le + fournisseur. Une stack qui utilise un produit hors du scan part vers le vrai + endpoint, et le README dit maintenant de quel scan il parle. +- **Le README ne dit plus que l'object storage est hors périmètre.** Il est + sorti en 0.15.0 ; la phrase était hors des blocs générés, là où `feint docs + --check` ne pouvait pas l'atteindre. +- **Un tag est refusé tant qu'un jalon ouvert réclame une version déjà + publiée** (#877). `v0.14.0` et `v0.15.0` étaient publiées alors que des jalons + portant leur nom restaient ouverts avec 19 et 10 issues : le suivi annonçait + deux versions sorties comme inachevées. `tools/release/milestone-gate.sh` pose + la question avant chaque tag, et dit quand il n'a pas pu mesurer plutôt que de + refuser pour une raison qui ne concerne pas le dépôt. +- **`docs/limits.md` a rattrapé des comportements changés depuis des mois** + (#857). Onze sections citaient des issues fermées depuis la dernière + relecture ; trois étaient fausses, dont un titre annonçant que le provider + Terraform Exoscale est refusé alors qu'il pilote le pack depuis septembre. +- **La frontière de ce produit est écrite** (#196, #733) : `docs/limits.md` + énonce que feint ne gère jamais une ressource dont la perte compterait. Deux + textes courants disaient le contraire l'un de l'autre depuis deux mois, sans + se citer. - **`feint run` laisse la sortie standard à la commande qu'il lance.** Sa propre prose part sur la sortie d'erreur et le reçu JSON dans un fichier. Mesuré plutôt que raisonné : la première version écrivait le reçu sur la @@ -50,6 +89,45 @@ change ni l'un ni l'autre a sa place dans `git log`. signalé. Le reçu rapporte aussi le runtime et les capacités que l'émulateur déclare, et non le mode demandé. +### Corrigé + +- **Un reset remet une règle de pare-feu sur l'interface, pas seulement dans le + store** (#871). `Survey` ne rend que des noms, et un jeu de règles dont le + contenu change garde le sien : rien dans l'observation ne pouvait voir la + différence, et un reset annonçait la convergence pendant que le runtime + appliquait encore les règles d'un état que le point de reprise ne décrit pas. + Reproduit par une connexion TCP sous `--vm incus` avant d'être corrigé. La + même passe a corrigé un reset qui démarrait la machine d'un enregistrement + décrit comme arrêté, puis écrivait « running » par-dessus l'état qu'on lui + demandait de retrouver. +- **Le verdict du reset compare les octets d'un objet**, et plus seulement son + nom (#871). Il lisait l'empreinte dans un champ que `ListObjectsV2` n'émet + pas : le champ était toujours vide, et deux contenus différents sous une même + clé se comparaient égaux. Chaque objet est maintenant récupéré et haché par + l'observateur. +- **`feint run` échoue quand il n'a pas tenu une promesse** (#871). Seul + « did not converge » faisait échouer la commande, donc un reset refusé, un + reset illisible et un labo que la commande possédait sans pouvoir l'arrêter + laissaient tous le code de sortie à celui de l'enfant. +- **Un point de reprise est entier, ou c'est le précédent** (#871). Les octets + étaient enregistrés avant l'instantané du plan de contrôle : un échec à la + seconde étape laissait un point de reprise antérieur du même nom avec son + ancien store à côté des nouvelles références d'objets. +- **Les points de reprise nommés cessent de s'écraser** (#871) : l'émulateur en + détient autant qu'on veut, le CLI en écrivait un par instance, donc + `checkpoint A` puis `checkpoint B` rendait `reset A` impossible. +- **Le crochet pre-commit `gofmt` échoue quand gofmt ne peut pas s'exécuter** + (#829). Il lisait la liste des fichiers à reformater et jamais le code de + sortie : tout échec de gofmt produisait une liste vide, et le crochet + répondait « Passed ». Sa fenêtre est un clone neuf avant `mise trust`, + c'est-à-dire le premier commit d'un nouveau contributeur. +- **Un verdict d'équilibrage est tiré de vingt-quatre connexions, pas six** + (#559). Six, c'est une pièce lancée six fois : un équilibreur qui fonctionne + envoyait toutes les connexions du même côté environ une fois sur trente-deux, + et la suite rougissait sur un sujet qui se comportait bien. Le même calcul + valait dans l'autre sens après un délien, en faux vert sur un vrai défaut. + + ## [0.15.0] - 2026-11-11 ### Ajouté diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e6cbccc..2bb4a3e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,24 @@ what this project is judged on: **a response shape a client can observe**, and ## [Unreleased] +## [0.16.0] - 2026-11-11 + ### Added +- **`feint checkpoint` and `feint reset`** (#853): a checkpoint records the + control plane and the object bytes under one name, and a reset returns the + lab to it — destroying what the checkpoint does not hold, starting again what + the host no longer holds, and putting each pack's firewall rule sets back on + the runtime. The verdict is not the operation's own: it compares what a + client can observe, control plane, bytes read back through the doors, and + what the host holds, and it says `did not converge` rather than reporting the + intention. +- **A Linux install route in the Quick Start** (#843), under the Homebrew one + and generated from the same version the install page is pinned to. Six lines, + and they verify: the bytes are checked against the published `checksums.txt` + before anything is installed. What that does not prove is said rather than + implied — it proves the bytes match the list, not who wrote the list, and the + signature over the list stays in `docs/install.md`. - **`feint run` owns the lab** (#848): with no `--endpoint` it starts an emulator, waits for it, resolves the provider environment the way `feint env` does, runs the command and stops everything it started. `--provider` narrows @@ -34,6 +50,28 @@ what this project is judged on: **a response shape a client can observe**, and ### Changed +- **The coverage headline names the population it counted.** It read "Of the + 548 operations upstream declares" while the Scaleway scan covers 11 of the 56 + products in that SDK, so a third of the provider was printed as the provider. + A stack using a product outside the scan reaches the real endpoint, and the + README now says which scan it is talking about. +- **`README.md` stops saying object storage is out of scope.** It shipped in + 0.15.0; the sentence sat outside the generated blocks, where `feint docs + --check` could not reach it. +- **A tag is refused while an open milestone claims a version already tagged** + (#877). `v0.14.0` and `v0.15.0` were both published while milestones named + after them stayed open with 19 and 10 issues, so the tracker said two + released versions were unfinished. `tools/release/milestone-gate.sh` asks + before each tag, and says when it could not measure rather than refusing for + a reason that is not about the repository. +- **`docs/limits.md` caught up with behaviour that changed months ago** (#857). + Eleven sections cited issues that had closed since anyone last read them; + three were false, including a heading saying the Exoscale Terraform provider + is refused when it has been driving the pack since September. +- **The boundary of this product is written down** (#196, #733): + `docs/limits.md` states that feint never manages a resource whose loss would + matter. Two current texts had said opposite things for two months, neither + citing the other. - **`feint run` leaves stdout to the command it runs.** Its own prose goes to stderr and the JSON receipt to a path. Measured rather than reasoned: the first version wrote the receipt to stdout, and replaying a surveyed stack @@ -45,6 +83,41 @@ what this project is judged on: **a response shape a client can observe**, and that reported it. The receipt also reports the runtime and the capabilities the emulator declares, rather than the mode that was asked for. +### Fixed + +- **A reset puts a firewall rule back on the interface, not only in the store** + (#871). `Survey` answers names, and a rule set whose content changed keeps its + name, so nothing in the observation could see the difference: a reset reported + convergence while the runtime went on enforcing the rules of a state the + checkpoint does not describe. Reproduced with a TCP connection under + `--vm incus` before being fixed. The same pass fixed a reset starting the + machine of a record the checkpoint describes as stopped, and then writing + `running` over the state it had been asked to return to. +- **The reset verdict compares the bytes of an object**, not just its name + (#871). It read the digest from a field `ListObjectsV2` does not emit, so the + field was always empty and two different contents under one key compared + equal. Each object is now fetched and hashed by the observer. +- **`feint run` fails when it did not keep a promise** (#871). Only + `did not converge` used to fail the run, so a failed reset, one that could not + be read back, and a lab this command owned and could not stop all left the + exit code at the child's. +- **A checkpoint is whole or it is the previous one** (#871). The bytes were + saved before the control-plane snapshot, so a failure in the second step left + an earlier checkpoint of that name holding its old store beside the new object + references. +- **Named checkpoints stop overwriting each other** (#871): the emulator holds + any number and the CLI wrote one file per instance, so `checkpoint A` then + `checkpoint B` made `reset A` impossible. +- **The `gofmt` pre-commit hook fails when gofmt cannot run** (#829). It read + the list of offending files and never the exit code, so any failure of gofmt + produced an empty list and the hook reported `Passed`. Its window is a fresh + clone before `mise trust`, which is a new contributor's first commit. +- **A balancer verdict is drawn from twenty-four connections, not six** (#559). + Six is a coin flipped six times: a balancer that works sent every connection + to one side about one run in thirty-two, so the dataplane suite reddened on a + subject that was behaving. The same arithmetic ran the other way after an + unlink, as a false green over a real defect. + ## [0.15.0] - 2026-11-11 ### Added diff --git a/README.fr.md b/README.fr.md index 2a93196c..4f3a32ea 100644 --- a/README.fr.md +++ b/README.fr.md @@ -87,7 +87,7 @@ Sans Homebrew, la même chose sur Linux : ```bash ( set -e - v=0.15.0 + v=0.16.0 case $(uname -m) in x86_64) a=feint-linux-amd64 ;; aarch64) a=feint-linux-arm64 ;; @@ -127,18 +127,18 @@ terraform apply # le vrai provider Scaleway ```yaml services: feint: - image: ghcr.io/stephrobert/feint:v0.15.0 + image: ghcr.io/stephrobert/feint:v0.16.0 ports: ["4599:4599"] ``` -L'image ne sert que le plan de contrôle et ne porte aucun tag `latest` ; [docs/install.md](docs/install.md) donne la forme GitLab, la forme compose et la vérification de signature. Pour la tirer directement : `docker run --rm -p 127.0.0.1:4599:4599 ghcr.io/stephrobert/feint:v0.15.0`. +L'image ne sert que le plan de contrôle et ne porte aucun tag `latest` ; [docs/install.md](docs/install.md) donne la forme GitLab, la forme compose et la vérification de signature. Pour la tirer directement : `docker run --rm -p 127.0.0.1:4599:4599 ghcr.io/stephrobert/feint:v0.16.0`. **Dans GitHub Actions, sans conteneur**, l'action du Marketplace : ```yaml - uses: stephrobert/setup-feint@v1 with: - version: 0.15.0 + version: 0.16.0 provider: scaleway # exporte ce dont le client officiel a besoin ``` diff --git a/README.md b/README.md index 268f1521..53d5273f 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Without Homebrew, the same thing on Linux: ```bash ( set -e - v=0.15.0 + v=0.16.0 case $(uname -m) in x86_64) a=feint-linux-amd64 ;; aarch64) a=feint-linux-arm64 ;; @@ -116,18 +116,18 @@ terraform apply # the real Scaleway provider ```yaml services: feint: - image: ghcr.io/stephrobert/feint:v0.15.0 + image: ghcr.io/stephrobert/feint:v0.16.0 ports: ["4599:4599"] ``` -The image is control-plane only and carries no `latest` tag; [docs/install.md](docs/install.md) has the GitLab form, the compose form and the signature verification. Pull it directly with `docker run --rm -p 127.0.0.1:4599:4599 ghcr.io/stephrobert/feint:v0.15.0`. +The image is control-plane only and carries no `latest` tag; [docs/install.md](docs/install.md) has the GitLab form, the compose form and the signature verification. Pull it directly with `docker run --rm -p 127.0.0.1:4599:4599 ghcr.io/stephrobert/feint:v0.16.0`. **In GitHub Actions without a container** — the action from the Marketplace: ```yaml - uses: stephrobert/setup-feint@v1 with: - version: 0.15.0 + version: 0.16.0 provider: scaleway # exports what the official client needs ``` @@ -256,7 +256,7 @@ the same 6.0.4, and says what to install, which is the point of having it. -Version **0.15.0**, named rather than fetched as `latest`: a mutable reference +Version **0.16.0**, named rather than fetched as `latest`: a mutable reference downloads whatever is newest, which is a binary neither of us can name afterwards and a release adopted the day it is published. This line moves when the CHANGELOG does, and `feint docs --check` fails until it has. @@ -265,7 +265,7 @@ Needs `cosign` (or `gh`, further down) and nothing else. Every file is fetched to disk and verified before anything runs it. ```bash -base=https://github.com/stephrobert/feint/releases/download/v0.15.0 +base=https://github.com/stephrobert/feint/releases/download/v0.16.0 # The published binary for this machine, or a refusal: no default, because # the wrong architecture fails at exec with a message naming neither. @@ -308,7 +308,7 @@ difference between an identity and a provenance: without it the check accepts anything this repository attested, whichever workflow did it. ```bash -gh release download v0.15.0 --repo stephrobert/feint --pattern 'feint-linux-amd64' +gh release download v0.16.0 --repo stephrobert/feint --pattern 'feint-linux-amd64' gh attestation verify feint-linux-amd64 --repo stephrobert/feint \ --signer-workflow stephrobert/feint/.github/workflows/release.yml ``` diff --git a/docs/install.md b/docs/install.md index fdf8cbdd..68ee6e1e 100644 --- a/docs/install.md +++ b/docs/install.md @@ -5,7 +5,7 @@ Feint itself needs nothing: one Go binary, no dependency, no daemon. -Version **0.15.0**, named rather than fetched as `latest`: a mutable reference +Version **0.16.0**, named rather than fetched as `latest`: a mutable reference downloads whatever is newest, which is a binary neither of us can name afterwards and a release adopted the day it is published. This line moves when the CHANGELOG does, and `feint docs --check` fails until it has. @@ -14,7 +14,7 @@ Needs `cosign` (or `gh`, further down) and nothing else. Every file is fetched to disk and verified before anything runs it. ```bash -base=https://github.com/stephrobert/feint/releases/download/v0.15.0 +base=https://github.com/stephrobert/feint/releases/download/v0.16.0 # The published binary for this machine, or a refusal: no default, because # the wrong architecture fails at exec with a message naming neither. @@ -57,7 +57,7 @@ difference between an identity and a provenance: without it the check accepts anything this repository attested, whichever workflow did it. ```bash -gh release download v0.15.0 --repo stephrobert/feint --pattern 'feint-linux-amd64' +gh release download v0.16.0 --repo stephrobert/feint --pattern 'feint-linux-amd64' gh attestation verify feint-linux-amd64 --repo stephrobert/feint \ --signer-workflow stephrobert/feint/.github/workflows/release.yml ``` @@ -176,7 +176,7 @@ One tag per release, the release's own, and nothing mutable: no `latest`, for the same reason the commands above name a version. ```bash -docker run --rm -p 127.0.0.1:4599:4599 ghcr.io/stephrobert/feint:v0.15.0 +docker run --rm -p 127.0.0.1:4599:4599 ghcr.io/stephrobert/feint:v0.16.0 curl http://127.0.0.1:4599/_feint/health ``` @@ -186,7 +186,7 @@ healthcheck answers, so the first step can talk to it immediately: ```yaml services: feint: - image: ghcr.io/stephrobert/feint:v0.15.0 + image: ghcr.io/stephrobert/feint:v0.16.0 ports: - 4599:4599 ``` @@ -195,7 +195,7 @@ In GitLab CI: ```yaml services: - - name: ghcr.io/stephrobert/feint:v0.15.0 + - name: ghcr.io/stephrobert/feint:v0.16.0 alias: feint ``` @@ -210,11 +210,11 @@ just pushed, and `tools/release/preflight.sh` runs it against the previous release before a tag exists. ```bash -cosign verify ghcr.io/stephrobert/feint:v0.15.0 \ +cosign verify ghcr.io/stephrobert/feint:v0.16.0 \ --certificate-identity-regexp '^https://github\.com/stephrobert/feint/\.github/workflows/release\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com -gh attestation verify oci://ghcr.io/stephrobert/feint:v0.15.0 \ +gh attestation verify oci://ghcr.io/stephrobert/feint:v0.16.0 \ --repo stephrobert/feint \ --signer-workflow stephrobert/feint/.github/workflows/release.yml ``` diff --git a/examples/README.md b/examples/README.md index a972b4c5..1d5b4309 100644 --- a/examples/README.md +++ b/examples/README.md @@ -47,7 +47,7 @@ that wants real machines behind `--vm` on a host with Incus: ```yaml - uses: stephrobert/setup-feint@v1 with: - version: 0.15.0 + version: 0.16.0 provider: scaleway # exports what the official client needs - run: terraform apply -auto-approve ``` diff --git a/examples/compose/compose.yaml b/examples/compose/compose.yaml index 01ad4a75..84f7cde1 100644 --- a/examples/compose/compose.yaml +++ b/examples/compose/compose.yaml @@ -14,7 +14,7 @@ services: feint: - image: ghcr.io/stephrobert/feint:v0.15.0 + image: ghcr.io/stephrobert/feint:v0.16.0 # Published on the loopback rather than on every interface. The emulator # accepts any credential and holds its state in memory — SECURITY.md is # explicit that it must not be exposed to a network you do not control. diff --git a/examples/github-actions/terraform.yml b/examples/github-actions/terraform.yml index 991bc48a..bdece71c 100644 --- a/examples/github-actions/terraform.yml +++ b/examples/github-actions/terraform.yml @@ -11,7 +11,7 @@ # # - uses: stephrobert/setup-feint@v1 # with: -# version: 0.15.0 +# version: 0.16.0 # provider: scaleway # # which installs the verified binary and exports the client environment. Use the @@ -36,7 +36,7 @@ jobs: feint: # One tag per release, and no `latest`: a mutable reference pulls # whatever is newest, which is an image nobody can name afterwards. - image: ghcr.io/stephrobert/feint:v0.15.0 + image: ghcr.io/stephrobert/feint:v0.16.0 ports: - 4599:4599 diff --git a/examples/gitlab-ci/.gitlab-ci.yml b/examples/gitlab-ci/.gitlab-ci.yml index f88a5248..8840a587 100644 --- a/examples/gitlab-ci/.gitlab-ci.yml +++ b/examples/gitlab-ci/.gitlab-ci.yml @@ -15,7 +15,7 @@ # fact. Change it when you upgrade, on purpose. variables: - FEINT_IMAGE: ghcr.io/stephrobert/feint:v0.15.0 + FEINT_IMAGE: ghcr.io/stephrobert/feint:v0.16.0 # The service is reachable by its alias inside the job's network, and the # emulator's own port is the same one it uses everywhere else. FEINT_ENDPOINT: http://feint:4599 diff --git a/tools/demo/ci.tape b/tools/demo/ci.tape index 1b6125dc..4c283114 100644 --- a/tools/demo/ci.tape +++ b/tools/demo/ci.tape @@ -70,7 +70,7 @@ Sleep 1200ms # worked, and no viewer could copy the line. A hard-coded version in a recording # is the kind of figure this repository refuses, so tools/demo/ci.sh fails when # this line and the CHANGELOG disagree. -Type "docker run -d --rm --name feint -p 4599:4599 ghcr.io/stephrobert/feint:v0.15.0" +Type "docker run -d --rm --name feint -p 4599:4599 ghcr.io/stephrobert/feint:v0.16.0" Enter Sleep 4s