feat: hand-write the eight services the CRUD engine cannot classify - #161
Merged
Merged
Conversation
Not one of these eight services' 31 operations carries a CRUD verb prefix,
so the generic engine can never classify them and scaffolding would leave
them registered and serving nothing — the outcome the coverage policy
forbids. Each gets a hand-written provider instead: eks-auth,
inspector-scan, ec2-instance-connect, kinesis-video-webrtc-storage,
marketplace-commerce-analytics, cloudsearch-domain, geo-routes and
payment-cryptography-data.
Three of them sign with a name an already-registered neighbour claims, and
registering those without more would leave them reachable by nothing. The
gateway splits a shared signing name by asking each sibling whether it
models the request, and that question reads a route table built only from
CRUD-classifiable operations — which these services have none of. So
crud.RegisterRoutes lets a provider declare the routes it serves by hand.
It claims nothing about fidelity: Handle still finds no OpMeta and returns
ErrUnclassified. What it changes is which service is asked.
The compatibility harness could not reach two of the eight: botocore
refused the stub input client-side, once on a string minimum and once on a
tagged union with no member set. Both read as a service serving nothing
when the defect is in the harness, the same way an unsatisfied collection
minimum already did, so _coverage.py now handles them too. That made one
probe reach the gateway for the first time and find a pre-existing
fabricated success in workspaces-web, recorded as a strict xfail with its
mechanism named — a greedy CRUD route swallowing a more specific
unclassifiable one, which is a coverage re-derivation of its own.
Driving all 31 operations through real boto3 clients found three defects
no Go test could see, all now fixed and covered:
- botocore rewrites cloudsearch-domain's Search from the modelled GET to
a form POST, so the modelled route matched nothing and the request
stayed with cloudsearch. The provider declares the POST route and reads
q from the form body.
- GeneratePinData returned an empty PinData; it is a tagged union, and
botocore raises rather than returning one with no member set.
- geo-routes bound PricingBucket into the body, but the model binds it to
the x-amz-geo-pricing-bucket header, so botocore dropped it.
Registered services 205 -> 213, serving 201 -> 209, hand-verified
operations 4,497 -> 4,528. The registered-only set is still exactly the
known four.
This was referenced Sep 12, 2026
skyoo2003
added a commit
that referenced
this pull request
Sep 13, 2026
…e fragments The 400-character ceiling was wide enough to hold two full sentences plus a subordinate clause, so it never pushed against the one-sentence rule it was meant to back — the #163 fragment reached 484 and still read as being in the spirit of the limit. At 200 the ceiling and the rule push the same way. Refits the eight unreleased fragments that were over. What came out is detail the linked issue already carries: the eight service names in #161, the CI-runner timing in #163, the DynamoDB and Lambda operation counts in #167. Also trims RELEASE.md's "right length" example, which was 203 characters and would have failed the limit the paragraph above it states, and labels both examples with their length.
skyoo2003
added a commit
that referenced
this pull request
Sep 13, 2026
…g to 200 (#168) * fix: give six changelog fragments the issue link the release tag checks Six fragments wrote `Issue:` at the top level instead of under `custom:`, where `.changie.yaml` declares it. `changie batch` rendered them as `[#<no value>](.../issues/<no value>)`, which release.yml:234 rejects — the tag would have failed at the notes-validation step, after the guardrails had already passed. The pre-flight grep in RELEASE.md (`grep -L 'Issue: "[0-9]'`) matches both spellings, so it did not catch this. Also trims the #163 fragment from 484 to 337 characters, under the 400-char ceiling `.changie.yaml` sets and `changie new` enforces on fragments it writes itself. The CI-runner timing detail it dropped is in the issue. * docs: correct the two protocol-table rows that miscount EC2 as having no model The protocol table said 12 services have no in-tree Smithy model and exactly one speaks a protocol the parser cannot read. Counted against internal/generated/fidelity/manifest_gen.go, it is 11 and two: `ec2` is model-backed but speaks `aws.protocols#ec2Query`, which parser.go:441-453 does not recognise alongside the five it does. That put the page at odds with fidelity-manifest.md:106 and compatibility-policy.md:78, which both say 11, and left a core service's missing engine coverage unexplained. Rows now sum to 431 either way, so the arithmetic did not expose it, and no test gates this table. Adds a paragraph separating what EC2 loses from what a registered-only service loses: EC2 is served by a hand-written provider and is not in the registered-only five, but its model-declared tail stays `unimplemented` rather than falling back to `auto-crud`. * docs: halve the changelog body ceiling to 200 characters and refit the fragments The 400-character ceiling was wide enough to hold two full sentences plus a subordinate clause, so it never pushed against the one-sentence rule it was meant to back — the #163 fragment reached 484 and still read as being in the spirit of the limit. At 200 the ceiling and the rule push the same way. Refits the eight unreleased fragments that were over. What came out is detail the linked issue already carries: the eight service names in #161, the CI-runner timing in #163, the DynamoDB and Lambda operation counts in #167. Also trims RELEASE.md's "right length" example, which was 203 characters and would have failed the limit the paragraph above it states, and labels both examples with their length.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Eight AWS services hold no CRUD-shaped operation between them, so the generic engine can never classify their 31 operations and scaffolding would leave them registered and serving nothing. Each gets a hand-written provider, plus the one piece of plumbing without which three of them would be registered and reachable by nothing.
Related Issue
Refs #160 (Phase 1 — the models these eight are built from)
Changes
The eight providers — 31 operations, all
hand-verified, noneunimplementedeksauthinspectorscanec2instanceconnectkinesisvideowebrtcstoragekinesisvideomarketplacecommerceanalyticscloudsearchdomaincloudsearch; sqlite-backed, documents round-tripgeoroutespaymentcryptographydatapayment-cryptography; encrypt→decrypt round-tripscrud.RegisterRoutes— why the contested three are reachableThe gateway splits a shared SigV4 signing name by asking each sibling whether it models the request. That question reads a route table built only from CRUD-classifiable operations, and these services have none — so
payment-cryptography-data's traffic was kept by the control plane, andcloudsearch-domain's by a Query provider.RegisterRouteslets a provider declare the routes it serves by hand. It claims nothing about fidelity:Handlestill finds noOpMetaand returnsErrUnclassified. What it changes is which service is asked.Handleis untouched, and noserviceIDOverridesentry was added — the model decides, not a hardcoded winner.Compatibility harness
_coverage.py's stub builder could not satisfy two of the eight client-side: a string minimum (SSHPublicKeyis 80 characters at the least) and a tagged union with no member set. Both read as a service serving nothing when the defect is in the harness — the same way an unsatisfied collection minimum already did, which that file handles and comments on. Nothing in any assertion was weakened.A pre-existing fabricated success, surfaced and recorded
Padding strings to their minimum made one probe reach the gateway for the first time, and it found
workspacesweb.AssociateBrowserSettingsanswering 200 for an operation nothing implements: the CRUD registry holds only classifiable operations, soUpdatePortalatPUT /portals/{portalArn+}greedily swallows/portals/<arn>/browserSettings. The fix belongs in codegen and was implemented and measured before being reverted — it moves 569 operations between fidelity tiers and takes registered-only from 4 to 1. That is a coverage re-derivation of its own, so it is recorded intest_no_fabricated_success.py's ownKNOWN_UNFIXEDledger as a strict xfail with the mechanism named, and left to the scaffolding phase.Three wire-level defects found by driving real clients
Every unit test here asserts Go-side response maps, which is a layer above the wire. Pointing real boto3 clients at all 31 operations found three defects invisible to them — all fixed, each with a reproducer that was confirmed RED first:
cloudsearchdomain.Searchwas unreachable. botocore rewrites it from the modelledGET /2013-01-01/search?format=sdk&pretty=trueinto aPOSTwith those terms in a form body. The modelled route matched nothing, so the request stayed withcloudsearchand came back 501. The provider now declares the POST route — the same table backs its own resolution andRegisterRoutes, so the two cannot disagree — and readsqfrom the form body.GeneratePinDatareturnedPinData: {}. It is a tagged union; botocore raisesPinData must have one and only one member setrather than returning, so the 200 was unusable. It now carries the member the caller's generation attribute implies.geo-routessentPricingBucketin the body. The model binds it to thex-amz-geo-pricing-bucketheader, so botocore discarded it in all five operations without erroring. It is a header now.Published figures, re-derived
hand-verifiedauto-crud/unimplementedTest Plan
go vet ./...— cleangolangci-lint run—0 issues.CGO_ENABLED=0 go build ./...— cleanCGO_ENABLED=0 go test ./...— green, including the five published-figure gatesmake test-compat— 1,155 passed, 1 xfailed (was 1,144 tests before this branch)make stats—Services: 213make codegenrun twice, byte-identicalmake build— 34.8 MiB, budget 45 MiBcrud: 87–96%, all above the 80% floorTwo tests were proven to bite rather than trusted:
crud.RegisterRoutescall makesTestContestedDataPlanesResolveToThemselvesfail withexpected: "paymentcryptographydata", actual: "paymentcryptography"— the control plane keeping the request, which is the exact failure the mechanism exists to prevent.POST /2013-01-01/searchroute makes the newtest_handverified_is_reachable.pyrow fail withcloudsearchdomain.Search … answered 501.Both files were restored byte-identical and re-verified green.
Checklist
golangci-lint run)