fix: footnotes instead of notes for compatibility

This commit is contained in:
Julian Sommer
2026-09-14 12:05:32 +10:00
parent bba735497f
commit 19a047bccb
16 changed files with 1052 additions and 1875 deletions
+1 -1011
View File
File diff suppressed because it is too large Load Diff
@@ -18,7 +18,7 @@ tags:
## Overview
Spinifex implements **9 of the 15** operations (60.0%) in the ACM `2015-12-08` API model.
Spinifex implements **9 of the 15** operations (**60.0%**) in the ACM `2015-12-08` API model.
### Import and issuance
+11 -11
View File
@@ -19,14 +19,14 @@ tags:
Spinifex serves the AWS APIs below. Every page counts the operations in the pinned `aws-sdk-go v1.55.8` `api-2.json` model for its service and reports, operation by operation, whether Spinifex implements it.
| Service | API version | Implemented | Modelled | Coverage |
|---|---|---:|---:|---:|
| [ACM](/docs/acm-api-coverage) | 2015-12-08 | 9 | 15 | 60.0% |
| [EC2](/docs/ec2-api-coverage) | 2016-11-15 | 121 | 625 | 19.4% |
| [ECR](/docs/ecr-api-coverage) | 2015-09-21 | 19 | 47 | 40.4% |
| [ECS](/docs/ecs-api-coverage) | 2014-11-13 | 31 | 56 | 55.4% |
| [EKS](/docs/eks-api-coverage) | 2017-11-01 | 34 | 56 | 60.7% |
| [ELBv2](/docs/elbv2-api-coverage) | 2015-12-01 | 33 | 46 | 71.7% |
| [IAM](/docs/iam-api-coverage) | 2010-05-08 | 75 | 159 | 47.2% |
| [RDS](/docs/rds-api-coverage) | 2014-10-31 | 26 | 162 | 16.0% |
| [STS](/docs/sts-api-coverage) | 2011-06-15 | 4 | 8 | 50.0% |
| Service | Implemented | Modelled | Coverage |
|---|---:|---:|---:|
| [ACM](/docs/acm-api-coverage) | 9 | 15 | 60.0% |
| [EC2](/docs/ec2-api-coverage) | 121 | 625 | 19.4% |
| [ECR](/docs/ecr-api-coverage) | 19 | 47 | 40.4% |
| [ECS](/docs/ecs-api-coverage) | 31 | 56 | 55.4% |
| [EKS](/docs/eks-api-coverage) | 34 | 56 | 60.7% |
| [ELBv2](/docs/elbv2-api-coverage) | 33 | 46 | 71.7% |
| [IAM](/docs/iam-api-coverage) | 75 | 159 | 47.2% |
| [RDS](/docs/rds-api-coverage) | 26 | 162 | 16.0% |
| [STS](/docs/sts-api-coverage) | 4 | 8 | 50.0% |
File diff suppressed because it is too large Load Diff
@@ -18,7 +18,7 @@ tags:
## Overview
Spinifex implements **19 of the 47** operations (40.4%) in the ECR `2015-09-21` API model.
Spinifex implements **19 of the 47** operations (**40.4%**) in the ECR `2015-09-21` API model.
### Two endpoints, one registry
@@ -18,7 +18,7 @@ tags:
## Overview
Spinifex implements **31 of the 56** operations (55.4%) in the ECS `2014-11-13` API model.
Spinifex implements **31 of the 56** operations (**55.4%**) in the ECS `2014-11-13` API model.
### No Fargate
@@ -18,7 +18,7 @@ tags:
## Overview
Spinifex implements **34 of the 56** operations (60.7%) in the EKS `2017-11-01` API model.
Spinifex implements **34 of the 56** operations (**60.7%**) in the EKS `2017-11-01` API model.
### Routing
@@ -18,7 +18,7 @@ tags:
## Overview
Spinifex implements **33 of the 46** operations (71.7%) in the ELBv2 `2015-12-01` API model.
Spinifex implements **33 of the 46** operations (**71.7%**) in the ELBv2 `2015-12-01` API model.
### Two data planes
+169 -162
View File
@@ -18,7 +18,7 @@ tags:
## Overview
Spinifex implements **75 of the 159** operations (47.2%) in the IAM `2010-05-08` API model.
Spinifex implements **75 of the 159** operations (**47.2%**) in the IAM `2010-05-08` API model.
### Scope
@@ -26,164 +26,171 @@ All IAM operations are account-scoped. The root user of an account bypasses poli
### Operations
| Operation | Status | Notes |
|---|---|---|
| `AddClientIDToOpenIDConnectProvider` | ❌ Not implemented | |
| `AddRoleToInstanceProfile` | ✅ Implemented | |
| `AddUserToGroup` | ✅ Implemented | |
| `AttachGroupPolicy` | ✅ Implemented | |
| `AttachRolePolicy` | ✅ Implemented | |
| `AttachUserPolicy` | ✅ Implemented | |
| `ChangePassword` | ❌ Not implemented | |
| `CreateAccessKey` | ✅ Implemented | |
| `CreateAccountAlias` | ❌ Not implemented | |
| `CreateGroup` | ✅ Implemented | |
| `CreateInstanceProfile` | ✅ Implemented | |
| `CreateLoginProfile` | ❌ Not implemented | |
| `CreateOpenIDConnectProvider` | ✅ Implemented | |
| `CreatePolicy` | ✅ Implemented | |
| `CreatePolicyVersion` | ❌ Not implemented | |
| `CreateRole` | ✅ Implemented | |
| `CreateSAMLProvider` | ❌ Not implemented | |
| `CreateServiceLinkedRole` | ⛔ Not applicable | Service-linked roles exist for AWS service principals, which this platform has none of. |
| `CreateServiceSpecificCredential` | ⛔ Not applicable | CodeCommit credentials; the service does not exist here. |
| `CreateUser` | ✅ Implemented | |
| `CreateVirtualMFADevice` | ❌ Not implemented | |
| `DeactivateMFADevice` | ❌ Not implemented | |
| `DeleteAccessKey` | ✅ Implemented | |
| `DeleteAccountAlias` | ❌ Not implemented | |
| `DeleteAccountPasswordPolicy` | ❌ Not implemented | |
| `DeleteGroup` | ✅ Implemented | |
| `DeleteGroupPolicy` | ✅ Implemented | |
| `DeleteInstanceProfile` | ✅ Implemented | |
| `DeleteLoginProfile` | ❌ Not implemented | |
| `DeleteOpenIDConnectProvider` | ✅ Implemented | |
| `DeletePolicy` | ✅ Implemented | |
| `DeletePolicyVersion` | ❌ Not implemented | |
| `DeleteRole` | ✅ Implemented | |
| `DeleteRolePermissionsBoundary` | ❌ Not implemented | |
| `DeleteRolePolicy` | ✅ Implemented | |
| `DeleteSAMLProvider` | ❌ Not implemented | |
| `DeleteSSHPublicKey` | ⛔ Not applicable | CodeCommit SSH keys; the service does not exist here. |
| `DeleteServerCertificate` | ❌ Not implemented | |
| `DeleteServiceLinkedRole` | ⛔ Not applicable | Service-linked roles exist for AWS service principals, which this platform has none of. |
| `DeleteServiceSpecificCredential` | ⛔ Not applicable | CodeCommit credentials; the service does not exist here. |
| `DeleteSigningCertificate` | ⛔ Not applicable | X.509 signing certificates are an EC2-Classic SOAP credential, retired by AWS. |
| `DeleteUser` | ✅ Implemented | |
| `DeleteUserPermissionsBoundary` | ❌ Not implemented | |
| `DeleteUserPolicy` | ✅ Implemented | |
| `DeleteVirtualMFADevice` | ❌ Not implemented | |
| `DetachGroupPolicy` | ✅ Implemented | |
| `DetachRolePolicy` | ✅ Implemented | |
| `DetachUserPolicy` | ✅ Implemented | |
| `EnableMFADevice` | ❌ Not implemented | |
| `GenerateCredentialReport` | ❌ Not implemented | |
| `GenerateOrganizationsAccessReport` | ⛔ Not applicable | AWS Organizations is not offered, so there is no organization to report on. |
| `GenerateServiceLastAccessedDetails` | ❌ Not implemented | |
| `GetAccessKeyLastUsed` | ❌ Not implemented | |
| `GetAccountAuthorizationDetails` | ❌ Not implemented | |
| `GetAccountPasswordPolicy` | ❌ Not implemented | |
| `GetAccountSummary` | ✅ Implemented | |
| `GetContextKeysForCustomPolicy` | ❌ Not implemented | |
| `GetContextKeysForPrincipalPolicy` | ❌ Not implemented | |
| `GetCredentialReport` | ❌ Not implemented | |
| `GetGroup` | ✅ Implemented | |
| `GetGroupPolicy` | ✅ Implemented | |
| `GetInstanceProfile` | ✅ Implemented | |
| `GetLoginProfile` | ❌ Not implemented | |
| `GetMFADevice` | ❌ Not implemented | |
| `GetOpenIDConnectProvider` | ✅ Implemented | |
| `GetOrganizationsAccessReport` | ⛔ Not applicable | AWS Organizations is not offered, so there is no organization to report on. |
| `GetPolicy` | ✅ Implemented | |
| `GetPolicyVersion` | ✅ Implemented | |
| `GetRole` | ✅ Implemented | |
| `GetRolePolicy` | ✅ Implemented | |
| `GetSAMLProvider` | ❌ Not implemented | |
| `GetSSHPublicKey` | ⛔ Not applicable | CodeCommit SSH keys; the service does not exist here. |
| `GetServerCertificate` | ❌ Not implemented | |
| `GetServiceLastAccessedDetails` | ❌ Not implemented | |
| `GetServiceLastAccessedDetailsWithEntities` | ❌ Not implemented | |
| `GetServiceLinkedRoleDeletionStatus` | ⛔ Not applicable | Service-linked roles exist for AWS service principals, which this platform has none of. |
| `GetUser` | ✅ Implemented | |
| `GetUserPolicy` | ✅ Implemented | |
| `ListAccessKeys` | ✅ Implemented | |
| `ListAccountAliases` | ❌ Not implemented | |
| `ListAttachedGroupPolicies` | ✅ Implemented | |
| `ListAttachedRolePolicies` | ✅ Implemented | |
| `ListAttachedUserPolicies` | ✅ Implemented | |
| `ListEntitiesForPolicy` | ❌ Not implemented | |
| `ListGroupPolicies` | ✅ Implemented | |
| `ListGroups` | ✅ Implemented | |
| `ListGroupsForUser` | ✅ Implemented | |
| `ListInstanceProfileTags` | ✅ Implemented | |
| `ListInstanceProfiles` | ✅ Implemented | |
| `ListInstanceProfilesForRole` | ✅ Implemented | |
| `ListMFADeviceTags` | ❌ Not implemented | |
| `ListMFADevices` | ❌ Not implemented | |
| `ListOpenIDConnectProviderTags` | ✅ Implemented | |
| `ListOpenIDConnectProviders` | ✅ Implemented | |
| `ListPolicies` | ✅ Implemented | |
| `ListPoliciesGrantingServiceAccess` | ❌ Not implemented | |
| `ListPolicyTags` | ✅ Implemented | |
| `ListPolicyVersions` | ✅ Implemented | |
| `ListRolePolicies` | ✅ Implemented | |
| `ListRoleTags` | ✅ Implemented | |
| `ListRoles` | ✅ Implemented | |
| `ListSAMLProviderTags` | ❌ Not implemented | |
| `ListSAMLProviders` | ❌ Not implemented | |
| `ListSSHPublicKeys` | ⛔ Not applicable | CodeCommit SSH keys; the service does not exist here. |
| `ListServerCertificateTags` | ❌ Not implemented | |
| `ListServerCertificates` | ❌ Not implemented | |
| `ListServiceSpecificCredentials` | ⛔ Not applicable | CodeCommit credentials; the service does not exist here. |
| `ListSigningCertificates` | ⛔ Not applicable | X.509 signing certificates are an EC2-Classic SOAP credential, retired by AWS. |
| `ListUserPolicies` | ✅ Implemented | |
| `ListUserTags` | ✅ Implemented | |
| `ListUsers` | ✅ Implemented | |
| `ListVirtualMFADevices` | ❌ Not implemented | |
| `PutGroupPolicy` | ✅ Implemented | |
| `PutRolePermissionsBoundary` | ❌ Not implemented | |
| `PutRolePolicy` | ✅ Implemented | |
| `PutUserPermissionsBoundary` | ❌ Not implemented | |
| `PutUserPolicy` | ✅ Implemented | |
| `RemoveClientIDFromOpenIDConnectProvider` | ❌ Not implemented | |
| `RemoveRoleFromInstanceProfile` | ✅ Implemented | |
| `RemoveUserFromGroup` | ✅ Implemented | |
| `ResetServiceSpecificCredential` | ⛔ Not applicable | CodeCommit credentials; the service does not exist here. |
| `ResyncMFADevice` | ❌ Not implemented | |
| `SetDefaultPolicyVersion` | ❌ Not implemented | |
| `SetSecurityTokenServicePreferences` | ❌ Not implemented | |
| `SimulateCustomPolicy` | ❌ Not implemented | |
| `SimulatePrincipalPolicy` | ❌ Not implemented | |
| `TagInstanceProfile` | ✅ Implemented | |
| `TagMFADevice` | ❌ Not implemented | |
| `TagOpenIDConnectProvider` | ✅ Implemented | |
| `TagPolicy` | ✅ Implemented | |
| `TagRole` | ✅ Implemented | |
| `TagSAMLProvider` | ❌ Not implemented | |
| `TagServerCertificate` | ❌ Not implemented | |
| `TagUser` | ✅ Implemented | |
| `UntagInstanceProfile` | ✅ Implemented | |
| `UntagMFADevice` | ❌ Not implemented | |
| `UntagOpenIDConnectProvider` | ✅ Implemented | |
| `UntagPolicy` | ✅ Implemented | |
| `UntagRole` | ✅ Implemented | |
| `UntagSAMLProvider` | ❌ Not implemented | |
| `UntagServerCertificate` | ❌ Not implemented | |
| `UntagUser` | ✅ Implemented | |
| `UpdateAccessKey` | ✅ Implemented | |
| `UpdateAccountPasswordPolicy` | ❌ Not implemented | |
| `UpdateAssumeRolePolicy` | ✅ Implemented | |
| `UpdateGroup` | ❌ Not implemented | |
| `UpdateLoginProfile` | ❌ Not implemented | |
| `UpdateOpenIDConnectProviderThumbprint` | ❌ Not implemented | |
| `UpdateRole` | ✅ Implemented | |
| `UpdateRoleDescription` | ❌ Not implemented | |
| `UpdateSAMLProvider` | ❌ Not implemented | |
| `UpdateSSHPublicKey` | ⛔ Not applicable | CodeCommit SSH keys; the service does not exist here. |
| `UpdateServerCertificate` | ❌ Not implemented | |
| `UpdateServiceSpecificCredential` | ⛔ Not applicable | CodeCommit credentials; the service does not exist here. |
| `UpdateSigningCertificate` | ⛔ Not applicable | X.509 signing certificates are an EC2-Classic SOAP credential, retired by AWS. |
| `UpdateUser` | ❌ Not implemented | |
| `UploadSSHPublicKey` | ⛔ Not applicable | CodeCommit SSH keys; the service does not exist here. |
| `UploadServerCertificate` | ❌ Not implemented | |
| `UploadSigningCertificate` | ⛔ Not applicable | X.509 signing certificates are an EC2-Classic SOAP credential, retired by AWS. |
| Operation | Status |
|---|---|
| `AddClientIDToOpenIDConnectProvider` | ❌ Not implemented |
| `AddRoleToInstanceProfile` | ✅ Implemented |
| `AddUserToGroup` | ✅ Implemented |
| `AttachGroupPolicy` | ✅ Implemented |
| `AttachRolePolicy` | ✅ Implemented |
| `AttachUserPolicy` | ✅ Implemented |
| `ChangePassword` | ❌ Not implemented |
| `CreateAccessKey` | ✅ Implemented |
| `CreateAccountAlias` | ❌ Not implemented |
| `CreateGroup` | ✅ Implemented |
| `CreateInstanceProfile` | ✅ Implemented |
| `CreateLoginProfile` | ❌ Not implemented |
| `CreateOpenIDConnectProvider` | ✅ Implemented |
| `CreatePolicy` | ✅ Implemented |
| `CreatePolicyVersion` | ❌ Not implemented |
| `CreateRole` | ✅ Implemented |
| `CreateSAMLProvider` | ❌ Not implemented |
| `CreateServiceLinkedRole` | ⛔ Not applicable [3](#notes) |
| `CreateServiceSpecificCredential` | ⛔ Not applicable [1](#notes) |
| `CreateUser` | ✅ Implemented |
| `CreateVirtualMFADevice` | ❌ Not implemented |
| `DeactivateMFADevice` | ❌ Not implemented |
| `DeleteAccessKey` | ✅ Implemented |
| `DeleteAccountAlias` | ❌ Not implemented |
| `DeleteAccountPasswordPolicy` | ❌ Not implemented |
| `DeleteGroup` | ✅ Implemented |
| `DeleteGroupPolicy` | ✅ Implemented |
| `DeleteInstanceProfile` | ✅ Implemented |
| `DeleteLoginProfile` | ❌ Not implemented |
| `DeleteOpenIDConnectProvider` | ✅ Implemented |
| `DeletePolicy` | ✅ Implemented |
| `DeletePolicyVersion` | ❌ Not implemented |
| `DeleteRole` | ✅ Implemented |
| `DeleteRolePermissionsBoundary` | ❌ Not implemented |
| `DeleteRolePolicy` | ✅ Implemented |
| `DeleteSAMLProvider` | ❌ Not implemented |
| `DeleteSSHPublicKey` | ⛔ Not applicable [1](#notes) |
| `DeleteServerCertificate` | ❌ Not implemented |
| `DeleteServiceLinkedRole` | ⛔ Not applicable [3](#notes) |
| `DeleteServiceSpecificCredential` | ⛔ Not applicable [1](#notes) |
| `DeleteSigningCertificate` | ⛔ Not applicable [4](#notes) |
| `DeleteUser` | ✅ Implemented |
| `DeleteUserPermissionsBoundary` | ❌ Not implemented |
| `DeleteUserPolicy` | ✅ Implemented |
| `DeleteVirtualMFADevice` | ❌ Not implemented |
| `DetachGroupPolicy` | ✅ Implemented |
| `DetachRolePolicy` | ✅ Implemented |
| `DetachUserPolicy` | ✅ Implemented |
| `EnableMFADevice` | ❌ Not implemented |
| `GenerateCredentialReport` | ❌ Not implemented |
| `GenerateOrganizationsAccessReport` | ⛔ Not applicable [2](#notes) |
| `GenerateServiceLastAccessedDetails` | ❌ Not implemented |
| `GetAccessKeyLastUsed` | ❌ Not implemented |
| `GetAccountAuthorizationDetails` | ❌ Not implemented |
| `GetAccountPasswordPolicy` | ❌ Not implemented |
| `GetAccountSummary` | ✅ Implemented |
| `GetContextKeysForCustomPolicy` | ❌ Not implemented |
| `GetContextKeysForPrincipalPolicy` | ❌ Not implemented |
| `GetCredentialReport` | ❌ Not implemented |
| `GetGroup` | ✅ Implemented |
| `GetGroupPolicy` | ✅ Implemented |
| `GetInstanceProfile` | ✅ Implemented |
| `GetLoginProfile` | ❌ Not implemented |
| `GetMFADevice` | ❌ Not implemented |
| `GetOpenIDConnectProvider` | ✅ Implemented |
| `GetOrganizationsAccessReport` | ⛔ Not applicable [2](#notes) |
| `GetPolicy` | ✅ Implemented |
| `GetPolicyVersion` | ✅ Implemented |
| `GetRole` | ✅ Implemented |
| `GetRolePolicy` | ✅ Implemented |
| `GetSAMLProvider` | ❌ Not implemented |
| `GetSSHPublicKey` | ⛔ Not applicable [1](#notes) |
| `GetServerCertificate` | ❌ Not implemented |
| `GetServiceLastAccessedDetails` | ❌ Not implemented |
| `GetServiceLastAccessedDetailsWithEntities` | ❌ Not implemented |
| `GetServiceLinkedRoleDeletionStatus` | ⛔ Not applicable [3](#notes) |
| `GetUser` | ✅ Implemented |
| `GetUserPolicy` | ✅ Implemented |
| `ListAccessKeys` | ✅ Implemented |
| `ListAccountAliases` | ❌ Not implemented |
| `ListAttachedGroupPolicies` | ✅ Implemented |
| `ListAttachedRolePolicies` | ✅ Implemented |
| `ListAttachedUserPolicies` | ✅ Implemented |
| `ListEntitiesForPolicy` | ❌ Not implemented |
| `ListGroupPolicies` | ✅ Implemented |
| `ListGroups` | ✅ Implemented |
| `ListGroupsForUser` | ✅ Implemented |
| `ListInstanceProfileTags` | ✅ Implemented |
| `ListInstanceProfiles` | ✅ Implemented |
| `ListInstanceProfilesForRole` | ✅ Implemented |
| `ListMFADeviceTags` | ❌ Not implemented |
| `ListMFADevices` | ❌ Not implemented |
| `ListOpenIDConnectProviderTags` | ✅ Implemented |
| `ListOpenIDConnectProviders` | ✅ Implemented |
| `ListPolicies` | ✅ Implemented |
| `ListPoliciesGrantingServiceAccess` | ❌ Not implemented |
| `ListPolicyTags` | ✅ Implemented |
| `ListPolicyVersions` | ✅ Implemented |
| `ListRolePolicies` | ✅ Implemented |
| `ListRoleTags` | ✅ Implemented |
| `ListRoles` | ✅ Implemented |
| `ListSAMLProviderTags` | ❌ Not implemented |
| `ListSAMLProviders` | ❌ Not implemented |
| `ListSSHPublicKeys` | ⛔ Not applicable [1](#notes) |
| `ListServerCertificateTags` | ❌ Not implemented |
| `ListServerCertificates` | ❌ Not implemented |
| `ListServiceSpecificCredentials` | ⛔ Not applicable [1](#notes) |
| `ListSigningCertificates` | ⛔ Not applicable [4](#notes) |
| `ListUserPolicies` | ✅ Implemented |
| `ListUserTags` | ✅ Implemented |
| `ListUsers` | ✅ Implemented |
| `ListVirtualMFADevices` | ❌ Not implemented |
| `PutGroupPolicy` | ✅ Implemented |
| `PutRolePermissionsBoundary` | ❌ Not implemented |
| `PutRolePolicy` | ✅ Implemented |
| `PutUserPermissionsBoundary` | ❌ Not implemented |
| `PutUserPolicy` | ✅ Implemented |
| `RemoveClientIDFromOpenIDConnectProvider` | ❌ Not implemented |
| `RemoveRoleFromInstanceProfile` | ✅ Implemented |
| `RemoveUserFromGroup` | ✅ Implemented |
| `ResetServiceSpecificCredential` | ⛔ Not applicable [1](#notes) |
| `ResyncMFADevice` | ❌ Not implemented |
| `SetDefaultPolicyVersion` | ❌ Not implemented |
| `SetSecurityTokenServicePreferences` | ❌ Not implemented |
| `SimulateCustomPolicy` | ❌ Not implemented |
| `SimulatePrincipalPolicy` | ❌ Not implemented |
| `TagInstanceProfile` | ✅ Implemented |
| `TagMFADevice` | ❌ Not implemented |
| `TagOpenIDConnectProvider` | ✅ Implemented |
| `TagPolicy` | ✅ Implemented |
| `TagRole` | ✅ Implemented |
| `TagSAMLProvider` | ❌ Not implemented |
| `TagServerCertificate` | ❌ Not implemented |
| `TagUser` | ✅ Implemented |
| `UntagInstanceProfile` | ✅ Implemented |
| `UntagMFADevice` | ❌ Not implemented |
| `UntagOpenIDConnectProvider` | ✅ Implemented |
| `UntagPolicy` | ✅ Implemented |
| `UntagRole` | ✅ Implemented |
| `UntagSAMLProvider` | ❌ Not implemented |
| `UntagServerCertificate` | ❌ Not implemented |
| `UntagUser` | ✅ Implemented |
| `UpdateAccessKey` | ✅ Implemented |
| `UpdateAccountPasswordPolicy` | ❌ Not implemented |
| `UpdateAssumeRolePolicy` | ✅ Implemented |
| `UpdateGroup` | ❌ Not implemented |
| `UpdateLoginProfile` | ❌ Not implemented |
| `UpdateOpenIDConnectProviderThumbprint` | ❌ Not implemented |
| `UpdateRole` | ✅ Implemented |
| `UpdateRoleDescription` | ❌ Not implemented |
| `UpdateSAMLProvider` | ❌ Not implemented |
| `UpdateSSHPublicKey` | ⛔ Not applicable [1](#notes) |
| `UpdateServerCertificate` | ❌ Not implemented |
| `UpdateServiceSpecificCredential` | ⛔ Not applicable [1](#notes) |
| `UpdateSigningCertificate` | ⛔ Not applicable [4](#notes) |
| `UpdateUser` | ❌ Not implemented |
| `UploadSSHPublicKey` | ⛔ Not applicable [1](#notes) |
| `UploadServerCertificate` | ❌ Not implemented |
| `UploadSigningCertificate` | ⛔ Not applicable [4](#notes) |
### Notes
1. CodeCommit credentials and SSH keys; the service does not exist here.
2. AWS Organizations is not offered, so there is no organization to report on.
3. Service-linked roles exist for AWS service principals, which this platform has none of.
4. X.509 signing certificates are an EC2-Classic SOAP credential, retired by AWS.
+29 -20
View File
@@ -45,7 +45,10 @@
"vpc"
],
"notApplicable": {
"DescribeSpotPriceHistory": "On owned hardware there is no spot-to-on-demand price differential, so any figure would be invented."
"DescribeSpotPriceHistory": "spot-price"
},
"notes": {
"spot-price": "On owned hardware there is no spot-to-on-demand price differential, so any figure would be invented."
}
},
"ecr": {
@@ -123,25 +126,31 @@
"policies"
],
"notApplicable": {
"CreateServiceLinkedRole": "Service-linked roles exist for AWS service principals, which this platform has none of.",
"CreateServiceSpecificCredential": "CodeCommit credentials; the service does not exist here.",
"DeleteSSHPublicKey": "CodeCommit SSH keys; the service does not exist here.",
"DeleteServiceLinkedRole": "Service-linked roles exist for AWS service principals, which this platform has none of.",
"DeleteServiceSpecificCredential": "CodeCommit credentials; the service does not exist here.",
"DeleteSigningCertificate": "X.509 signing certificates are an EC2-Classic SOAP credential, retired by AWS.",
"GenerateOrganizationsAccessReport": "AWS Organizations is not offered, so there is no organization to report on.",
"GetOrganizationsAccessReport": "AWS Organizations is not offered, so there is no organization to report on.",
"GetSSHPublicKey": "CodeCommit SSH keys; the service does not exist here.",
"GetServiceLinkedRoleDeletionStatus": "Service-linked roles exist for AWS service principals, which this platform has none of.",
"ListSSHPublicKeys": "CodeCommit SSH keys; the service does not exist here.",
"ListServiceSpecificCredentials": "CodeCommit credentials; the service does not exist here.",
"ListSigningCertificates": "X.509 signing certificates are an EC2-Classic SOAP credential, retired by AWS.",
"ResetServiceSpecificCredential": "CodeCommit credentials; the service does not exist here.",
"UpdateSSHPublicKey": "CodeCommit SSH keys; the service does not exist here.",
"UpdateServiceSpecificCredential": "CodeCommit credentials; the service does not exist here.",
"UpdateSigningCertificate": "X.509 signing certificates are an EC2-Classic SOAP credential, retired by AWS.",
"UploadSSHPublicKey": "CodeCommit SSH keys; the service does not exist here.",
"UploadSigningCertificate": "X.509 signing certificates are an EC2-Classic SOAP credential, retired by AWS."
"CreateServiceLinkedRole": "service-linked-roles",
"CreateServiceSpecificCredential": "codecommit",
"DeleteSSHPublicKey": "codecommit",
"DeleteServiceLinkedRole": "service-linked-roles",
"DeleteServiceSpecificCredential": "codecommit",
"DeleteSigningCertificate": "signing-certificates",
"GenerateOrganizationsAccessReport": "organizations",
"GetOrganizationsAccessReport": "organizations",
"GetSSHPublicKey": "codecommit",
"GetServiceLinkedRoleDeletionStatus": "service-linked-roles",
"ListSSHPublicKeys": "codecommit",
"ListServiceSpecificCredentials": "codecommit",
"ListSigningCertificates": "signing-certificates",
"ResetServiceSpecificCredential": "codecommit",
"UpdateSSHPublicKey": "codecommit",
"UpdateServiceSpecificCredential": "codecommit",
"UpdateSigningCertificate": "signing-certificates",
"UploadSSHPublicKey": "codecommit",
"UploadSigningCertificate": "signing-certificates"
},
"notes": {
"codecommit": "CodeCommit credentials and SSH keys; the service does not exist here.",
"organizations": "AWS Organizations is not offered, so there is no organization to report on.",
"service-linked-roles": "Service-linked roles exist for AWS service principals, which this platform has none of.",
"signing-certificates": "X.509 signing certificates are an EC2-Classic SOAP credential, retired by AWS."
}
},
"rds": {
+29 -2
View File
@@ -19,7 +19,7 @@ tags:
## Overview
Spinifex implements **26 of the 162** operations (16.0%) in the RDS `2014-10-31` API model.
Spinifex implements **26 of the 162** operations (**16.0%**) in the RDS `2014-10-31` API model.
### Engines
@@ -41,7 +41,34 @@ Enforcement requires that the connection is encrypted, not that the client valid
### Rejected parameters
A parameter whose omission would create a false safety, security or availability guarantee is rejected rather than silently dropped — `MultiAZ=true` on a single-AZ platform, `PubliclyAccessible=true` against a private endpoint, `StorageEncrypted=false` where unencrypted storage is not offered. Parameters that are merely inert are accepted as no-ops.
A parameter whose omission would create a false safety, security or availability guarantee is rejected with `InvalidParameterValue` rather than silently dropped. Parameters that are merely inert — `AutoMinorVersionUpgrade`, `CopyTagsToSnapshot`, `DeleteAutomatedBackups`, Performance Insights and Enhanced Monitoring fields — are accepted as no-ops.
| Parameter | Why it is rejected |
|-----------|--------------------|
| `MultiAZ=true` | Single-AZ platform; a standby would not exist |
| `PubliclyAccessible=true` | The endpoint is a private VPC address |
| `StorageEncrypted=false` | Unencrypted storage is not offered |
| `EnableIAMDatabaseAuthentication` | IAM database authentication is not implemented |
| `Iops`, `StorageThroughput`, `StorageType` ≠ `gp3` | Provisioned performance classes are not implemented |
| `KmsKeyId`, `TdeCredentialArn` | Storage is encrypted with the cluster key, not a customer-managed one |
| `AvailabilityZone` | The platform exposes a single zone |
| `AvailabilityZoneGroup` (orderable options) | It selects a zone or local-zone group, and naming a zone is already refused |
| `DBSecurityGroups` | EC2-Classic security groups — use `VpcSecurityGroupIds` |
| `DBClusterIdentifier`, `DBClusterSnapshotIdentifier` | Clustered engines are not offered |
| `EnableCloudwatchLogsExports` | Log export is not implemented |
| `EngineVersion` other than the engine's pin, `Engine` on modify | No in-place engine or version change |
| `Engine=mysql` (and Aurora engines) | Oracle MySQL is not offered; `mariadb` is a distinct engine, not an alias for it |
| `NewDBInstanceIdentifier` | The identifier is the DNS label and the KV key |
| `DBPortNumber`, `DBSubnetGroupName` on modify | Both would move the endpoint |
| `MaxAllocatedStorage` | Storage autoscaling is not implemented |
| `ManageMasterUserPassword`, `RotateMasterUserPassword` | Secrets Manager integration is not offered |
| `CACertificateIdentifier` | The serving certificate is minted from the cluster CA |
| `Domain`, `DomainFqdn` | Active Directory domain join is not offered |
| `OptionGroupName` | Option groups are not offered |
| `CustomIamInstanceProfile` | The DB VM's instance profile is platform-owned |
| `EnableCustomerOwnedIp` | An Outposts feature |
| `ForceFailover` (reboot) | No standby to fail over to |
| `DBSnapshotIdentifier` (stop) | Snapshot-on-stop is not implemented |
### Operations
+28 -1
View File
@@ -18,4 +18,31 @@ Enforcement requires that the connection is encrypted, not that the client valid
### Rejected parameters
A parameter whose omission would create a false safety, security or availability guarantee is rejected rather than silently dropped — `MultiAZ=true` on a single-AZ platform, `PubliclyAccessible=true` against a private endpoint, `StorageEncrypted=false` where unencrypted storage is not offered. Parameters that are merely inert are accepted as no-ops.
A parameter whose omission would create a false safety, security or availability guarantee is rejected with `InvalidParameterValue` rather than silently dropped. Parameters that are merely inert — `AutoMinorVersionUpgrade`, `CopyTagsToSnapshot`, `DeleteAutomatedBackups`, Performance Insights and Enhanced Monitoring fields — are accepted as no-ops.
| Parameter | Why it is rejected |
|-----------|--------------------|
| `MultiAZ=true` | Single-AZ platform; a standby would not exist |
| `PubliclyAccessible=true` | The endpoint is a private VPC address |
| `StorageEncrypted=false` | Unencrypted storage is not offered |
| `EnableIAMDatabaseAuthentication` | IAM database authentication is not implemented |
| `Iops`, `StorageThroughput`, `StorageType` ≠ `gp3` | Provisioned performance classes are not implemented |
| `KmsKeyId`, `TdeCredentialArn` | Storage is encrypted with the cluster key, not a customer-managed one |
| `AvailabilityZone` | The platform exposes a single zone |
| `AvailabilityZoneGroup` (orderable options) | It selects a zone or local-zone group, and naming a zone is already refused |
| `DBSecurityGroups` | EC2-Classic security groups — use `VpcSecurityGroupIds` |
| `DBClusterIdentifier`, `DBClusterSnapshotIdentifier` | Clustered engines are not offered |
| `EnableCloudwatchLogsExports` | Log export is not implemented |
| `EngineVersion` other than the engine's pin, `Engine` on modify | No in-place engine or version change |
| `Engine=mysql` (and Aurora engines) | Oracle MySQL is not offered; `mariadb` is a distinct engine, not an alias for it |
| `NewDBInstanceIdentifier` | The identifier is the DNS label and the KV key |
| `DBPortNumber`, `DBSubnetGroupName` on modify | Both would move the endpoint |
| `MaxAllocatedStorage` | Storage autoscaling is not implemented |
| `ManageMasterUserPassword`, `RotateMasterUserPassword` | Secrets Manager integration is not offered |
| `CACertificateIdentifier` | The serving certificate is minted from the cluster CA |
| `Domain`, `DomainFqdn` | Active Directory domain join is not offered |
| `OptionGroupName` | Option groups are not offered |
| `CustomIamInstanceProfile` | The DB VM's instance profile is platform-owned |
| `EnableCustomerOwnedIp` | An Outposts feature |
| `ForceFailover` (reboot) | No standby to fail over to |
| `DBSnapshotIdentifier` (stop) | Snapshot-on-stop is not implemented |
@@ -18,7 +18,7 @@ tags:
## Overview
Spinifex implements **4 of the 8** operations (50.0%) in the STS `2011-06-15` API model.
Spinifex implements **4 of the 8** operations (**50.0%**) in the STS `2011-06-15` API model.
### Trust policies
@@ -16,8 +16,8 @@ sections:
- instructions
- troubleshooting
resources:
- title: "RDS CLI Reference"
url: "https://github.com/mulgadc/spinifex/blob/main/docs/COMMANDS.md#rds-postgresql-and-mariadb"
- title: "RDS API Coverage"
url: "/docs/rds-api-coverage"
- title: "Terraform AWS Provider — aws_db_instance"
url: "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance"
- title: "Spinifex Repository"
@@ -30,7 +30,7 @@ resources:
> The smallest end-to-end RDS example on Spinifex: a VPC with a public client subnet and a private DB subnet, a DB subnet group, a parameter group, a PostgreSQL DB instance, and a client VM with `psql` already pointed at the endpoint.
This workbook is **PostgreSQL throughout** — the engine, the `postgres18` parameter group, port `5432`, the client package and the `~/.pgpass` credential are all PostgreSQL's. Spinifex also serves MariaDB 11.8 under `engine = "mariadb"`; running this workbook against it means changing `engine`, `engine_version`, the parameter-group `family` to `mariadb11.8`, the security-group port to `3306` and the client to `mariadb-client`. See the [RDS command reference](https://github.com/mulgadc/spinifex/blob/main/docs/COMMANDS.md#rds-postgresql-and-mariadb) for both engines' pins, families and limits. Note that `mysql` is not an accepted engine and is not an alias for `mariadb`.
This workbook is **PostgreSQL throughout** — the engine, the `postgres18` parameter group, port `5432`, the client package and the `~/.pgpass` credential are all PostgreSQL's. Spinifex also serves MariaDB 11.8 under `engine = "mariadb"`; running this workbook against it means changing `engine`, `engine_version`, the parameter-group `family` to `mariadb11.8`, the security-group port to `3306` and the client to `mariadb-client`. See the [RDS API coverage page](/docs/rds-api-coverage) for both engines' pins, families and limits. Note that `mysql` is not an accepted engine and is not an alias for `mariadb`.
## Overview
+76 -26
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"unicode/utf8"
@@ -31,11 +32,12 @@ const (
var slugPattern = regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`)
// OperationStatus is one row of a published coverage table.
// OperationStatus is one row of a published coverage table. NoteKey names the
// shared explanation behind the row's status, where one is declared.
type OperationStatus struct {
Operation string
Status string
Note string
NoteKey string
}
// ImplementedPercent is the share of modelled operations bound to a real
@@ -51,18 +53,18 @@ func (c OperationCoverage) ImplementedPercent() float64 {
// followed by the registered operations the pinned model does not describe.
//
// notApplicable declares operations the platform will never serve, mapped to
// the reason why. A refusing handler is the same fact reached mechanically, so
// both publish as StatusNotApplicable; the declaration only adds the reason.
// the note key explaining why. A refusing handler is the same fact reached
// mechanically, so both publish as StatusNotApplicable.
func (c OperationCoverage) OperationStatuses(notApplicable map[string]string) ([]OperationStatus, error) {
registered := toSet(c.Registered)
stubbed := toSet(c.Stubbed)
unsupported := toSet(c.Unsupported)
modelled := toSet(c.Modelled)
for operation, reason := range notApplicable {
for operation, noteKey := range notApplicable {
switch {
case reason == "":
return nil, fmt.Errorf("awsmodel: %s operation %q is declared not applicable with no reason", c.Service, operation)
case noteKey == "":
return nil, fmt.Errorf("awsmodel: %s operation %q is declared not applicable with no note", c.Service, operation)
case !modelled[operation]:
return nil, fmt.Errorf("awsmodel: %s operation %q is declared not applicable but is not in the pinned model", c.Service, operation)
case registered[operation] && !unsupported[operation]:
@@ -81,8 +83,8 @@ func (c OperationCoverage) OperationStatuses(notApplicable map[string]string) ([
case registered[operation]:
status = StatusImplemented
}
if reason, ok := notApplicable[operation]; ok {
statuses = append(statuses, OperationStatus{Operation: operation, Status: StatusNotApplicable, Note: reason})
if noteKey, ok := notApplicable[operation]; ok {
statuses = append(statuses, OperationStatus{Operation: operation, Status: StatusNotApplicable, NoteKey: noteKey})
continue
}
statuses = append(statuses, OperationStatus{Operation: operation, Status: status})
@@ -104,9 +106,14 @@ type PageMetadata struct {
Description string `json:"description"`
Tags []string `json:"tags"`
// NotApplicable maps an operation the platform will never serve to the
// reason why. Checked against the model and the dispatch tables at render,
// so a typo or a later implementation fails the build rather than lying.
// Notes are the shared explanations a page footnotes, keyed by a short
// name. One note covers every operation that shares a reason.
Notes map[string]string `json:"notes,omitempty"`
// NotApplicable maps an operation the platform will never serve to the key
// of the note explaining why. Checked against the model, the notes and the
// dispatch tables at render, so a typo or a later implementation fails the
// build rather than publishing a claim that has quietly become false.
NotApplicable map[string]string `json:"notApplicable,omitempty"`
}
@@ -182,6 +189,42 @@ func validatePage(owner string, page PageMetadata, slugs map[string]bool) error
return nil
}
// noteOrder returns the note keys in the order they are published. Sorted by
// key rather than by first use, so adding an operation cannot renumber a note
// that is already published.
func noteOrder(notes map[string]string) []string {
keys := make([]string, 0, len(notes))
for key := range notes {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
// numberNotes assigns each note its published footnote number, rejecting a note
// nothing references and a reference to a note that does not exist.
func numberNotes(page PageMetadata) (map[string]int, error) {
referenced := map[string]bool{}
for operation, key := range page.NotApplicable {
if _, ok := page.Notes[key]; !ok {
return nil, fmt.Errorf("awsmodel: coverage page %s: operation %q references undefined note %q", page.Slug, operation, key)
}
referenced[key] = true
}
numbering := make(map[string]int, len(page.Notes))
for index, key := range noteOrder(page.Notes) {
if !referenced[key] {
return nil, fmt.Errorf("awsmodel: coverage page %s: note %q is never referenced", page.Slug, key)
}
if page.Notes[key] == "" {
return nil, fmt.Errorf("awsmodel: coverage page %s: note %q is empty", page.Slug, key)
}
numbering[key] = index + 1
}
return numbering, nil
}
// RenderServicePage renders one publishable coverage page. intro is optional
// hand-written prose emitted above the table; it may not open a new page
// section, so its headings must be level three or deeper.
@@ -203,7 +246,7 @@ func RenderServicePage(coverage OperationCoverage, pages PageSet, intro string)
writeFrontmatter(&body, page, pages.Category)
fmt.Fprintf(&body, "# %s\n\n## Overview\n\n", page.Title)
fmt.Fprintf(&body, "Spinifex implements **%d of the %d** operations (%.1f%%) in the %s `%s` API model.\n\n",
fmt.Fprintf(&body, "Spinifex implements **%d of the %d** operations (**%.1f%%**) in the %s `%s` API model.\n\n",
len(coverage.Implemented), len(coverage.Modelled), coverage.ImplementedPercent(), page.Name, coverage.APIVersion)
if intro != "" {
@@ -214,18 +257,25 @@ func RenderServicePage(coverage OperationCoverage, pages PageSet, intro string)
if err != nil {
return "", err
}
body.WriteString("### Operations\n\n")
if len(page.NotApplicable) == 0 {
body.WriteString("| Operation | Status |\n|---|---|\n")
for _, status := range statuses {
fmt.Fprintf(&body, "| `%s` | %s |\n", status.Operation, status.Status)
}
return body.String(), nil
numbering, err := numberNotes(page)
if err != nil {
return "", err
}
body.WriteString("| Operation | Status | Notes |\n|---|---|---|\n")
body.WriteString("### Operations\n\n| Operation | Status |\n|---|---|\n")
for _, status := range statuses {
fmt.Fprintf(&body, "| `%s` | %s | %s |\n", status.Operation, status.Status, status.Note)
if number, ok := numbering[status.NoteKey]; ok {
fmt.Fprintf(&body, "| `%s` | %s [%d](#notes) |\n", status.Operation, status.Status, number)
continue
}
fmt.Fprintf(&body, "| `%s` | %s |\n", status.Operation, status.Status)
}
if len(numbering) > 0 {
body.WriteString("\n### Notes\n\n")
for index, key := range noteOrder(page.Notes) {
fmt.Fprintf(&body, "%d. %s\n", index+1, page.Notes[key])
}
}
return body.String(), nil
}
@@ -240,14 +290,14 @@ func RenderIndexPage(coverages []OperationCoverage, pages PageSet, intro string)
writeFrontmatter(&body, pages.Index, pages.Category)
fmt.Fprintf(&body, "# %s\n\n## Overview\n\n", pages.Index.Title)
fmt.Fprintf(&body, "Spinifex serves the AWS APIs below. Every page counts the operations in the pinned `aws-sdk-go %s` `api-2.json` model for its service and reports, operation by operation, whether Spinifex implements it.\n\n", SourceSDKVersion)
body.WriteString("| Service | API version | Implemented | Modelled | Coverage |\n|---|---|---:|---:|---:|\n")
body.WriteString("| Service | Implemented | Modelled | Coverage |\n|---|---:|---:|---:|\n")
for _, coverage := range sortedCoverages(coverages) {
page, ok := pages.Services[coverage.Service]
if !ok {
continue
}
fmt.Fprintf(&body, "| [%s](/docs/%s) | %s | %d | %d | %.1f%% |\n",
page.Name, page.Slug, coverage.APIVersion, len(coverage.Implemented), len(coverage.Modelled), coverage.ImplementedPercent())
fmt.Fprintf(&body, "| [%s](/docs/%s) | %d | %d | %.1f%% |\n",
page.Name, page.Slug, len(coverage.Implemented), len(coverage.Modelled), coverage.ImplementedPercent())
}
if intro != "" {
+68 -5
View File
@@ -77,7 +77,7 @@ func TestRenderServicePageCarriesEveryStatus(t *testing.T) {
`category: "Coverage"`,
"sections:\n - overview\n",
"# STS API Coverage\n\n## Overview\n",
"Spinifex implements **1 of the 8** operations (12.5%) in the STS `2011-06-15` API model.",
"Spinifex implements **1 of the 8** operations (**12.5%**) in the STS `2011-06-15` API model.",
"### Scope\n\nHand-written prose.",
"| `AssumeRole` | " + StatusImplemented + " |",
"| `GetSessionToken` | " + StatusStub + " |",
@@ -164,13 +164,13 @@ func TestOperationStatusesRejectsStaleNotApplicableClaims(t *testing.T) {
}
}
func TestOperationStatusesPublishesDeclaredReasons(t *testing.T) {
func TestOperationStatusesCarriesTheNoteKey(t *testing.T) {
coverage, err := CompareOperations(STS, DispatchInventory{Registered: []string{"AssumeRole"}})
if err != nil {
t.Fatal(err)
}
statuses, err := coverage.OperationStatuses(map[string]string{"GetFederationToken": "No federation broker."})
statuses, err := coverage.OperationStatuses(map[string]string{"GetFederationToken": "no-broker"})
if err != nil {
t.Fatal(err)
}
@@ -178,14 +178,77 @@ func TestOperationStatusesPublishesDeclaredReasons(t *testing.T) {
if status.Operation != "GetFederationToken" {
continue
}
if status.Status != StatusNotApplicable || status.Note != "No federation broker." {
t.Fatalf("status = %q, note = %q", status.Status, status.Note)
if status.Status != StatusNotApplicable || status.NoteKey != "no-broker" {
t.Fatalf("status = %q, note key = %q", status.Status, status.NoteKey)
}
return
}
t.Fatal("declared operation is missing from the table")
}
// A footnote reused across operations is written once, and both directions of
// the reference are checked so neither side can rot.
func TestRenderServicePageFootnotesSharedNotes(t *testing.T) {
pages := testPageSet(t)
iam := pages.Services[IAM]
if len(iam.Notes) == 0 {
t.Skip("no notes are declared for IAM")
}
coverage, err := CompareOperations(IAM, DispatchInventory{Registered: []string{"CreateUser"}})
if err != nil {
t.Fatal(err)
}
page, err := RenderServicePage(coverage, pages, "")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(page, "\n### Notes\n\n1. ") {
t.Errorf("page has no numbered notes section:\n%s", page)
}
if !strings.Contains(page, "| `UploadSSHPublicKey` | "+StatusNotApplicable+" [") {
t.Errorf("a declared operation carries no footnote reference:\n%s", page)
}
for _, text := range iam.Notes {
if strings.Count(page, text) != 1 {
t.Errorf("note %q is not written exactly once", text)
}
}
}
func TestRenderServicePageRejectsBrokenNoteReferences(t *testing.T) {
pages := testPageSet(t)
coverage, err := CompareOperations(STS, DispatchInventory{Registered: []string{"AssumeRole"}})
if err != nil {
t.Fatal(err)
}
sts := pages.Services[STS]
for name, mutate := range map[string]func(*PageMetadata){
"undefined note": func(p *PageMetadata) {
p.NotApplicable = map[string]string{"GetFederationToken": "nonexistent"}
},
"unreferenced note": func(p *PageMetadata) {
p.Notes = map[string]string{"orphan": "Nothing points at this."}
},
"empty note": func(p *PageMetadata) {
p.Notes = map[string]string{"blank": ""}
p.NotApplicable = map[string]string{"GetFederationToken": "blank"}
},
} {
t.Run(name, func(t *testing.T) {
broken := sts
mutate(&broken)
pages.Services[STS] = broken
defer func() { pages.Services[STS] = sts }()
if _, err := RenderServicePage(coverage, pages, ""); err == nil {
t.Fatal("RenderServicePage accepted a broken note reference")
}
})
}
}
// An opaque service has no dispatch table to count, so publishing a page for it
// would report 0% where the truth is that the surface is served elsewhere.
func TestRenderServicePageRefusesOpaqueCoverage(t *testing.T) {