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
22 changes: 20 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,26 @@ the subject: `core`, `machine`, `conformance`. State: `blocked`, with the
blocker named in the body. There is no `wontfix` label: the project's word for
out of scope is a decline in the pack, with a reason.

**Milestones** are the six waves of the roadmap's sequence — an order, not a
schedule, so they carry no due dates.
**Milestones** are themes, not versions, and they carry no due dates: an order,
never a schedule. That sentence used to say "the six waves of the roadmap's
sequence", which stopped being true at 0.9.0 and was still there two years of
releases later — which is how two milestones came to be titled after published
tags, `0.14.0` and `0.15.0`, while holding 19 and 10 open issues between them.

So the rule is now checkable rather than described, and it is narrower than it
first looks: **an open milestone never claims a version that is already
tagged.** A milestone for work still ahead may carry its number — `1.0.0` is
one, and it is the case where the tag should wait for the milestone rather than
the reverse. What is refused is a statement about the past that the tags
disprove. The second half is its mirror: the version being tagged must hold no
open issue under its own name.

`tools/release/milestone-gate.sh` asks both before a tag, and says so when it
could not measure instead of refusing for a reason that is not about the
repository. `tools/ci`'s `TestTheMilestoneGateRefusesAVersionOnAnOpenMilestone`
is driven by the payload that asked for it, and
`TestTheMilestoneGateAcceptsTheShippedShape` keeps the accepting half honest: a
gate that refused every tag would pass the first test and teach `--no-verify`.

`tools/issues/setup.sh` creates the labels and milestones (`--dry-run` shows
what it would do); `tools/issues/batches/` holds the eighteen batch
Expand Down
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 59 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 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.
>
> **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
41 changes: 26 additions & 15 deletions 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 59 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 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.
>
> **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 Expand Up @@ -943,8 +943,8 @@ what this emulator serves, declines on purpose, or has not triaged yet.

#### Scaleway

204 routes mounted. Of the 548 operations upstream declares: 37% served,
62% declined on purpose, 0% untriaged.
204 routes mounted. Of the 548 operations upstream declares in the 11 product(s)
this scan covers: 37% served, 62% declined on purpose, 0% untriaged.

| Group | Served | Declined | Untriaged | Upstream |
|---|--:|--:|--:|--:|
Expand All @@ -961,8 +961,8 @@ what this emulator serves, declines on purpose, or has not triaged yet.

#### Exoscale

105 routes mounted. Of the 374 operations upstream declares: 28% served,
71% declined on purpose, 0% untriaged.
105 routes mounted. Of the 374 operations upstream declares in the 1 product(s)
this scan covers: 28% served, 71% declined on purpose, 0% untriaged.

| Group | Served | Declined | Untriaged | Upstream |
|---|--:|--:|--:|--:|
Expand All @@ -979,8 +979,8 @@ what this emulator serves, declines on purpose, or has not triaged yet.

#### Outscale

100 routes mounted. Of the 263 operations upstream declares: 38% served,
61% declined on purpose, 0% untriaged.
100 routes mounted. Of the 263 operations upstream declares in the 2 product(s)
this scan covers: 38% served, 61% declined on purpose, 0% untriaged.

| Group | Served | Declined | Untriaged | Upstream |
|---|--:|--:|--:|--:|
Expand Down Expand Up @@ -1024,14 +1024,25 @@ conformance run. The sequence and the state of every batch live in
measurements that cut them (2026-07-30) are archived under
[docs/history/](docs/history/), where a snapshot cannot read as the present.

