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
67 changes: 67 additions & 0 deletions .github/ISSUE_TEMPLATE/service_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Service Not Supported
description: Ask for an AWS service DevCloud does not register yet
title: "[Service] "
labels: ["enhancement", "services"]
body:
- type: markdown
attributes:
value: |
DevCloud registers a subset of the AWS services AWS publishes. When a call
reaches a service that is not registered it returns `UnknownService`
rather than reaching a provider.

This form is the demand signal that decides which services get added next.
See [docs/coverage.md](https://github.com/skyoo2003/devcloud/blob/main/docs/coverage.md)
for what is registered today, and what "registered" does and does not promise.

- type: input
id: service
attributes:
label: Service
description: The AWS service name, as your SDK or tool spells it.
placeholder: e.g. appflow, amp, secretsmanager
validations:
required: true

- type: input
id: client
attributes:
label: SDK or tool that hit the wall
description: What was making the call, and which version.
placeholder: e.g. boto3 1.35.0 / Terraform aws provider 5.60 / AWS CLI v2
validations:
required: true

- type: textarea
id: blocked
attributes:
label: What did it block?
description: >
What you could not do because the service was missing — a test that had to
be skipped, a `terraform apply` that failed, a CI job pointed at real AWS.
Which operations you actually call matters more than the service alone.
placeholder: |
Our integration suite creates an AppFlow flow in setup, so the whole
module is skipped locally and only runs in the billed CI account.
Operations we use: CreateFlow, DescribeFlow, DeleteFlow.
validations:
required: true

- type: textarea
id: unrouted
attributes:
label: Unrouted-service output (optional)
description: >
With `admin.enabled: true`, DevCloud counts the services it was asked for
and does not register. Paste the output of
`curl -s localhost:4747/devcloud/api/unrouted` — it turns a request into a
measurement.


Two caveats before you read it: a request DevCloud cannot classify at all
is routed to S3 rather than counted here, so the list is a floor and not a
census; and a non-zero `droppedServiceIds` means the collector hit its cap
and the list is incomplete.
render: json
validations:
required: false
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Today DevCloud targets **AWS**. Our long-term goal is to support the full range

## Features

- **104 AWS services** across all major categories (run `make stats` for current counts; see [services-matrix.md](docs/services-matrix.md))
- **148 AWS services registered, 117 serving at least one operation** — the other 31 are routed and decline with a clean AWS error rather than letting the call bill a real AWS account. The split is the honest form of the number; see [coverage.md](docs/coverage.md) for what it does and does not promise, and [services-matrix.md](docs/services-matrix.md) for the list
- **boto3-compatible** — a 729-test compatibility suite runs in CI (`make test-compat`); works with most boto3 apps, and unsupported operations return a clean AWS error, never a false success
- **Cross-service integration** — CFN provisioning, DDB Streams → Lambda, EventBridge targets, S3 → Lambda
- **Smithy-driven codegen** — auto-generate Go types, routers, and error catalogues from Smithy models
Expand Down
5 changes: 5 additions & 0 deletions changes/unreleased/Added-20260905-180000.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Added
body: 'Admin API endpoint `GET /devcloud/api/unrouted` reports which AWS services this DevCloud was asked for and does not register, with per-service counts and first/last seen. Requires `admin.enabled: true`. Two documented ceilings: a request DevCloud cannot classify is routed to S3 rather than counted, so the figure is a floor; and a non-zero `droppedServiceIds` means the collector hit its key cap. Paste the output into a service request to turn a missing service into a measurement'
time: 2026-09-05T18:00:00.000000+09:00
custom:
Issue: "138"
5 changes: 5 additions & 0 deletions changes/unreleased/Added-20260905-180001.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Added
body: '"Service Not Supported" issue form for asking for an AWS service DevCloud does not register yet. It asks which operations you actually call and accepts the `/devcloud/api/unrouted` output, because a service name alone does not say how much of it you need'
time: 2026-09-05T18:00:01.000000+09:00
custom:
Issue: "138"
5 changes: 5 additions & 0 deletions changes/unreleased/Changed-20260905-180002.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Changed
body: 'The AWS coverage target is 205 services, not 431. Of the 283 services DevCloud does not register, only 57 have been built by at least two of moto, LocalStack, and terraform-provider-aws, and 115 by none of them; DevCloud has received zero service requests to date. Rather than build a long tail nobody has asked for, the target is now every registered service plus every service with demonstrated demand. The remaining 226 are not refused — any of them can be onboarded on request — but they are no longer promised. Evidence and method in docs/demand.md; the decision rule was fixed in advance and is published in docs/coverage.md'
time: 2026-09-05T18:00:02.000000+09:00
custom:
Issue: "138"
5 changes: 5 additions & 0 deletions changes/unreleased/Fixed-20260905-180003.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Fixed
body: 'Stale service counts across the docs. README.md advertised 104 services and docs/faq.md 101, when 148 are registered; docs/fidelity-manifest.md also carried outdated per-tier operation totals. Every count now appears with its depth split (148 registered / 117 serving at least one operation), so the number is never read as a capability claim on its own'
time: 2026-09-05T18:00:03.000000+09:00
custom:
Issue: "138"
12 changes: 10 additions & 2 deletions cmd/devcloud/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,22 @@ func main() {
// The log collector is only built when admin is enabled; otherwise no
// consumer can ever read it, so a nil collector keeps Add off the request
// hot path (see gateway.New).
// unroutedCollector follows the same rule and answers a different question:
// which services was this DevCloud asked for that it does not register. Its
// cap has headroom over the upstream model count — a real deployment cannot
// name more distinct AWS services than AWS publishes, so reaching it means
// something is sending junk, and the report says so rather than truncating
// in silence.
var logCollector *admin.LogCollector
var unroutedCollector *admin.UnroutedCollector
adminHandler := http.NotFoundHandler()
if cfg.Admin.Enabled {
logCollector = admin.NewLogCollector(1000)
adminHandler = admin.NewAPI(registry, logCollector).Handler()
unroutedCollector = admin.NewUnroutedCollector(1000)
adminHandler = admin.NewAPI(registry, logCollector, unroutedCollector).Handler()
slog.Info("admin API enabled")
}
gw := gateway.New(cfg.Server.Port, registry, adminHandler, logCollector)
gw := gateway.New(cfg.Server.Port, registry, adminHandler, logCollector, unroutedCollector)

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
Expand Down
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ This directory holds DevCloud's technical documentation. Use the map below to ju
- **[Configuration](configuration.md)** — YAML options, env-var overrides, tier shortcuts
- **[Architecture](architecture.md)** — system design, plugin model, codegen pipeline, multi-CSP vision
- **[Roadmap](roadmap.md)** — phased plan toward multi-CSP support
- **[Services Matrix](services-matrix.md)** — 104 services, coverage status, boto3 pass rate
- **[Coverage](coverage.md)** — 148 registered / 117 serving, what the count promises, and the target
- **[Demand](demand.md)** — which unregistered services have demonstrated demand, and how that was measured
- **[Services Matrix](services-matrix.md)** — per-service coverage status and boto3 pass rate

## What you can rely on

Expand Down
5 changes: 3 additions & 2 deletions docs/compatibility-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,9 @@ Depending on any of the following will break, and breaking it is **not** a major
migration. Do not treat DevCloud as a database.
- **`unimplemented` → served transitions.** An operation that returns an error today may start
returning a response. This is additive, and ships in a minor release.
- **Service coverage.** New services may be added in a minor release. The 104 services present
at v1.0 are a floor, not a ceiling.
- **Service coverage.** New services may be added in a minor release. The 148 services registered
today are a floor, not a ceiling — and not a promise of depth either: 31 of them serve no
operation and only decline cleanly. See [coverage.md](coverage.md).
- **Error codes, HTTP status and message wording.** What *is* guaranteed for an `unimplemented`
operation is that it **fails** — an AWS-shaped error, never a fabricated success. Which error
is not: it comes from whichever provider handles the request, so it is `InvalidAction` (400)
Expand Down
100 changes: 100 additions & 0 deletions docs/coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ DevCloud's service count is not a capability claim on its own, so this page neve
states it alone. Three numbers describe the surface, and they mean different
things:

> **The coverage target is not 100% of AWS.** It was, and the evidence did not
> support it — see [The target, and the rule that set it](#the-target-and-the-rule-that-set-it).

| Number | What it means | Today |
|---|---|---|
| **Registered** | The gateway routes the service. A call reaches DevCloud instead of falling through to real AWS. | **148** |
Expand Down Expand Up @@ -88,6 +91,103 @@ left unrouted rather than guessed: all four Lex services sign as `lex` and none
is called `lex`, so `lex` routes nowhere and each Lex service is reached by its
own unambiguous name.

## The target, and the rule that set it

**Decided 2026-09-05. The target is 205 services, not 431.**

DevCloud's roadmap previously aimed at every AWS service AWS publishes — 431
model files. That target rested on an assumption nobody had tested: that the
services DevCloud does not register are services anyone wants. Before committing
to building ~283 of them, the assumption was tested, and it did not hold.

**The rule was fixed before the numbers were seen.** Four outcomes were written
down in advance, including one for "the method itself failed", specifically so
the result could not be argued into whichever answer was most convenient once it
arrived. The full evidence is in [demand.md](demand.md), re-derivable with
`python3 scripts/demand_rank.py`.

**What was measured.** For each of the 283 unregistered services, how many of
three independent projects have already built it: `moto` (163 services),
LocalStack (119), `terraform-provider-aws` (273). Each serves a population
DevCloud names as its user, and each only adds a service when someone asks.

| Reading | Value |
|---|---|
| Missing services (`M`) | 283 |
| `M` built by all three projects | 8 |
| `M` built by at least two | **57 (20.1%)** |
| `M` built by exactly one | 111 |
| `M` built by none | 115 |
| DevCloud's own service requests, all time | **0** |

**The outcome that fired.** The rule kept the 100% target only if ≥60% of `M`
had support ≥ 2, and narrowed to a demand set if ≥100 did. 57 cleared neither
bar, so the pre-registered consequence applies: **the 100% claim is dropped from
the docs before any bulk work, and the published target becomes the demand set.**

Four fifths of the AWS surface DevCloud does not cover is surface that three
projects — with far more history and staffing than DevCloud — have collectively
declined to build. That is not an accident of their roadmaps. It is what a long
tail looks like.

| | Services |
|---|---|
| Registered today | 148 |
| Target: registered + demonstrated demand | **205** |
| Explicitly not targeted | 226 |

The 226 are not refused. Any of them can be onboarded when someone asks — the
["Service not supported"](https://github.com/skyoo2003/devcloud/issues/new?template=service_request.yml)
form is that channel, and one report outranks all three proxies, because it is
demand rather than a stand-in for it. What changed is that they are no longer
work DevCloud has promised.

**What this verdict is not.** The three sources measure *emulator and provider
effort*, not user demand. They are a proxy, chosen because DevCloud had no
measurement of its own and the alternative was an unbounded wait. The instrument
below now accrues the real thing.

## Service not supported?

DevCloud counts what it was asked for and could not route. Enable the admin API
(`admin.enabled: true`) and read:

```bash
curl -s localhost:4747/devcloud/api/unrouted | jq .
```

```json
{
"services": [
{ "serviceId": "appflow", "count": 2,
"firstSeen": "2026-09-05T17:54:25+09:00",
"lastSeen": "2026-09-05T17:54:40+09:00" }
],
"maxServiceIds": 1000,
"droppedServiceIds": 0
}
```

Then open a [service request](https://github.com/skyoo2003/devcloud/issues/new?template=service_request.yml)
and paste it. That output is what moves a service from the 226 into the target.

**Two ceilings, so the number is not read as more than it is.**

1. **It is a floor, not a census.** `gateway.DetectProtocol` classifies a request
it cannot identify as `("rest-xml", "s3")`, and S3 is registered — so an
unrecognisable request is routed to S3 rather than counted as a miss. What
this does catch is the case that matters: a real SDK or CLI call to an
unregistered service signs with that service's own name and misses the
registry. Verified with boto3 — `boto3.client("appflow")` is recorded as
`appflow`.
2. **A non-zero `droppedServiceIds` means the list is incomplete.** Service IDs
come from caller-controlled headers, so the collector caps how many distinct
ones it holds and reports both the cap and what it dropped, rather than
growing without bound or truncating in silence.

The counts live in memory and reset when the process does. This is a local
development tool; nothing is sent anywhere.

## Runtime cost

Measured on an Apple Silicon machine, `CGO_ENABLED=0`, going from 105 to 147
Expand Down
Loading