From afa66d4d13831fb9ff5403c8ec5779d0067fd9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20ROBERT?= Date: Fri, 28 Aug 2026 23:40:22 +0200 Subject: [PATCH 1/2] docs: the four claims the capability matrix did not reach, and the gate caught the author of this commit (#591) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An external audit read `main` on 2026-08-28 and found four sentences the matrix #592 shipped does not yet cover. Each was measured in the files before being touched. **The Quick Start's four commands install feint and not the engine they run.** `examples/quickstart/scaleway/feint.yaml` declares `iac.engine: terraform` and its `main.tf` requires `>= 1.7.0`, so a reader on a bare machine cannot reach the `Apply complete!` the block prints underneath — #593's own complaint, one level out. The block now names Git and the engine above the commands, and the version is read from the declaration and the `required_version` rather than typed beside them: a number written next to a file that owns it goes stale in silence, which is exactly what happened to the resource count. **"No credentials" was not what is true.** The quickstart carries a fake access key and secret key on purpose, because the official client requires syntactically valid values. It says "no cloud credentials" now, in both languages. **Two sentences asserted an implicit matrix.** `docs/install.md` said "`scw`, `octl`, `exo` and Terraform drive it" without saying which packs an engine drives. And the safety block said "`scw`, `octl`, `exo`, Terraform and OpenTofu run against the emulator in CI". That last one is the part worth recording. The first rewrite of it — naming three CLIs and two providers in one unit — was **refused by the gate #592 shipped two hours earlier**, four times over: `exo`×Outscale, `exo`×Scaleway, `octl`×Scaleway, `scw`×Outscale, none of which the matrix carries. The claim reader treats a unit naming several packs and several clients as asserting every pair, which is the argument that lot's own report made, and which the author of this commit then walked into. The sentence now names no pair at all: each pack's own official CLI, and an infrastructure engine wherever a pack admits one. The repository description on GitHub carried the same ambiguity and is corrected by hand — it is outside everything `feint docs --check` can reach, and that is worth knowing rather than assuming a gate covers it. Co-Authored-By: Claude Opus 5 (1M context) --- README.fr.md | 5 ++- README.md | 7 +++- docs/install.md | 6 +-- internal/cli/capability_render.go | 4 +- internal/cli/docs_banner.go | 3 +- internal/cli/docs_quickstart.go | 70 +++++++++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 9 deletions(-) diff --git a/README.fr.md b/README.fr.md index cf1185a3..7522c8ac 100644 --- a/README.fr.md +++ b/README.fr.md @@ -53,7 +53,7 @@ **Pointez Terraform et les CLI officielles vers votre propre machine.** -Aucun compte cloud, aucun identifiant, et rien de créé nulle part. +Aucun compte cloud, aucun identifiant de cloud, et rien de créé nulle part. Terraform et OpenTofu pilotent Scaleway et Outscale. Chaque pack a en plus son CLI officiel, et chacun d'eux pilote cet émulateur de bout en bout : @@ -71,6 +71,9 @@ son CLI officiel, et chacun d'eux pilote cet émulateur de bout en bout : **Sur votre machine**, un binaire statique et une stack assez courte pour se lire : +> Il vous faut **Git** et **Terraform >= 1.7.0**. feint lui-même n'a aucune dépendance +> d'exécution : c'est la stack qui réclame le moteur. + ```bash brew install stephrobert/feint/feint git clone https://github.com/stephrobert/feint diff --git a/README.md b/README.md index 44180183..35edaf1f 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ > [!IMPORTANT] > **What is safe to point at this emulator, and what is not.** > -> **Proven**: 350 of the 373 mounted operations are driven by a real client, on every pull request. `scw`, `octl`, `exo`, Terraform and OpenTofu run 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). +> **Proven**: 350 of the 373 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 50 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. > @@ -42,7 +42,7 @@ **Point Terraform and the official cloud CLIs at your own machine.** -No cloud account, no credentials, and nothing created anywhere. +No cloud account, no cloud credentials, and nothing created anywhere. Terraform and OpenTofu drive Scaleway and Outscale. Each pack also has its own official CLI, and every one of them drives this emulator end to end: @@ -60,6 +60,9 @@ own official CLI, and every one of them drives this emulator end to end: **On your machine** — one static binary, and a stack short enough to read whole: +> You need **Git** and **Terraform >= 1.7.0**. feint itself has no runtime +> dependency: it is the stack that asks for the engine. + ```bash brew install stephrobert/feint/feint git clone https://github.com/stephrobert/feint diff --git a/docs/install.md b/docs/install.md index 814a77ea..a433e456 100644 --- a/docs/install.md +++ b/docs/install.md @@ -101,9 +101,9 @@ nothing is signed until it is released: go install github.com/stephrobert/feint/cmd/feint@latest ``` -That gives you the control plane — every API answers, `scw`, `octl`, `exo` -and Terraform drive it, and nothing runs. It is what CI uses and it needs no -prerequisite at all. +That gives you the control plane — every API answers, `scw`, `octl` and `exo` +drive their own packs, Terraform and OpenTofu drive Scaleway and Outscale, and +nothing runs. It is what CI uses and it needs no prerequisite at all. ## With Homebrew diff --git a/internal/cli/capability_render.go b/internal/cli/capability_render.go index 425b3c09..d038cb3b 100644 --- a/internal/cli/capability_render.go +++ b/internal/cli/capability_render.go @@ -147,7 +147,7 @@ func renderPromise(french bool) (string, error) { if french { b.WriteString("**Pointez Terraform et les CLI officielles vers votre propre machine.**\n") - b.WriteString("Aucun compte cloud, aucun identifiant, et rien de créé nulle part.\n\n") + b.WriteString("Aucun compte cloud, aucun identifiant de cloud, et rien de créé nulle part.\n\n") b.WriteString(wrapParagraph(fmt.Sprintf( "%s pilotent %s. Chaque pack a en plus son CLI officiel, et chacun d'eux pilote cet "+ "émulateur de bout en bout :", @@ -155,7 +155,7 @@ func renderPromise(french bool) (string, error) { b.WriteString("\n") } else { b.WriteString("**Point Terraform and the official cloud CLIs at your own machine.**\n") - b.WriteString("No cloud account, no credentials, and nothing created anywhere.\n\n") + b.WriteString("No cloud account, no cloud credentials, and nothing created anywhere.\n\n") b.WriteString(wrapParagraph(fmt.Sprintf( "%s drive %s. Each pack also has its own official CLI, and every one of them drives "+ "this emulator end to end:", diff --git a/internal/cli/docs_banner.go b/internal/cli/docs_banner.go index 72441c42..a40258ff 100644 --- a/internal/cli/docs_banner.go +++ b/internal/cli/docs_banner.go @@ -196,7 +196,8 @@ func renderSafety(facts safetyFacts, french bool) string { b.WriteString("> [!IMPORTANT]\n") b.WriteString("> **What is safe to point at this emulator, and what is not.**\n>\n") fmt.Fprintf(&b, "> **Proven**: %d of the %d mounted operations are driven by a real client, on every "+ - "pull request. `scw`, `octl`, `exo`, Terraform and OpenTofu run against the emulator in "+ + "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).\n>\n", facts.Driven, facts.Mounted) diff --git a/internal/cli/docs_quickstart.go b/internal/cli/docs_quickstart.go index 13db174c..3a908338 100644 --- a/internal/cli/docs_quickstart.go +++ b/internal/cli/docs_quickstart.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "strings" ) @@ -91,6 +92,32 @@ func renderQuickstart(goMod, changelog string, french bool) (string, error) { } else { b.WriteString("**On your machine** — one static binary, and a stack short enough to read whole:\n\n") } + + // What the reader needs that these four commands do not install. + // + // They install feint, clone, change directory and run — and the third one + // runs the engine the stack declares, which is not feint. Measured + // 2026-08-28: a reader on a machine carrying neither Git nor Terraform + // cannot reach the `Apply complete!` this block prints underneath, which is + // #593's own complaint one level out: a Quick Start that displays an output + // its own commands cannot produce. + // + // The engine and its floor are read from the stack's own declaration and its + // `required_version`, never typed here: a number written beside a file that + // owns it is a number that goes stale in silence, and this block exists + // because that happened to the resource count. + if engine, floor, ok := quickstartEngineFloor(stack); ok { + if french { + fmt.Fprintf(&b, "> Il vous faut **Git** et **%s %s**. feint lui-même n'a aucune dépendance\n"+ + "> d'exécution : c'est la stack qui réclame le moteur.\n\n", + engineName(engine), floor) + } else { + fmt.Fprintf(&b, "> You need **Git** and **%s %s**. feint itself has no runtime\n"+ + "> dependency: it is the stack that asks for the engine.\n\n", + engineName(engine), floor) + } + } + b.WriteString("```bash\n") fmt.Fprintf(&b, "brew install %s/%s\n", slug, pathBase(slug)) fmt.Fprintf(&b, "git clone https://github.com/%s\n", slug) @@ -364,3 +391,46 @@ func writeSplicedTranslatedBanner(root string, order []string, routes map[string } return os.WriteFile(path, []byte(updated), 0o644) //nolint:gosec // documentation is world-readable by design } + +// quickstartEngineFloor answers the engine a quickstart declares and the lowest +// version its Terraform will accept, both read from the files that own them. +// +// Two files, because the two facts live apart: `feint.yaml` names the engine +// `feint up` will run, and `main.tf`'s `required_version` names the floor that +// engine will refuse to go below. Typing either one beside this block is what +// #593's resource count already proved goes stale. +// +// It answers false rather than guessing when either is missing: a prerequisite +// line that names a version nobody declared is worse than none, because a reader +// installs that version and still fails. +func quickstartEngineFloor(stack string) (engine, floor string, ok bool) { + declaration, err := os.ReadFile(filepath.Join(stack, "feint.yaml")) + if err != nil { + return "", "", false + } + engineLine := regexp.MustCompile(`(?m)^\s*engine:\s*(\S+)\s*$`).FindSubmatch(declaration) + if engineLine == nil { + return "", "", false + } + main, err := os.ReadFile(filepath.Join(stack, "main.tf")) + if err != nil { + return "", "", false + } + version := regexp.MustCompile(`required_version\s*=\s*"([^"]+)"`).FindSubmatch(main) + if version == nil { + return "", "", false + } + return string(engineLine[1]), strings.TrimSpace(string(version[1])), true +} + +// engineName spells an engine the way its own project does. +func engineName(engine string) string { + switch engine { + case "terraform": + return "Terraform" + case "opentofu": + return "OpenTofu" + default: + return engine + } +} From 92569b91ad54fb273bef731a3435b7e13adb3045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20ROBERT?= Date: Fri, 28 Aug 2026 23:43:16 +0200 Subject: [PATCH 2/2] chore: the maintainer's site mockups are ignored, not merely untracked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `maquettes-site/` sat untracked in the working tree. Untracked is not pushed — git ships commits and it was in none — but untracked is also one `git add -A` away from being staged by somebody in a hurry, and the ignore line that was there had already been lost once to a pre-commit stash and restore. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e0486662..99b1f5bf 100644 --- a/.gitignore +++ b/.gitignore @@ -274,3 +274,6 @@ crash.*.log # Sandbox artefact: the harness bind-mounts /dev/null here, it is not a project file .mcp.json + +# Site mockups, the maintainer's own work in progress +maquettes-site/