**Object storage is out of scope for now.** The Scaleway Terraform provider
hardcodes `https://s3.<region>.scw.cloud`, so it needs DNS interception and TLS
rather than an endpoint setting — and that is not theory: a surveyed stack's
`CreateBucket` went to the real endpoint and got a 403 while everything else it
did ran here (#262). Exoscale's `sos-endpoint` is served empty for the
same reason: an invented address is a promise the emulator cannot keep. See
[docs/limits.md](docs/limits.md), which states every limit that was measured
rather than assumed.
**Object storage is served since 0.15.0, and it is opt-in.** The reason it was
out of scope for a year is worth keeping, because it is what the feature had to
answer: the Scaleway Terraform provider hardcodes
`https://s3.<region>.scw.cloud`, so an endpoint setting cannot redirect it — a
surveyed stack's `CreateBucket` went to the real endpoint and got a 403 while
everything else it did ran here (#262).

What changed is that `feint run` terminates that host itself, with an authority
it generates for the run alone, and **refuses any host it was not told about
rather than tunnelling it**. On the published provider 2.82.0 that path reads
139,264 identical bytes back out of the local byte store by sha256, with an
empty second plan. It is opt-in because interception is a side effect on the
machine that runs it, and because a client carrying real credentials is refused
unless you say otherwise.

Normal Exoscale bucket creation is still not certified (#858), and the
multipart states are #856. [docs/confidence.md](docs/confidence.md) carries the
row with its client versions, and [docs/limits.md](docs/limits.md) states every
limit that was measured rather than assumed.

---

Expand Down
9 changes: 5 additions & 4 deletions docs/limits-acks.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"comment": "When each section of docs/limits.md was last confirmed against the issues it cites. A citation is a signal, never a proof: most closed issues are decisions whose limits hold. Re-date a line only after looking at the section. See tools/docs/limits-acks.py and #558.",
"acknowledged": {
"A Scaleway VPC's Network ACL is a record, and nothing at the edge applies it": "2026-08-27",
"A Scaleway load balancer and public gateway record their configuration; nothing forwards packets": "2026-08-27",
Expand All @@ -21,16 +20,17 @@
"Identifiers are not checked against anything": "2026-09-02",
"Lifecycle transitions are immediate, unless you ask for otherwise": "2026-09-03",
"Managed Kubernetes is not emulated, and a CRUD-only version is refused (#283)": "2026-08-27",
"Object Storage: simple explicit doors, not full client workflows": "2026-11-07",
"Object Storage: simple explicit doors, not full client workflows": "2026-11-11",
"Outscale and Exoscale trail Scaleway, and the generated tables say by how much": "2026-08-27",
"Placement is recorded, never enforced (#285)": "2026-08-27",
"ReadTags does not list an internet service, because upstream names no type for one": "2026-08-27",
"The Exoscale Terraform provider is refused, and why": "2026-08-27",
"The Exoscale stack's second plan is not empty: two per-id outputs read back null at apply time (#520)": "2026-08-29",
"The Outscale Net-peering surface is proven by `shapes/`, and its transcript is not committed (#438)": "2026-11-11",
"The Scaleway gateway's `GET /metadata` is served, and what it answers is three constants (#776)": "2026-09-15",
"The catalogue is a whitelist, and its values are measured": "2026-08-27",
"The contracts do not guarantee the same thing": "2026-08-27",
"The cost of DNS/TLS interception, measured (#76)": "2026-08-27",
"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-08-29",
"The per-parameter half: 18 Scaleway list operations, 72 parameters, each served or refused (#277)": "2026-08-29",
Expand All @@ -40,5 +40,6 @@
"`feint images resolve` prints only what boots, and names what it left out (#476)": "2026-08-30",
"`lb/v1` refusals carry an envelope the real one does not": "2026-09-03",
"`scw` 2.56.3 prints a recovered panic on every successful `lb acl delete`, and the defect is upstream (#505)": "2026-08-28"
}
},
"comment": "When each section of docs/limits.md was last confirmed against the issues it cites. A citation is a signal, never a proof: most closed issues are decisions whose limits hold. Re-date a line only after looking at the section. See tools/docs/limits-acks.py and #558."
}
43 changes: 43 additions & 0 deletions docs/limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,22 @@ interception plus a certificate the provider will accept"*, as if both were the
hard part. The certificate is the easy, safe half; the DNS redirect is the hard,
dangerous one.

**Since 0.15.0 the cost is paid, and not by the route this section costed.**
`feint run` (#849) stands a scoped forward proxy in front of the child: it
accepts the `CONNECT`, terminates TLS with an authority generated for that run
alone, and **refuses any host it was not told about rather than tunnelling
it**. Measured on the published Scaleway provider 2.82.0, on the host network,
with no sudo and no namespace: apply complete, 139,264 identical bytes read
back from the local byte store by sha256, an empty second plan, a complete
teardown. The authority is owner-only and never installed durably, and a client
carrying real credentials is refused unless `--unsafe-real-credentials` says
otherwise.

What follows is therefore the cost of the **DNS** route, kept because it is
what a reader comparing the two approaches needs, and because the namespace
correction below is a measurement error worth not repeating. It is no longer
the reason anything is refused.

### 1. How many endpoints are actually hardcoded: one product, one client

The blocker is *"an endpoint built in code that no setting overrides"*. Swept
Expand Down Expand Up @@ -3081,6 +3097,33 @@ the `provider-pack-author` skill), not a redesign. The lifting condition is
the tables', never this paragraph's: the day the counts converge, the
generated artefacts say so without anyone rewriting a sentence here.

## The Outscale Net-peering surface is proven by `shapes/`, and its transcript is not committed (#438)

The five peering operations are proven: `CreateNetPeering`, `AcceptNetPeering`,
`RejectNetPeering`, `DeleteNetPeering` and `ReadNetPeerings` each read
`driven: true`, `shape: observed` and `negative: true` in
`coverage/evidence.json`, from `shapes/outscale.json`. Nothing about that
surface is unmeasured.

What is **not** committed is a second proof of the same surface: the recorded
transcript was never added to `corpus/outscale/`, which holds five
`oapi-cli-*.jsonl` files and none for peering. Its replay produces a cascade
from a single `CreateNet` conflict that nobody has reproduced, and
`corpus/accepted.json` forbids the only shape that would let it in anyway: *an
entry that excuses nothing fails the gate*, so an exemption whose reason is
"the root is not known" cannot be written there.

So the choice is deliberate rather than pending. A corpus trimmed until it
passes is the outcome that file exists to make impossible, and a second proof
of an already-proven surface does not buy enough to justify committing a
replay whose failure nobody understands. The recorder findings that run
surfaced are not lost: they are the `REDACTED-10` tag-key family, filed
against the instrument as #384, which `corpus/accepted.json` already records
as "NOT THE EMULATOR".

This limit lifts the day the `CreateNet` conflict is reproduced, or the day a
client this project drives exercises peering in a way `shapes/` cannot cover.

## The contracts do not guarantee the same thing

Every response the emulator writes is checked against the provider's own API
Expand Down
17 changes: 12 additions & 5 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@ What is *measured* rather than planned lives in [limits.md](limits.md), and how
the pieces fit together in [architecture.md](architecture.md).


## The order 0.11.0 is being worked in

Frozen 2026-08-22. Thirteen issues remain, and they are not independent: two of
them move the numbers the other eleven quote. The order below is the dependency,
not a preference.
## The order 0.11.0 was worked in

Frozen 2026-08-22, and kept in the past tense deliberately: 0.11.0 shipped, and
so did 0.12.0 to 0.15.0 after it. This heading read "is being worked in" for
three releases, which is the same failure as a milestone titled after a
published tag — a sentence nobody could notice was stale because nothing
measured it. **The order being worked now is the 1.0 one, at the end of this
page.**

Thirteen issues remained at the freeze, and they were not independent: two of
them moved the numbers the other eleven quoted. The order below is the
dependency, not a preference.

**1 — Make the measurement trustworthy. Blocking.**

Expand Down
Loading
Loading