From c0d54e61319a3ce7a4233a440bfc2747ab047864 Mon Sep 17 00:00:00 2001 From: Julian Sommer <115516918+juliansommer@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:27:17 +1000 Subject: [PATCH] docs: publish one column of implemented operations and nothing else --- docs/coverage/ecr/README.md | 56 ++++++++++--------------- docs/coverage/ecs/README.md | 71 +++++++++++++++----------------- docs/coverage/elbv2/README.md | 74 +++++++++++++++++----------------- internal/awsmodel/page.go | 47 ++++++++++----------- internal/awsmodel/page_test.go | 17 ++++++-- 5 files changed, 125 insertions(+), 140 deletions(-) diff --git a/docs/coverage/ecr/README.md b/docs/coverage/ecr/README.md index 18311e2b6..d9b9fc143 100644 --- a/docs/coverage/ecr/README.md +++ b/docs/coverage/ecr/README.md @@ -30,38 +30,24 @@ Registry replication is a stub for the same kind of reason: a deployment is a si ### Operations -| Operation | Status | -|---|---| -| `BatchCheckLayerAvailability` | 🟡 Stub | -| `BatchDeleteImage` | ✅ Implemented | -| `BatchGetImage` | ✅ Implemented | -| `CompleteLayerUpload` | 🟡 Stub | -| `CreateRepository` | ✅ Implemented | -| `DeleteLifecyclePolicy` | ✅ Implemented | -| `DeleteRepository` | ✅ Implemented | -| `DeleteRepositoryPolicy` | ✅ Implemented | -| `DescribeImages` | ✅ Implemented | -| `DescribeRegistry` | 🟡 Stub | -| `DescribeRepositories` | ✅ Implemented | -| `GetAuthorizationToken` | ✅ Implemented | -| `GetDownloadUrlForLayer` | 🟡 Stub | -| `GetLifecyclePolicy` | ✅ Implemented | -| `GetLifecyclePolicyPreview` | ✅ Implemented | -| `GetRegistryPolicy` | 🟡 Stub | -| `GetRepositoryPolicy` | ✅ Implemented | -| `InitiateLayerUpload` | 🟡 Stub | -| `ListImages` | ✅ Implemented | -| `ListTagsForResource` | ✅ Implemented | -| `PutImage` | ✅ Implemented | -| `PutImageTagMutability` | ✅ Implemented | -| `PutLifecyclePolicy` | ✅ Implemented | -| `PutRegistryPolicy` | 🟡 Stub | -| `PutReplicationConfiguration` | 🟡 Stub | -| `SetRepositoryPolicy` | ✅ Implemented | -| `StartLifecyclePolicyPreview` | ✅ Implemented | -| `TagResource` | 🟡 Stub | -| `UntagResource` | 🟡 Stub | -| `UploadLayerPart` | 🟡 Stub | -| `GetImageScanningConfiguration` | 🔒 Outside the pinned model | -| `ListRepositories` | 🔒 Outside the pinned model | -| `ReplicateImage` | 🔒 Outside the pinned model | +| Operation | +|---| +| `BatchDeleteImage` | +| `BatchGetImage` | +| `CreateRepository` | +| `DeleteLifecyclePolicy` | +| `DeleteRepository` | +| `DeleteRepositoryPolicy` | +| `DescribeImages` | +| `DescribeRepositories` | +| `GetAuthorizationToken` | +| `GetLifecyclePolicy` | +| `GetLifecyclePolicyPreview` | +| `GetRepositoryPolicy` | +| `ListImages` | +| `ListTagsForResource` | +| `PutImage` | +| `PutImageTagMutability` | +| `PutLifecyclePolicy` | +| `SetRepositoryPolicy` | +| `StartLifecyclePolicyPreview` | diff --git a/docs/coverage/ecs/README.md b/docs/coverage/ecs/README.md index f66aa4a42..6046b14ae 100644 --- a/docs/coverage/ecs/README.md +++ b/docs/coverage/ecs/README.md @@ -26,41 +26,36 @@ Clusters, services and tasks run on EC2 container instances. There is no Fargate ### Operations -| Operation | Status | -|---|---| -| `CreateCapacityProvider` | ✅ Implemented | -| `CreateCluster` | ✅ Implemented | -| `CreateService` | ✅ Implemented | -| `DeleteCapacityProvider` | ✅ Implemented | -| `DeleteCluster` | ✅ Implemented | -| `DeleteService` | ✅ Implemented | -| `DeregisterContainerInstance` | ✅ Implemented | -| `DeregisterTaskDefinition` | ✅ Implemented | -| `DescribeCapacityProviders` | ✅ Implemented | -| `DescribeClusters` | ✅ Implemented | -| `DescribeContainerInstances` | ✅ Implemented | -| `DescribeServices` | ✅ Implemented | -| `DescribeTaskDefinition` | ✅ Implemented | -| `DescribeTasks` | ✅ Implemented | -| `ListAccountSettings` | 🟡 Stub | -| `ListClusters` | ✅ Implemented | -| `ListContainerInstances` | ✅ Implemented | -| `ListServices` | ✅ Implemented | -| `ListServicesByNamespace` | 🟡 Stub | -| `ListTagsForResource` | ✅ Implemented | -| `ListTaskDefinitionFamilies` | 🟡 Stub | -| `ListTaskDefinitions` | ✅ Implemented | -| `ListTasks` | ✅ Implemented | -| `PutAccountSetting` | 🟡 Stub | -| `PutClusterCapacityProviders` | ✅ Implemented | -| `RegisterContainerInstance` | ✅ Implemented | -| `RegisterTaskDefinition` | ✅ Implemented | -| `RunTask` | ✅ Implemented | -| `StartTask` | ✅ Implemented | -| `StopTask` | ✅ Implemented | -| `SubmitTaskStateChange` | ✅ Implemented | -| `TagResource` | ✅ Implemented | -| `UntagResource` | ✅ Implemented | -| `UpdateCluster` | 🟡 Stub | -| `UpdateContainerInstancesState` | ✅ Implemented | -| `UpdateService` | ✅ Implemented | +| Operation | +|---| +| `CreateCapacityProvider` | +| `CreateCluster` | +| `CreateService` | +| `DeleteCapacityProvider` | +| `DeleteCluster` | +| `DeleteService` | +| `DeregisterContainerInstance` | +| `DeregisterTaskDefinition` | +| `DescribeCapacityProviders` | +| `DescribeClusters` | +| `DescribeContainerInstances` | +| `DescribeServices` | +| `DescribeTaskDefinition` | +| `DescribeTasks` | +| `ListClusters` | +| `ListContainerInstances` | +| `ListServices` | +| `ListTagsForResource` | +| `ListTaskDefinitions` | +| `ListTasks` | +| `PutClusterCapacityProviders` | +| `RegisterContainerInstance` | +| `RegisterTaskDefinition` | +| `RunTask` | +| `StartTask` | +| `StopTask` | +| `SubmitTaskStateChange` | +| `TagResource` | +| `UntagResource` | +| `UpdateContainerInstancesState` | +| `UpdateService` | diff --git a/docs/coverage/elbv2/README.md b/docs/coverage/elbv2/README.md index bb16f2ea1..ac7b24cd6 100644 --- a/docs/coverage/elbv2/README.md +++ b/docs/coverage/elbv2/README.md @@ -26,41 +26,39 @@ The data plane is a system-managed load balancer VM, launched automatically duri ### Operations -| Operation | Status | -|---|---| -| `AddListenerCertificates` | ✅ Implemented | -| `AddTags` | ✅ Implemented | -| `CreateListener` | ✅ Implemented | -| `CreateLoadBalancer` | ✅ Implemented | -| `CreateRule` | ✅ Implemented | -| `CreateTargetGroup` | ✅ Implemented | -| `DeleteListener` | ✅ Implemented | -| `DeleteLoadBalancer` | ✅ Implemented | -| `DeleteRule` | ✅ Implemented | -| `DeleteTargetGroup` | ✅ Implemented | -| `DeregisterTargets` | ✅ Implemented | -| `DescribeAccountLimits` | ✅ Implemented | -| `DescribeListenerCertificates` | ✅ Implemented | -| `DescribeListeners` | ✅ Implemented | -| `DescribeLoadBalancerAttributes` | ✅ Implemented | -| `DescribeLoadBalancers` | ✅ Implemented | -| `DescribeRules` | ✅ Implemented | -| `DescribeSSLPolicies` | ✅ Implemented | -| `DescribeTags` | ✅ Implemented | -| `DescribeTargetGroupAttributes` | ✅ Implemented | -| `DescribeTargetGroups` | ✅ Implemented | -| `DescribeTargetHealth` | ✅ Implemented | -| `ModifyListener` | ✅ Implemented | -| `ModifyLoadBalancerAttributes` | ✅ Implemented | -| `ModifyRule` | ✅ Implemented | -| `ModifyTargetGroup` | ✅ Implemented | -| `ModifyTargetGroupAttributes` | ✅ Implemented | -| `RegisterTargets` | ✅ Implemented | -| `RemoveListenerCertificates` | ✅ Implemented | -| `RemoveTags` | ✅ Implemented | -| `SetIpAddressType` | ✅ Implemented | -| `SetRulePriorities` | ✅ Implemented | -| `SetSecurityGroups` | ✅ Implemented | -| `SetSubnets` | ✅ Implemented | -| `DescribeListenerAttributes` | 🔒 Outside the pinned model | -| `ModifyListenerAttributes` | 🔒 Outside the pinned model | +| Operation | +|---| +| `AddListenerCertificates` | +| `AddTags` | +| `CreateListener` | +| `CreateLoadBalancer` | +| `CreateRule` | +| `CreateTargetGroup` | +| `DeleteListener` | +| `DeleteLoadBalancer` | +| `DeleteRule` | +| `DeleteTargetGroup` | +| `DeregisterTargets` | +| `DescribeAccountLimits` | +| `DescribeListenerCertificates` | +| `DescribeListeners` | +| `DescribeLoadBalancerAttributes` | +| `DescribeLoadBalancers` | +| `DescribeRules` | +| `DescribeSSLPolicies` | +| `DescribeTags` | +| `DescribeTargetGroupAttributes` | +| `DescribeTargetGroups` | +| `DescribeTargetHealth` | +| `ModifyListener` | +| `ModifyLoadBalancerAttributes` | +| `ModifyRule` | +| `ModifyTargetGroup` | +| `ModifyTargetGroupAttributes` | +| `RegisterTargets` | +| `RemoveListenerCertificates` | +| `RemoveTags` | +| `SetIpAddressType` | +| `SetRulePriorities` | +| `SetSecurityGroups` | +| `SetSubnets` | diff --git a/internal/awsmodel/page.go b/internal/awsmodel/page.go index 91dffdfcc..299beade9 100644 --- a/internal/awsmodel/page.go +++ b/internal/awsmodel/page.go @@ -11,8 +11,9 @@ import ( "unicode/utf8" ) -// Published operation states. The symbol carries the status at a glance and the -// label disambiguates it; the docs site escapes inline SVG, so these are runes. +// Operation states. Only Implemented reaches a published page; the rest sort +// the internal report. The symbol carries the status at a glance and the label +// disambiguates it; the docs site escapes inline SVG, so these are runes. const ( StatusImplemented = "✅ Implemented" StatusStub = "🟡 Stub" @@ -104,11 +105,21 @@ func (c OperationCoverage) OperationStatuses(page PageMetadata) ([]OperationStat } statuses = append(statuses, OperationStatus{Operation: operation, Status: status}) } + // A route outside the pinned model is still only as real as its handler, so + // its dispatch state wins: being absent from the model says nothing about + // whether the platform answers it. for _, operation := range c.Extra { if internal[operation] { continue } - statuses = append(statuses, OperationStatus{Operation: operation, Status: StatusOutsideModel}) + status := StatusOutsideModel + switch { + case stubbed[operation]: + status = StatusStub + case unsupported[operation]: + status = StatusNotImplemented + } + statuses = append(statuses, OperationStatus{Operation: operation, Status: status}) } return statuses, nil } @@ -327,32 +338,18 @@ func RenderServicePage(coverage OperationCoverage, pages PageSet, intro string) return body.String(), nil } -// writeOperations publishes one row per operation the platform serves. A gap is -// left out, and so is an operation the platform will never offer: the page says -// what is there, and the internal report carries everything that is not. The -// status column is written only where a row needs it, because a table whose -// every cell reads "Implemented" says nothing the heading has not. +// writeOperations publishes one row per operation the platform implements from +// the pinned model, and nothing else. There is no status column because every +// row carries the same status: a gap, a stub, a route the model does not +// describe and an operation the platform will never offer are all the internal +// report's to carry. func writeOperations(body *strings.Builder, statuses []OperationStatus) { - rows := make([]OperationStatus, 0, len(statuses)) - uniform := true + body.WriteString("### Operations\n\n| Operation |\n|---|\n") for _, status := range statuses { - if status.Status == StatusNotImplemented || status.Status == StatusNotApplicable { + if status.Status != StatusImplemented { continue } - uniform = uniform && status.Status == StatusImplemented - rows = append(rows, status) - } - - if uniform { - body.WriteString("### Operations\n\n| Operation |\n|---|\n") - for _, row := range rows { - fmt.Fprintf(body, "| `%s` |\n", row.Operation) - } - return - } - body.WriteString("### Operations\n\n| Operation | Status |\n|---|---|\n") - for _, row := range rows { - fmt.Fprintf(body, "| `%s` | %s |\n", row.Operation, row.Status) + fmt.Fprintf(body, "| `%s` |\n", status.Operation) } } diff --git a/internal/awsmodel/page_test.go b/internal/awsmodel/page_test.go index 21c5462e2..e990fab5d 100644 --- a/internal/awsmodel/page_test.go +++ b/internal/awsmodel/page_test.go @@ -65,7 +65,7 @@ func TestParsePageSetRejectsUnpublishableMetadata(t *testing.T) { } } -func TestRenderServicePageCarriesEveryPublishedStatus(t *testing.T) { +func TestRenderServicePagePublishesOnlyImplementedOperations(t *testing.T) { pages := testPageSet(t) coverage, err := CompareOperations(STS, DispatchInventory{ Registered: []string{"AssumeRole", "GetCallerIdentity", "GetSessionToken", "PublishInternal"}, @@ -87,14 +87,23 @@ func TestRenderServicePageCarriesEveryPublishedStatus(t *testing.T) { "# STS API Coverage\n\n## Overview\n", "Spinifex implements **1 operation** in the STS `2011-06-15` API model.", "### Scope\n\nHand-written prose.", - "| `AssumeRole` | " + StatusImplemented + " |", - "| `GetSessionToken` | " + StatusStub + " |", - "| `PublishInternal` | " + StatusOutsideModel + " |", + "### Operations\n\n| Operation |\n|---|\n| `AssumeRole` |\n", } { if !strings.Contains(page, want) { t.Errorf("page does not contain %q:\n%s", want, page) } } + // Everything other than an implemented operation from the pinned model is + // the internal report's: a stub answers with an error, a refusal serves + // nothing, and a route the model does not describe has no page to sit on. + for _, absent := range []string{ + "GetSessionToken", "GetCallerIdentity", "PublishInternal", + StatusStub, StatusOutsideModel, StatusNotImplemented, "Status", + } { + if strings.Contains(page, absent) { + t.Errorf("page publishes %q:\n%s", absent, page) + } + } if strings.Contains(page, "