Behaviour Coverage
A prediction from chant graph --traffic reaches an engine with every entity the project declares, and not every entity has a rate. This page is the whole of what the lexicons say about their own entities, generated from the rows each contributes through its plugin’s behaviourKinds field. A table transcribed by hand stops being true the first time a row changes, so test/behaviour-coverage.test.ts fails when this page and the rows disagree.
Three verdicts, not two
Section titled “Three verdicts, not two”An entity resolves to exactly one verdict, and the second and third are different answers rather than one answer written twice.
| Verdict | What it means | Where the entity lands |
|---|---|---|
| Mapped | an engine prices it, as a kind, with its size read from a declared property | entities, with figures |
| Declared unmapped | its lexicon has looked at this kind of entity and it carries no rate, and the row says why | unpredicted, reason unsupported-kind, with the row’s sentence as the detail |
| No row | its lexicon owns the entity and has never looked at it | unpredicted, reason unsupported-kind, with a detail saying so and naming the lexicon |
The distinction between the last two is why the second table in each section exists. “A role is a grant, not a resource traffic flows through” is an answer a reader can act on. “Nothing has an opinion about this” is a missing row in the owning lexicon and should be filed as one. Collapsing them would leave a reader unable to tell a decision from an omission, which is the observation contract’s “absent is not the same as unread”, applied to prediction.
A fourth outcome, provider-not-modelled, is a boundary rather than a verdict about one row. The terraform lexicon declares resources of every provider there is and models one of them, so a google_ resource is reported as belonging to a substrate nothing here models, and nothing needs filing for it.
Neither road ends in a dropped entity or a zero. The request builder refuses a report that leaves an entity in neither map, so one that vanished is a build failure rather than an estate that looks smaller than it is.
Reading a row
Section titled “Reading a row”The size is the value of the named property, sent verbatim. t3.medium and db.r6g.xlarge are the provider’s own vocabulary, which an engine’s price model is written against, and parsing either into a vCPU count here would put a lossy hop in front of a model that already knows the string. Where the declaration states no size, the request carries none, because a guessed size is a guessed price. A property of the wrong shape (a number where a string is named) is absent rather than coerced.
The region is the request’s unless the row names a property the entity states its own zone in.
The last table in each section is for whatever has no row: the rules a lexicon states as code rather than as rows, each shown with the sentence it produces, and the unknown-type detail a reader will see for anything the lexicon owns and has not classified.
The rows, per lexicon
Section titled “The rows, per lexicon”| Owns | Rows keyed by | Provider | Contributed from |
|---|---|---|---|
AWS:: | the entity type, which is the CloudFormation resource type | aws | lexicons/aws/src/behaviour-kinds.ts |
Mapped (15)
Section titled “Mapped (15)”| Type | Engine kind | Size read from | Region |
|---|---|---|---|
AWS::CloudFront::Distribution | cdn | none | the request’s |
AWS::DynamoDB::Table | database | BillingMode (string) | the request’s |
AWS::EC2::Instance | compute | InstanceType (string) | the request’s |
AWS::EC2::Volume | block-store | Size (number) | AvailabilityZone, else the request’s |
AWS::ECS::Service | compute | none | the request’s |
AWS::EKS::Cluster | control-plane | none | the request’s |
AWS::EKS::Nodegroup | compute | none | the request’s |
AWS::ElastiCache::CacheCluster | cache | CacheNodeType (string) | the request’s |
AWS::ElasticLoadBalancingV2::LoadBalancer | load-balancer | Type (string) | the request’s |
AWS::Lambda::Function | serverless | MemorySize (number) | the request’s |
AWS::RDS::DBCluster | database | DBClusterInstanceClass (string) | the request’s |
AWS::RDS::DBInstance | database | DBInstanceClass (string) | the request’s |
AWS::S3::Bucket | object-store | none | the request’s |
AWS::SNS::Topic | queue | none | the request’s |
AWS::SQS::Queue | queue | none | the request’s |
Declared unmapped (38)
Section titled “Declared unmapped (38)”| Type | Why it carries no rate |
|---|---|
AWS::BedrockAgentCore::Gateway | a front door to an agent runtime, billed by what passes through it rather than by existing |
AWS::BedrockAgentCore::GatewayTarget | one target behind a gateway; the gateway and the runtime are where the meters are |
AWS::BedrockAgentCore::Memory | agent memory billed by what is stored and retrieved, which the declaration does not state |
AWS::BedrockAgentCore::Runtime | an agent runtime billed per invocation and per token, neither of which the declaration states; there is no hour of existence to price |
AWS::BedrockAgentCore::WorkloadIdentity | an identity a runtime acts as; a grant, with no rate and no capacity |
AWS::CloudFormation::Condition | a template predicate deciding whether something is created; it is never created itself |
AWS::CloudFormation::Parameter | a template input, not a thing the account holds. It shapes what is created and is never created itself |
AWS::EC2::EIP | priced only while it is attached to nothing, which is a fact about the running account rather than about the declaration |
AWS::EC2::InternetGateway | a boundary crossing rather than a resource: no rate of its own and no capacity to saturate |
AWS::EC2::NatGateway | priced as an hourly rate plus a per-gigabyte meter on everything that crosses it. The declaration states the first and nothing at all about the second, and no engine kind here models a half-known price. A figure covering the hourly half alone would read as the cost of the gateway and be wrong by whatever the traffic did |
AWS::EC2::Route | routing, which decides where an edge goes and is not itself at either end of one |
AWS::EC2::RouteTable | routing, which decides where an edge goes and is not itself at either end of one |
AWS::EC2::SecurityGroup | a filter on edges that already exist, not a thing at either end of one |
AWS::EC2::Subnet | a network boundary rather than a node: its contribution to a prediction is the zone membership that reaches the engine as containment coverage |
AWS::EC2::SubnetRouteTableAssociation | an association between two boundaries; nothing flows through it that does not already flow through them |
AWS::EC2::VPC | a network boundary rather than a node: its contribution to a prediction is the zone membership that reaches the engine as containment coverage |
AWS::EC2::VPCGatewayAttachment | an attachment between two boundaries, and neither end is priced by it |
AWS::ECR::Repository | priced by the gigabytes stored in it, which the declaration does not state |
AWS::ECS::Cluster | a namespace for services rather than capacity of its own; the services and their nodes carry the figures |
AWS::ECS::TaskDefinition | a template a service runs copies of; the service carries the figures, and pricing both would count the estate twice |
AWS::ElasticLoadBalancingV2::Listener | a port on a load balancer; the load balancer carries the rate |
AWS::ElasticLoadBalancingV2::ListenerRule | a routing rule on a listener, and a rule is not a thing at either end of an edge |
AWS::ElasticLoadBalancingV2::TargetGroup | a set of targets behind a load balancer; the load balancer carries the rate |
AWS::Events::Rule | priced by the events matched, which is a fact about traffic the declaration does not state |
AWS::IAM::InstanceProfile | a wrapper that hands a role to an instance; the instance carries the figures |
AWS::IAM::ManagedPolicy | a policy document is a statement about permission; nothing about it saturates or accrues |
AWS::IAM::Policy | a policy document is a statement about permission; nothing about it saturates or accrues |
AWS::IAM::Role | a role is a grant, not a resource traffic flows through: no rate, no capacity, no verdict under a lost zone |
AWS::KMS::Key | priced per key per month plus a per-request meter the declaration does not state, and neither half is an hourly rate |
AWS::Lambda::EventSourceMapping | the wiring between a queue and a function, both of which are priced where they are declared |
AWS::Lambda::Permission | a grant letting one service invoke a function; the function carries the figures |
AWS::Logs::LogGroup | priced by the volume ingested into it, which the declaration does not state and no engine can infer from a graph |
AWS::RDS::DBSubnetGroup | the set of subnets a database may sit in; the database carries the figures |
AWS::Route53::HostedZone | priced per zone per month plus a per-query meter the declaration does not state |
AWS::S3::BucketPolicy | a bucket policy is a grant on the bucket beside it, and the bucket carries the figures |
AWS::SNS::Subscription | priced by the notifications delivered through it; the topic is what the graph has an edge to |
AWS::SQS::QueuePolicy | a queue policy is a grant on the queue beside it, and the queue carries the figures |
AWS::WAFv2::WebACL | priced by the requests inspected through it, which the declaration does not state |
| A type with no row above | Verdict | What the unpredicted entry says |
|---|---|---|
AWS::S3::Bucket.VersioningConfiguration, by rule | declared unmapped | a CloudFormation property type: a nested block of the resource above it, which became an entity of its own in the build. The resource carries the figures, and pricing the block as well would count part of the estate twice |
any other AWS:: type | unknown-type, the one verdict that is a defect | the entity has no row in any contributed coverage table. It is a type from a substrate that is modelled, and is neither mapped to an engine kind nor declared unmapped, so nothing has an opinion about it rather than a stated one. Add a row in the lexicon that owns AWS::. |
| Owns | Rows keyed by | Provider | Contributed from |
|---|---|---|---|
K8s:: | the entity type, which names the API group and kind | kubernetes | lexicons/k8s/src/behaviour-kinds.ts |
Mapped (9)
Section titled “Mapped (9)”| Type | Engine kind | Size read from | Region |
|---|---|---|---|
K8s::Apps::DaemonSet | compute | none | the request’s |
K8s::Apps::Deployment | compute | none | the request’s |
K8s::Apps::StatefulSet | compute | none | the request’s |
K8s::Batch::CronJob | serverless | none | the request’s |
K8s::Batch::Job | serverless | none | the request’s |
K8s::Core::PersistentVolumeClaim | block-store | spec.resources.requests.storage (string) | the request’s |
K8s::Core::Pod | compute | none | the request’s |
K8s::Core::Service | load-balancer | spec.type (string) | the request’s |
K8s::Networking::Ingress | load-balancer | none | the request’s |
Declared unmapped (30)
Section titled “Declared unmapped (30)”| Type | Why it carries no rate |
|---|---|
K8s::Argo::Application | a request to Argo CD to reconcile a source; whatever it creates is priced where that lands |
K8s::Autoscaling::HorizontalPodAutoscaler | an instruction about how many replicas to run; the workload it scales carries the figures, and a prediction is at one stated traffic level rather than across a scaling range |
K8s::Calico::FelixConfiguration | tuning for the CNI agent on every node; the nodes carry the figures |
K8s::CertManager::Certificate | a request to an issuer. The certificate costs nothing; the issuer’s work and the Secret it writes are elsewhere |
K8s::CertManager::ClusterIssuer | the issuer a certificate request names; it holds no capacity and serves no traffic |
K8s::Core::ConfigMap | configuration the workloads read; it has no rate and no saturation axis of its own |
K8s::Core::LimitRange | a default and a ceiling for workloads in a namespace; the workloads carry the figures |
K8s::Core::Namespace | a boundary the workloads sit inside, and the workloads carry the figures |
K8s::Core::ResourceQuota | a ceiling on a namespace’s total requests; the workloads under it carry the figures |
K8s::Core::Secret | configuration the workloads read; it has no rate and no saturation axis of its own |
K8s::Core::ServiceAccount | an identity, not a workload |
K8s::ExternalSecrets::ClusterSecretStore | names where secrets are fetched from; it holds no capacity and serves no traffic |
K8s::ExternalSecrets::ExternalSecret | a request to copy a value from an external store into a Secret; neither end is a rate |
K8s::Flux::GitRepository | a source Flux polls; the workloads it reconciles carry the figures |
K8s::Flux::Kustomization | a request to Flux to apply a source; whatever it creates is priced where that lands |
K8s::GKE::BackendConfig | tuning for a Google load balancer an Ingress creates; the gcp substrate is not modelled here, so neither end is priced here |
K8s::HorizontalPodAutoscaler | an instruction about how many replicas to run; the workload it scales carries the figures, and a prediction is at one stated traffic level rather than across a scaling range |
K8s::Monitoring::PrometheusRule | alerting and recording rules evaluated by Prometheus, which carries the figures |
K8s::Monitoring::ServiceMonitor | tells Prometheus what to scrape; Prometheus is a workload elsewhere in the graph and carries the figures |
K8s::Networking::IngressClass | names which controller handles an Ingress; the controller and the load balancer it creates carry the figures |
K8s::Networking::NetworkPolicy | a filter on edges that already exist, not a thing at either end of one |
K8s::NetworkingGKE::ManagedCertificate | a certificate Google issues for an Ingress; the gcp substrate is not modelled here |
K8s::NetworkingGKEBeta::FrontendConfig | tuning for a Google load balancer an Ingress creates; the gcp substrate is not modelled here |
K8s::Policy::PodDisruptionBudget | a constraint on voluntary eviction. It shapes what a lost zone does to a workload and has no rate of its own; expressing that constraint to an engine is a resilience input this contract has no field for |
K8s::Rbac::ClusterRole | the cluster-scoped twin of an RBAC role, and a grant for the same reason |
K8s::Rbac::ClusterRoleBinding | the cluster-scoped twin of an RBAC binding, and a grant for the same reason |
K8s::Rbac::Role | an RBAC role is a grant; the workloads it admits are what cost and saturate |
K8s::Rbac::RoleBinding | an RBAC binding is a grant; the workloads it admits are what cost and saturate |
K8s::Storage::StorageClass | the kind of disk a claim may ask for; the PersistentVolumeClaim carries the figures |
K8s::Traefik::IngressRoute | routing handled by the Traefik workload, which carries the figures |
| A type with no row above | Verdict | What the unpredicted entry says |
|---|---|---|
any other K8s:: type | unknown-type, the one verdict that is a defect | the entity has no row in any contributed coverage table. It is a type from a substrate that is modelled, and is neither mapped to an engine kind nor declared unmapped, so nothing has an opinion about it rather than a stated one. Add a row in the lexicon that owns K8s::. |
terraform
Section titled “terraform”| Owns | Rows keyed by | Provider | Contributed from |
|---|---|---|---|
Terraform:: | the provider type in a resource block’s address (aws_instance), since every such block arrives as Terraform::Resource; the root’s other blocks key by their entity type | aws | lexicons/terraform/src/behaviour/kinds.ts |
Mapped (17)
Section titled “Mapped (17)”| Type | Engine kind | Size read from | Region |
|---|---|---|---|
aws_alb | load-balancer | body.load_balancer_type (string) | the request’s |
aws_cloudfront_distribution | cdn | none | the request’s |
aws_db_instance | database | body.instance_class (string) | the request’s |
aws_dynamodb_table | database | body.billing_mode (string) | the request’s |
aws_ebs_volume | block-store | body.size (number) | body.availability_zone, else the request’s |
aws_ecs_service | compute | none | the request’s |
aws_eks_cluster | control-plane | none | the request’s |
aws_eks_node_group | compute | none | the request’s |
aws_elasticache_cluster | cache | body.node_type (string) | the request’s |
aws_instance | compute | body.instance_type (string) | the request’s |
aws_lambda_function | serverless | body.memory_size (number) | the request’s |
aws_lb | load-balancer | body.load_balancer_type (string) | the request’s |
aws_rds_cluster | database | none | the request’s |
aws_rds_cluster_instance | database | body.instance_class (string) | the request’s |
aws_s3_bucket | object-store | none | the request’s |
aws_sns_topic | queue | none | the request’s |
aws_sqs_queue | queue | none | the request’s |
Declared unmapped (53)
Section titled “Declared unmapped (53)”| Type | Why it carries no rate |
|---|---|
Terraform::Data | a read of something that exists outside this root; whatever it reads is priced where it is declared |
Terraform::Live | choudoufu’s estate declaration, which names the ownership marker every resource of the root carries and holds nothing itself |
Terraform::Locals | named expressions a root reuses; they exist only in the evaluation |
Terraform::Module | a call to a child module; the blocks the call expands to are entities of their own and carry the figures |
Terraform::Output | a value the root publishes after an apply, not a thing the account holds |
Terraform::Provider | a provider configuration: how terraform reaches an account, not a thing the account holds |
Terraform::Terraform | the root’s settings block, which names providers and a backend and holds nothing in any account |
Terraform::Variable | a root module input; it shapes what is created and is never created itself |
aws_cloudwatch_event_rule | priced by the events matched, which is a fact about traffic the declaration does not state |
aws_cloudwatch_log_group | priced by the volume ingested into it, which the declaration does not state and no engine can infer from a graph |
aws_db_subnet_group | the set of subnets a database may sit in; the database carries the figures |
aws_ecr_repository | priced by the gigabytes stored in it, which the declaration does not state |
aws_ecs_cluster | a namespace for services rather than capacity of its own; the services and their nodes carry the figures |
aws_ecs_task_definition | a template a service runs copies of; the service carries the figures, and pricing both would count the estate twice |
aws_eip | priced only while it is attached to nothing, which is a fact about the running account rather than about the declaration |
aws_elasticache_subnet_group | the set of subnets a cache may sit in; the cache carries the figures |
aws_iam_instance_profile | a wrapper that hands a role to an instance; the instance carries the figures |
aws_iam_policy | a policy document is a statement about permission; nothing about it saturates or accrues |
aws_iam_role | a role is a grant, not a resource traffic flows through: no rate, no capacity, no verdict under a lost zone |
aws_iam_role_policy | a policy document attached inline to a role; a statement about permission |
aws_iam_role_policy_attachment | the attachment of a grant to a role; neither end is priced by it |
aws_internet_gateway | a boundary crossing rather than a resource: no rate of its own and no capacity to saturate |
aws_kms_key | priced per key per month plus a per-request meter the declaration does not state, and neither half is an hourly rate |
aws_lambda_event_source_mapping | the wiring between a queue and a function, both of which are priced where they are declared |
aws_lambda_permission | a grant letting one service invoke a function; the function carries the figures |
aws_launch_template | a template instances are launched from; the instances carry the figures |
aws_lb_listener | a port on a load balancer; the load balancer carries the rate |
aws_lb_listener_rule | a routing rule on a listener, and a rule is not a thing at either end of an edge |
aws_lb_target_group | a set of targets behind a load balancer; the load balancer carries the rate |
aws_lb_target_group_attachment | one target’s membership of a target group; the target and the load balancer carry the figures |
aws_nat_gateway | priced as an hourly rate plus a per-gigabyte meter on everything that crosses it. The declaration states the first and nothing at all about the second, and no engine kind here models a half-known price |
aws_network_interface | where an instance’s networking lives; the instance carries the figures |
aws_route | routing, which decides where an edge goes and is not itself at either end of one |
aws_route53_record | a record in a zone priced per query; the zone is where the meter is, and the declaration does not state it |
aws_route53_zone | priced per zone per month plus a per-query meter the declaration does not state |
aws_route_table | routing, which decides where an edge goes and is not itself at either end of one |
aws_route_table_association | an association between two boundaries; nothing flows through it that does not already flow through them |
aws_s3_bucket_acl | a grant on the bucket beside it; the bucket carries the figures |
aws_s3_bucket_lifecycle_configuration | a setting on the bucket beside it; the bucket carries the figures |
aws_s3_bucket_ownership_controls | a setting on the bucket beside it; the bucket carries the figures |
aws_s3_bucket_policy | a bucket policy is a grant on the bucket beside it, and the bucket carries the figures |
aws_s3_bucket_public_access_block | a setting on the bucket beside it; the bucket carries the figures |
aws_s3_bucket_server_side_encryption_configuration | a setting on the bucket beside it; the bucket carries the figures |
aws_s3_bucket_versioning | a setting on the bucket beside it; the bucket carries the figures |
aws_security_group | a filter on edges that already exist, not a thing at either end of one |
aws_security_group_rule | one rule of a filter on edges that already exist; the rule is not a thing at either end of one |
aws_sns_topic_subscription | priced by the notifications delivered through it; the topic is what the graph has an edge to |
aws_sqs_queue_policy | a queue policy is a grant on the queue beside it, and the queue carries the figures |
aws_subnet | a network boundary rather than a node: its contribution to a prediction is the zone membership that reaches the engine as containment coverage |
aws_vpc | a network boundary rather than a node: its contribution to a prediction is the zone membership that reaches the engine as containment coverage |
aws_vpc_security_group_egress_rule | one rule of a filter on edges that already exist; the rule is not a thing at either end of one |
aws_vpc_security_group_ingress_rule | one rule of a filter on edges that already exist; the rule is not a thing at either end of one |
aws_wafv2_web_acl | priced by the requests inspected through it, which the declaration does not state |
| A type with no row above | Verdict | What the unpredicted entry says |
|---|---|---|
google_ | provider-not-modelled | Google Cloud (the google terraform provider) |
google-beta_ | provider-not-modelled | Google Cloud (the google-beta terraform provider) |
azurerm_ | provider-not-modelled | Azure (the azurerm terraform provider) |
azuread_ | provider-not-modelled | Azure (the azuread terraform provider) |
kubernetes_ | provider-not-modelled | the kubernetes terraform provider. The Kubernetes rows are the k8s lexicon’s own types, and a manifest applied through terraform is a different entity with a different shape |
helm_ | provider-not-modelled | Helm through terraform (a chart is a package, and what it installs is Kubernetes) |
null_ | provider-not-modelled | the null provider, a utility with nothing in any account |
random_ | provider-not-modelled | the random provider, a utility with nothing in any account |
local_ | provider-not-modelled | the local provider, which writes files on the machine running terraform |
tls_ | provider-not-modelled | the tls provider, a utility with nothing in any account |
time_ | provider-not-modelled | the time provider, a utility with nothing in any account |
archive_ | provider-not-modelled | the archive provider, a utility with nothing in any account |
external_ | provider-not-modelled | the external provider, a program run on the machine running terraform |
terraform_ | provider-not-modelled | terraform’s own built-in provider, which holds nothing in any account |
any other prefix, for example example_thing | provider-not-modelled | the example terraform provider, which nothing here models |
any other Terraform:: type | unknown-type, the one verdict that is a defect | the entity has no row in any contributed coverage table. It is a type from a substrate that is modelled, and is neither mapped to an engine kind nor declared unmapped, so nothing has an opinion about it rather than a stated one. Add a row in the lexicon that owns Terraform::. |
41 mapped and 121 declared unmapped across 3 lexicons. Any lexicon not listed here contributes no rows, so its types are unknown-type until it does.
Adding a row
Section titled “Adding a row”The row belongs to the lexicon that defines the entity. A mapped row names an engine kind and, where there is one, the declared property the size is read from. A declared-unmapped row is a sentence saying why the entity carries no rate. See the behaviourKinds member in the lexicon authoring overview, then run npm run generate:behaviour-coverage and commit this page with the row.