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
3 changes: 3 additions & 0 deletions changes/unreleased/Added-20261008-235900.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: Implement Phase 1C compute and network integrations by adding aws.protocols#ec2Query protocol detection in codegen parser, CRUD fallback engine XML response support with requestId and item list element wrappers, gateway error routing for ec2-query, and wiring EC2 unhandled operations into the fallback CRUD engine, promoting 498 EC2 operations to auto-crud.
time: 2026-10-08T23:59:00.000000+09:00
4 changes: 2 additions & 2 deletions docs/coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ denominators, because [routing and depth are two targets](#the-target):
| Tier | Serving target | All registered |
|---|---|---|
| `hand-verified` | 4,601 | 4,632 |
| `auto-crud` | 5,882 | 11,560 |
| `unimplemented` | 1,924 | 3,009 |
| `auto-crud` | 6,380 | 12,058 |
| `unimplemented` | 1,426 | 2,511 |
| **total known** | **12,407** | **19,201** |
| **hand-verified share** | **37.1%** | **24.1%** |

Expand Down
6 changes: 5 additions & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ across IAM (MFA devices, password changes, service credentials, SSH/server/signi
OIDC provider client IDs, delegation requests, access/credential reports, policy simulation,
and org root credentials/sessions) and STS (AssumeRoleWithWebIdentity, AssumeRoleWithSAML,
AssumeRoot, DecodeAuthorizationMessage, GetDelegatedAccessToken, GetFederationToken, GetWebIdentityToken).
The original 3,085-operation unimplemented baseline has **3,009 remaining**;
Phase 1C Compute & Network added `aws.protocols#ec2Query` protocol detection,
CRUD engine XML response support (`<requestId>`, item list tags), gateway error routing,
and wired EC2 unhandled operations into the fallback CRUD engine, promoting 498 EC2
operations into `auto-crud`.
The original 3,085-operation unimplemented baseline has **2,511 remaining**;
complete AWS operation support is still in progress across subsequent bundles. See
[EventBridge](services/eventbridge.md), [SNS](services/sns.md), [S3](services/s3.md) and
[IAM/STS](services/iam-sts.md) for local limits.
Expand Down
7 changes: 4 additions & 3 deletions internal/codegen/gen_crud_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,17 @@ func classifyOps(model *ir.Model) []crudOpData {
// matches it by the Action field of the form body instead. It is admitted here
// because that is a place the operation name genuinely is, not a guess.
//
// ec2-query stays out. It is form-encoded like query but not interchangeable
// with it, and the only service that speaks it has a hand-written provider.
// ec2-query is form-encoded like query and admitted here so that EC2's
// unhandled operations can fall back to the CRUD engine.
//
// This is deliberately the same question crud.Servable answers at runtime. The
// two must agree: a protocol classified here but refused there registers
// operations nothing can reach, and the fidelity manifest would call them
// auto-crud.
func engineServable(protocol string) bool {
return strings.HasPrefix(protocol, "json") ||
protocol == "rest-json" || protocol == "rest-xml" || protocol == "query"
protocol == "rest-json" || protocol == "rest-xml" || protocol == "query" ||
protocol == "ec2-query"
}

// ServiceCRUDData classifies an engine-servable model's CRUD operations. It
Expand Down
20 changes: 14 additions & 6 deletions internal/codegen/gen_crud_meta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,24 @@ func TestServiceCRUDDataAdmitsQuery(t *testing.T) {
assert.Empty(t, ops["DescribeLoadBalancers"].URI)
}

// TestServiceCRUDDataRejectsEC2Query holds the remaining boundary. ec2Query is
// form-encoded like query but not interchangeable with it, and the only service
// that speaks it has a hand-written provider that never reaches the engine.
func TestServiceCRUDDataRejectsEC2Query(t *testing.T) {
// TestServiceCRUDDataAdmitsEC2Query verifies that ec2-query is engine-servable.
// Like query, it has no HTTP binding so it contributes no REST routes, but its
// operations can be served by the fallback engine.
func TestServiceCRUDDataAdmitsEC2Query(t *testing.T) {
model := crudModel("ec2-query",
ir.Operation{Name: "DescribeInstances", OutputName: "DescribeInstancesOutput"},
)

_, ok := ServiceCRUDData(model)
assert.False(t, ok, "ec2-query must not be engine-servable")
data, ok := ServiceCRUDData(model)
require.True(t, ok, "ec2-query must be engine-servable")

ops := map[string]crudOpData{}
for _, op := range data.Ops {
ops[op.Op] = op
}
assert.Empty(t, ops["DescribeInstances"].Method)
assert.Empty(t, ops["DescribeInstances"].URI)
assert.Equal(t, "Get", ops["DescribeInstances"].Verb)
}

// TestServiceCRUDDataSkipsUnclassifiableService is the rds-data case: a
Expand Down
3 changes: 3 additions & 0 deletions internal/codegen/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,9 @@ func detectProtocol(s *rawShape) string {
if _, ok := s.Traits["aws.protocols#awsQuery"]; ok {
return "query"
}
if _, ok := s.Traits["aws.protocols#ec2Query"]; ok {
return "ec2-query"
}
if _, ok := s.Traits["aws.protocols#restJson1"]; ok {
return "rest-json"
}
Expand Down
28 changes: 24 additions & 4 deletions internal/gateway/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func extractOperationName(r *http.Request, protocol string) string {
return target[idx+1:]
}
return target
case protocol == "query":
case protocol == "query" || protocol == "ec2-query":
// Only check URL query params — do NOT call r.FormValue() which
// consumes the request body. The service provider will parse the
// form body itself and extract the Action.
Expand Down Expand Up @@ -215,6 +215,18 @@ type awsQueryError struct {
Message string `xml:"Error>Message"`
}

// awsEC2QueryError is the EC2 Query-protocol error envelope.
type awsEC2QueryError struct {
XMLName xml.Name `xml:"Response"`
Errors struct {
Error struct {
Code string `xml:"Code"`
Message string `xml:"Message"`
} `xml:"Error"`
} `xml:"Errors"`
RequestID string `xml:"RequestID,omitempty"`
}

// awsJSONError is the envelope for a JSON-format AWS error response.
type awsJSONError struct {
Code string `json:"__type"`
Expand All @@ -239,12 +251,20 @@ func writeAWSError(w http.ResponseWriter, protocol string, status int, code, mes
}

var body []byte
if protocol == "query" {
ct := "application/xml"
switch protocol {
case "query":
body, _ = xml.Marshal(awsQueryError{Type: "Sender", Code: code, Message: message})
} else {
case "ec2-query":
e := awsEC2QueryError{RequestID: "req-err"}
e.Errors.Error.Code = code
e.Errors.Error.Message = message
body, _ = xml.Marshal(e)
ct = "text/xml"
default:
body, _ = xml.Marshal(awsXMLError{Code: code, Message: message})
}
w.Header().Set("Content-Type", "application/xml")
w.Header().Set("Content-Type", ct)
w.WriteHeader(status)
_, _ = w.Write([]byte(xml.Header))
_, _ = w.Write(body)
Expand Down
Loading
Loading