@suss/contract-terraform 0.33.1 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # @suss/contract-terraform
2
2
 
3
- Part of [suss](https://github.com/nimbuscloud-ai/suss), which reads both sides of every call in a repository and says where the two disagree.
3
+ Part of [suss](https://github.com/nimbuscloud-ai/suss), which reads both sides of every call in a repository and reports where the two disagree.
4
4
 
5
- Reads the boundaries a Terraform configuration declares, so code and other resources can be checked against them.
5
+ This package reads the boundaries a Terraform configuration declares, so code and other resources can be checked against them.
6
6
 
7
7
  ## What this package is
8
8
 
9
- A contract reader. It walks `.tf` files and describes what it finds as boundaries, the same way `@suss/contract-cloudformation` describes what a template declares. It knows no provider: a pack says that `aws_dynamodb_table` is a store and that `google_logging_metric` is a metric, and this matches on what the pack says.
9
+ A contract reader. It walks `.tf` files and records what it finds as boundaries, the same way `@suss/contract-cloudformation` records what a template declares. It has no provider knowledge of its own. A pack declares that `aws_dynamodb_table` is a store and that `google_logging_metric` is a metric, and this reader matches on those declarations.
10
10
 
11
11
  ```bash
12
12
  suss contract --from terraform infra/terraform/dynamodb -o tables.json
13
13
  ```
14
14
 
15
- The path may be one file or the directory a module lives in, since a module states its resources across several files. A root module that only calls child modules is read too: see below.
15
+ The path can be one file or the directory a module is in, since a module declares its resources across several files. A root module that only calls child modules is read too, as described below.
16
16
 
17
17
  ## What it reads
18
18
 
@@ -34,17 +34,17 @@ resource "aws_dynamodb_table" "orders" {
34
34
  }
35
35
  ```
36
36
 
37
- The resource label is the container, since that is what the rest of the configuration refers to. `name` is what the table is called once deployed, and it goes on the contract as the physical name.
37
+ The resource label is the container, since the rest of the configuration refers to the resource by its label. `name` is what the table is called once deployed, and it goes on the contract as the physical name.
38
38
 
39
- That name is usually built at deploy time, and Terraform interpolates the same way CloudFormation does, so `"${local.environment}-orders-v1"` is recorded as the pattern `{local.environment}-orders-v1`. Code that builds the same name from its own variable records `{stage}-orders-v1`, and the two pair on the fixed text. This is what lets a service written in TypeScript meet a table declared in HCL.
39
+ That name is usually built at deploy time. Terraform interpolates the same way CloudFormation does, so `"${local.environment}-orders-v1"` is recorded as the pattern `{local.environment}-orders-v1`. Code that builds the same name from its own variable records `{stage}-orders-v1`, and the two pair on the fixed text. That is how a service written in TypeScript pairs with a table declared in HCL.
40
40
 
41
- Each secondary index becomes its own boundary, because a query through an index keys on that index's fields. `hash_key` and `range_key` say what identifies an item, partition key first, and the `attribute` blocks give each key its type.
41
+ Each secondary index becomes its own boundary, because a query through an index keys on that index's fields. `hash_key` and `range_key` give what identifies an item, partition key first, and the `attribute` blocks give each key its type.
42
42
 
43
- A table declares its keys and lets every other attribute vary, so the contract says `fieldSet: "partial"` and the checker never calls an ordinary attribute unknown.
43
+ A table declares its keys and lets every other attribute vary, so the contract has `fieldSet: "partial"`, and the checker never reports an ordinary attribute as unknown.
44
44
 
45
45
  ## A structure written as JSON
46
46
 
47
- Several providers take a structure as a string rather than as blocks: a BigQuery table's schema, an ECS task's container definitions, an IAM policy document. Terraform gives an author two ways to write one, and both mean the same thing once deployed:
47
+ Several providers take a structure as a string instead of as blocks. Examples are a BigQuery table's schema and an ECS task's container definitions. Terraform lets an author write one in two ways, and both mean the same thing once deployed:
48
48
 
49
49
  ```hcl
50
50
  schema = jsonencode([
@@ -56,23 +56,23 @@ schema = <<EOF
56
56
  EOF
57
57
  ```
58
58
 
59
- `jsonAttributeValue` reads both, and a pack asks for one through `fieldsFromJson` on a storage entry, saying which attribute the JSON is at and which keys of an entry give the field its name, its type, and whether it is always set. A schema written this way is every field the item has, so the contract comes out `exhaustive`. One a file or a variable supplies is not written down anywhere the reader can see, so nothing is recorded rather than guessed.
59
+ `jsonAttributeValue` reads both. A pack asks for it through `fieldsFromJson` on a storage entry, giving the attribute the JSON is in, and the keys in each entry that give the field its name, its type, and whether it is always set. A schema written this way lists every field the item has, so the contract comes out `exhaustive`. A schema supplied by a file or a variable is not written anywhere the reader can see, so the reader records nothing and does not guess.
60
60
 
61
- A deployable steps into such an attribute rather than reading fields out of it. An ECS task writes its containers as `container_definitions = jsonencode([...])`, and a pack puts that attribute where it would put a block, so each container is read the same way a Cloud Run container written as blocks is.
61
+ For a deployable, the reader goes into such an attribute and reads blocks from it, where a store would have fields read out of it. An ECS task writes its containers as `container_definitions = jsonencode([...])`, and a pack treats that attribute the way it would treat a block. So each container is read the same way as a Cloud Run container written as blocks.
62
62
 
63
63
  ## A block written once and deployed many times
64
64
 
65
- A module rarely writes a container's variables out one block at a time. It writes one `dynamic "env"` block over a map, or, in an ECS task's JSON, a `for` expression over the same thing. Both are expanded where the configuration already says what is in that map, so the container declares the variables the deployment will give it rather than none at all. [How a repeated block is expanded](./DESIGN.md#a-block-written-once-and-deployed-many-times) says which spellings settle and which stay holes.
65
+ A module rarely writes a container's variables one block at a time. It writes one `dynamic "env"` block over a map, or, in an ECS task's JSON, a `for` expression over the same map. The reader expands both wherever the configuration already gives the contents of that map, so the container declares the variables the deployment will give it, instead of none. [How a repeated block is expanded](./DESIGN.md#a-block-written-once-and-deployed-many-times) covers which forms can be settled and which stay holes.
66
66
 
67
67
  ## A root module that calls child modules
68
68
 
69
- Plenty of configurations declare no resources at all at the root. They call a module per service, and every table and function is inside the child. A `source` that says which directory beside this one is followed and read as a module of its own, and everything the child declares gets `module.orders.` in front of its summary name and its deployable unit. [A module call this reader follows](./DESIGN.md#a-module-call-this-reader-follows) says what crosses between a call and the module it calls.
69
+ Many configurations declare no resources at the root. They call one module per service, and every table and function is inside the child. When a `source` points at a directory next to this one, the reader follows it and reads that directory as a module of its own. Everything the child declares gets `module.orders.` in front of its summary name and its deployable unit. [A module call this reader follows](./DESIGN.md#a-module-call-this-reader-follows) covers what passes between a call and the module it calls.
70
70
 
71
71
  ## Several entries for one resource type
72
72
 
73
- A pack states more than one entry for a resource type when the provider spells it differently across versions. A resource that runs whichever engine one of its attributes picks needs no second entry: a storage entry's `storageSystem` is either a word or an attribute and a table of what each value there means, so `aws_db_instance` reads its `engine` and `google_sql_database_instance` reads its `database_version`. A table matches whole values by default, or the start of a value under `matches: "prefix"`, which is what Cloud SQL needs: `database_version` states an engine and a release together, `POSTGRES_15` and `MYSQL_8_0_31`, and the releases change every quarter.
73
+ A pack declares more than one entry for a resource type when the provider spells it differently across versions. A resource whose engine is chosen by one of its attributes does not need a second entry. A storage entry's `storageSystem` is either a word, or an attribute plus a table of what each of its values means. So `aws_db_instance` reads its `engine`, and `google_sql_database_instance` reads its `database_version`. A table matches whole values by default, or the start of a value with `matches: "prefix"`. Cloud SQL needs the prefix form, because `database_version` gives an engine and a release together, as in `POSTGRES_15` and `MYSQL_8_0_31`, and the releases change every quarter.
74
74
 
75
- A value the pack does not list, and a value the configuration builds at deploy time, both leave the store with no engine on it. The resource is still read: the instance is deployed and it has a name, and the summary records a gap saying which attribute went unsettled. `appliesWhen` is for the other case, a resource that is not the thing the entry describes at all. A Firestore database in Datastore mode speaks a different API, so that entry gates on `type` and skips the resource outright.
75
+ If the pack does not list a value, or the configuration builds the value at deploy time, the store gets no engine. The resource is still read, since the instance is deployed and has a name, and the summary records a gap saying which attribute was not settled. `appliesWhen` covers a different case: a resource that is not what the entry describes at all. A Firestore database in Datastore mode uses a different API, so that entry checks `type` and skips the resource entirely.
76
76
 
77
77
  ## What a deployable declares
78
78
 
@@ -92,27 +92,27 @@ resource "aws_lambda_function" "confirm" {
92
92
  }
93
93
  ```
94
94
 
95
- The contract lists every variable the process starts with, the ones the configuration sets and the ones the platform adds, and says which is which. `LOG_LEVEL` is recorded as the text it is set to. `ORDERS_TABLE` is set to another resource, so the contract records both what that resource states its name to be and which resource it was, and code that reads `process.env.ORDERS_TABLE` to address a table reaches the table through either.
95
+ The contract lists every variable the process starts with, both the ones the configuration sets and the ones the platform adds, and marks which is which. `LOG_LEVEL` is recorded as the text it is set to. `ORDERS_TABLE` is set from another resource, so the contract records both the name that resource declares and which resource it was. Code that reads `process.env.ORDERS_TABLE` to address a table reaches the table through either.
96
96
 
97
- The unit is the resource label, `confirm`, rather than the name it deploys under. The label is what the rest of the configuration refers to it as, the way a logical id is in CloudFormation, so a variable pointing at the resource and the resource's own contract spell it the same way.
97
+ The unit is the resource label, `confirm`, whatever name the function deploys under. The rest of the configuration refers to the resource by its label, the way CloudFormation uses a logical id, so a variable that points at the resource and the resource's own contract both use the same name.
98
98
 
99
- One resource deploys several processes where the provider says so. An ECS task definition is one contract per container, and each container gets only its own variables.
99
+ One resource deploys several processes when the provider works that way. An ECS task definition produces one contract per container, and each container gets only its own variables.
100
100
 
101
- Every unsettled value in a contract is spelled the same way. An `image = var.image`, a `runtime` and a handler a variable supplies all come out as the pattern `{var.image}` rather than as the raw `${var.image}`, so nothing downstream has to know which field it is reading. A handler with a hole in it says nothing about which file the code is in, since splitting it at its last dot would pick a module nobody deploys.
101
+ Every value that cannot be settled is written the same way in a contract. An `image = var.image`, a `runtime`, and a handler supplied by a variable all come out as a pattern such as `{var.image}`, without the `$` of the raw `${var.image}`, so nothing later has to know which field it is reading. A handler with a hole in it does not show which file the code is in, since splitting it at its last dot would pick a module nobody deploys.
102
102
 
103
- A configuration says which handler runs and never which directory the deployed artifact was built from, so the handler is all the checker has to go on. A container deployable states no handler at all, since its image was built somewhere else, and then the caller says where the code is with `--code-scope api/web=services/api`, or `codeScopes` on the read options. [Which code a deployable unit runs](./DESIGN.md#which-code-a-deployable-unit-runs) says what that writes, and what happens when two units are pointed at one directory.
103
+ A configuration gives the handler that runs, but never the directory the deployed artifact was built from, so the handler is all the checker has to go on. A container deployable has no handler at all, since its image was built elsewhere. In that case the caller gives the location of the code with `--code-scope api/web=services/api`, or `codeScopes` in the read options. [Which code a deployable unit runs](./DESIGN.md#which-code-a-deployable-unit-runs) covers what that records, and what happens when two units point at one directory.
104
104
 
105
105
  ## What it will not tell you
106
106
 
107
- - **Only what a loaded pack describes.** A resource no entry covers, an IAM policy or a subnet, is skipped.
108
- - **A resource built with `for_each` or `count`** states one `resource` block for many tables, and this reads the block as written rather than working out what it expands to. A `dynamic` block inside a resource is expanded, since that one decides what a single deployed process is given.
109
- - **A name a variable supplies whole**, `name = var.table_name`, has no fixed text to pair on, so it records nothing rather than guessing.
110
- - **What a secret contains.** A variable a secret supplies records which resource supplies it and nothing else, since no configuration writes the contents down.
111
- - **Which source directory a deployable was built from.** Terraform builds the artifact outside the configuration, so a `filename` is a zip nothing in the run can open and a `source_dir` belongs to a data source this does not follow. `--code-scope` is how the caller supplies what the configuration never says.
107
+ - **Anything no loaded pack describes.** A resource that no entry covers, such as an IAM policy or a subnet, is skipped.
108
+ - **A resource built with `for_each` or `count`** has one `resource` block for many tables, and the reader reads the block as written without working out what it expands to. A `dynamic` block inside a resource is expanded, since it decides what a single deployed process is given.
109
+ - **A name that comes entirely from a variable**, such as `name = var.table_name`, has no fixed text to pair on, so the reader records nothing instead of guessing.
110
+ - **What a secret contains.** For a variable a secret supplies, the reader records which resource supplies it and nothing more, since no configuration writes the contents down.
111
+ - **Which source directory a deployable was built from.** Terraform builds the artifact outside the configuration. A `filename` is a zip nothing in the run can open, and a `source_dir` belongs to a data source the reader does not follow. `--code-scope` is how the caller supplies what the configuration never states.
112
112
 
113
113
  ## Where it fits in suss
114
114
 
115
- Depends on `@suss/behavioral-ir` for the summaries it produces and `hcl2-parser` for reading HCL. The storage pass in `@suss/checker` pairs what code touches against what this declares.
115
+ The package depends on `@suss/behavioral-ir` for the summaries it produces, and on `hcl2-parser` for reading HCL. The storage pass in `@suss/checker` pairs what code touches with what this reader declares.
116
116
 
117
117
  ## More
118
118
 
package/dist/index.cjs CHANGED
@@ -592,9 +592,9 @@ var TokenReader = class {
592
592
  return this.readTerm();
593
593
  }
594
594
  /**
595
- * A call where a comparison would be. Cloud Monitoring writes an SLO
596
- * burn-rate condition that way, and the call says what the condition
597
- * is about instead of any comparison.
595
+ * A call in place of a comparison. Cloud Monitoring writes an SLO
596
+ * burn-rate condition that way, and the call's arguments identify what
597
+ * the condition is about.
598
598
  */
599
599
  readCall() {
600
600
  const name = this.tokens[this.at];
package/dist/index.d.cts CHANGED
@@ -1,28 +1,28 @@
1
1
  import { DeployableUnit, MessageBusTechnology, MetricValueShape, MetricAccumulation, BehavioralSummary } from '@suss/behavioral-ir';
2
2
 
3
3
  /**
4
- * What a pack says about the resources one Terraform provider declares.
4
+ * The types a Terraform pack uses to describe the resources one provider
5
+ * declares.
5
6
  *
6
- * A provider states its own resource types, and a version of it states
7
- * their shape, so both belong to a pack rather than to this reader. The
8
- * reader walks HCL and matches; a pack says what `aws_dynamodb_table`
9
- * is and which provider versions it is describing.
7
+ * A provider defines its own resource types, and each version defines
8
+ * their attributes, so both belong in a pack and stay out of this
9
+ * reader. The reader walks HCL and matches resources. A pack describes
10
+ * what `aws_dynamodb_table` is and which provider versions that covers.
10
11
  *
11
- * Every entry is data rather than a function. A resource this shape
12
- * cannot describe is a reason to widen it, rather than a reason for a
13
- * pack to ship code the reader cannot see into.
12
+ * Every entry is data. When these types cannot describe a resource,
13
+ * widen them, so a pack never ships code the reader cannot inspect.
14
14
  */
15
15
 
16
16
  /**
17
- * An attribute whose value the pack translates into one of suss's own
18
- * words. The reader takes the value at `attribute` and looks it up in
19
- * `means`; a value the pack did not list says nothing, and so does one
20
- * the configuration builds at deploy time.
17
+ * An attribute whose value the pack translates into suss's own terms.
18
+ * The reader looks up the value at `attribute` in `means`. A value the
19
+ * pack did not list, or one the configuration builds at deploy time,
20
+ * maps to nothing.
21
21
  */
22
22
  interface AttributeMeaning<T extends string> {
23
- /** The attribute path whose value says which one it is. */
23
+ /** The attribute path to read. */
24
24
  attribute: string;
25
- /** What each value the provider can write there means. */
25
+ /** The suss term for each value the provider can write there. */
26
26
  means: Record<string, T>;
27
27
  /**
28
28
  * Whether a value has to equal a key of `means` or start with one.
@@ -30,45 +30,45 @@ interface AttributeMeaning<T extends string> {
30
30
  * and the releases change every quarter, so a pack lists the prefix.
31
31
  */
32
32
  matches?: "value" | "prefix";
33
- /** What the resource means when it never set the attribute at all. */
33
+ /** The meaning to use when the resource does not set the attribute. */
34
34
  whenUnset?: T;
35
35
  }
36
36
  /** A store a caller addresses by container and key. */
37
37
  interface StorageResource {
38
38
  kind: "storage";
39
39
  /**
40
- * Which store this is: dynamodb, s3. A resource that runs whichever
41
- * engine one of its attributes picks says which attribute instead,
42
- * and a value the pack does not list leaves the store with no engine
43
- * on it rather than dropping the resource.
40
+ * The store, such as `aws.dynamodb` or `s3`. A resource whose engine
41
+ * depends on one of its attributes gives an AttributeMeaning instead.
42
+ * A value the pack does not list leaves the store with no engine, and
43
+ * the resource is still read.
44
44
  */
45
45
  storageSystem: string | AttributeMeaning<string>;
46
- /** How a caller reaches it, when that is not the store's own name. */
46
+ /** How a caller reaches it, when that differs from the store's own name. */
47
47
  transport?: string;
48
48
  /**
49
- * What the resource declares. The default, `"container"`, means the
50
- * resource is the thing code addresses: code passes a bucket's name
51
- * to `bucket()`, so the declared name and the accessed name meet.
52
- * `"store"` means the resource only says the store exists. Code
53
- * splits a Redis cluster into key namespaces, no attribute of the
54
- * cluster lists them, so the summary gets no container name and
55
- * claims no access. Any match on the cluster's own name would be a
56
- * coincidence between a deployment name and a key prefix.
49
+ * What the resource declares. The default, `"container"`, means code
50
+ * addresses the resource directly: code passes a bucket's name to
51
+ * `bucket()`, so the declared name matches the accessed one. `"store"`
52
+ * means the resource only declares that the store exists. Code splits
53
+ * a Redis cluster into key namespaces that no attribute of the cluster
54
+ * lists, so the summary gets no container name and claims no access.
55
+ * A match on the cluster's own name would be a coincidence between a
56
+ * deployment name and a key prefix.
57
57
  */
58
58
  declares?: "container" | "store";
59
- /** The attribute that says what the resource is called once deployed. */
59
+ /** The attribute that contains the resource's deployed name. */
60
60
  nameAttribute?: string;
61
61
  /**
62
- * The attribute naming the namespace a container belongs to. A
62
+ * The attribute that contains the namespace a container belongs to. A
63
63
  * BigQuery table is always addressed through its dataset, so two
64
64
  * tables called `orders` in two datasets are two containers.
65
65
  */
66
66
  scopeAttribute?: string;
67
67
  /** Whether the fields it declares are every field an item has. */
68
68
  fieldSet: "exhaustive" | "partial" | "none";
69
- /** The attributes that state what identifies an item, in key order. */
69
+ /** The attributes that list an item's key fields, in key order. */
70
70
  identifies?: string[];
71
- /** Blocks that declare another way in, each keyed on its own fields. */
71
+ /** Blocks that declare another access path, each keyed on its own fields. */
72
72
  accessPathBlocks?: string[];
73
73
  /** The block that gives each field a type, and its two attributes. */
74
74
  fieldTypes?: {
@@ -77,35 +77,35 @@ interface StorageResource {
77
77
  typeAttribute: string;
78
78
  };
79
79
  /**
80
- * An attribute stating every field as JSON, one object per field. A
81
- * schema written this way is every field the item has, so reading one
82
- * makes the contract exhaustive whatever `fieldSet` says.
80
+ * An attribute that lists every field as JSON, one object per field. A
81
+ * schema written this way lists every field the item has, so reading
82
+ * one makes the contract exhaustive regardless of `fieldSet`.
83
83
  */
84
84
  fieldsFromJson?: {
85
85
  /** The attribute whose value is a JSON list of field objects. */
86
86
  attribute: string;
87
- /** The key of one entry that says what the field is called. */
87
+ /** The key in each entry that contains the field's name. */
88
88
  nameKey: string;
89
- /** The key of one entry that gives the field its type. */
89
+ /** The key in each entry that contains the field's type. */
90
90
  typeKey?: string;
91
- /** The key that says a field is always set, and the values that do. */
91
+ /** The key that marks a field as always set, and the values that do. */
92
92
  requires?: {
93
93
  key: string;
94
94
  values: string[];
95
95
  };
96
96
  };
97
97
  /**
98
- * How another way in says what it can serve. A DynamoDB index copies
99
- * some of an item rather than all of it, and a reader asking for
100
- * anything else gets nothing back and no error, so what it copies is
101
- * every field it has rather than the ones somebody wrote down.
98
+ * Which fields an access path can return. A DynamoDB index copies only
99
+ * part of an item, and a read of any other attribute returns nothing
100
+ * without an error, so the copied attributes are the index's complete
101
+ * field list.
102
102
  */
103
103
  serves?: {
104
- /** The attribute that says which kind of copy it keeps. */
104
+ /** The attribute that contains the projection kind. */
105
105
  kindAttribute: string;
106
- /** The attribute listing what it copies, for the kind that lists. */
106
+ /** The attribute listing the copied fields, for a kind that lists them. */
107
107
  fieldsAttribute: string;
108
- /** The value of `kindAttribute` that means it copies the item. */
108
+ /** The `kindAttribute` value that means the whole item is copied. */
109
109
  everything: string;
110
110
  };
111
111
  }
@@ -116,46 +116,45 @@ interface MessageBusResource {
116
116
  nameAttribute?: string;
117
117
  }
118
118
  /**
119
- * How the deployed metric type is spelled, with each `{...}` standing
120
- * for the value at that attribute. A CloudWatch metric is identified by
121
- * its namespace and its name together, so a template says where both
122
- * come from: `"{metric_transformation.namespace}/{metric_transformation.name}"`.
123
- * A hole the resource leaves unset makes the whole identity unknown, so
124
- * the summary pairs with nothing rather than with a half-spelled name.
119
+ * The pattern of the deployed metric type, where each `{...}` is
120
+ * replaced by the value at that attribute path. A CloudWatch metric is
121
+ * identified by its namespace and its name together, so its template
122
+ * gives both: `"{metric_transformation.namespace}/{metric_transformation.name}"`.
123
+ * When the resource leaves one of those attributes unset, the whole
124
+ * identity is unknown, so the summary does not pair on a partial name.
125
125
  */
126
126
  type MetricTypeTemplate = string;
127
127
  /** A named series of measurements a resource declares. */
128
128
  interface MetricResource {
129
129
  kind: "metric";
130
- /** Which system the series lives in: cloud-monitoring. */
130
+ /** The metrics system, such as `cloud-monitoring`. */
131
131
  metricSystem: string;
132
- /** How the string both sides spell is built from the resource. */
132
+ /** How the identity both sides write is built from the resource. */
133
133
  metricTypeTemplate: MetricTypeTemplate;
134
- /** Which attribute says whether one measurement is a number or a histogram. */
134
+ /** The attribute that decides whether a measurement is a number or a histogram. */
135
135
  values?: AttributeMeaning<MetricValueShape>;
136
- /** Which attribute says what a measurement covers. */
136
+ /** The attribute that decides what one measurement covers. */
137
137
  accumulates?: AttributeMeaning<MetricAccumulation>;
138
138
  }
139
139
  /**
140
- * How a reading says which metric it is about. A CloudWatch alarm
141
- * writes the namespace and the name in attributes of its own; a Cloud
142
- * Monitoring condition states a query, and the metric is one value
143
- * inside it.
140
+ * Where a reading gives the metric it is about. A CloudWatch alarm
141
+ * writes the namespace and the name as attributes. A Cloud Monitoring
142
+ * condition writes a query, and the metric is one value inside it.
144
143
  */
145
144
  type MetricIdentity = {
146
145
  from: "attributes";
147
146
  template: MetricTypeTemplate;
148
147
  } | {
149
148
  from: "query";
150
- /** The attribute whose query says which metric this is about. */
149
+ /** The attribute that contains the query. */
151
150
  attribute: string;
152
151
  /** The key inside that query whose value is the metric's type. */
153
152
  key: string;
154
153
  };
155
154
  /**
156
155
  * A resource that reads a metric another resource declares. One
157
- * resource usually states several readings, each in its own block and
158
- * each about its own metric, so each becomes a boundary of its own.
156
+ * resource often contains several readings, each in its own block and
157
+ * about its own metric, so each becomes its own boundary.
159
158
  */
160
159
  interface MetricReadingResource {
161
160
  kind: "metric-reading";
@@ -165,24 +164,24 @@ interface MetricReadingResource {
165
164
  * list means the resource is itself one reading.
166
165
  */
167
166
  readingBlocks: string[];
168
- /** How the reading spells the metric it is about. */
167
+ /** Where the reading gives the metric it is about. */
169
168
  identifies: MetricIdentity;
170
169
  /**
171
- * The attribute whose presence means the reading compares the series
172
- * against a value of this shape. A condition states a threshold, so
173
- * the number it compares against is the attribute being set at all.
170
+ * An attribute whose presence means the reading compares the series
171
+ * against a value of this shape. A condition that sets a threshold
172
+ * compares against a number, whatever the number is.
174
173
  */
175
174
  comparesTo?: {
176
175
  attribute: string;
177
176
  whenSet: MetricValueShape;
178
177
  };
179
- /** Which attribute says what the reading reduces each window to first. */
178
+ /** The attribute that decides what the reading reduces each window to. */
180
179
  reducesTo?: AttributeMeaning<MetricValueShape>;
181
180
  }
182
181
  /**
183
- * Where a provider writes one process's environment. A map attribute
184
- * gives the names as its keys; repeated entries each say what one
185
- * variable is called and then what supplies it, a literal or a secret.
182
+ * Where a provider writes one process's environment: either a map
183
+ * attribute keyed by variable name, or repeated entries that each give
184
+ * one variable's name and its source, a literal or a secret.
186
185
  */
187
186
  type EnvDeclaration = {
188
187
  style: "map";
@@ -191,81 +190,80 @@ type EnvDeclaration = {
191
190
  style: "entries";
192
191
  /** The block one entry is written as, as a dotted path. */
193
192
  block: string;
194
- /** The attribute stating what the variable is called. */
193
+ /** The attribute that contains the variable's name. */
195
194
  nameAttribute: string;
196
- /** The attribute stating the value, for an entry that writes one. */
195
+ /** The attribute that contains the value, for an entry that writes one. */
197
196
  valueAttribute?: string;
198
- /** The attribute stating which secret supplies the value. */
197
+ /** The attribute that contains the secret supplying the value. */
199
198
  secretAttribute?: string;
200
199
  };
201
200
  /**
202
- * How a platform spells the string that says which code it calls.
203
- * `"module.export"` is a module path and an exported name split at the
204
- * last dot, which is what Lambda takes. `"name"` is an exported name on
205
- * its own, which says nothing about which file it is in.
201
+ * How a platform writes the handler it calls. `"module.export"` is a
202
+ * module path and an exported name joined at the last dot, as Lambda
203
+ * takes it. `"name"` is an exported name alone, with no file.
206
204
  */
207
205
  type HandlerSpelling = "module.export" | "name";
208
- /** What the provider says one process runs. */
206
+ /** Where the provider records the code one process runs. */
209
207
  interface DeployableCode {
210
- /** The attribute that says which function the platform calls. */
208
+ /** The attribute that contains the function the platform calls. */
211
209
  handler?: {
212
210
  attribute: string;
213
211
  spelling: HandlerSpelling;
214
212
  };
215
- /** The attribute that says which image a container runs. */
213
+ /** The attribute that contains the image a container runs. */
216
214
  imageAttribute?: string;
217
215
  }
218
216
  /** A resource that deploys several processes, each with its own environment. */
219
217
  interface DeployableContainers {
220
218
  /** The blocks one container is written inside, outermost first. */
221
219
  blocks: string[];
222
- /** The attribute inside a container that says what it is called. */
220
+ /** The attribute inside a container that contains its name. */
223
221
  nameAttribute?: string;
224
222
  }
225
223
  /**
226
- * Something that gets deployed and runs: a function, a container, a
227
- * job. What it declares is the environment the process starts with, so
228
- * code reading a variable the deployment never sets is a defect either
229
- * side can be shown.
224
+ * Something that gets deployed and runs, such as a function, a
225
+ * container or a job. It declares the environment the process starts
226
+ * with, so suss can report code that reads a variable the deployment
227
+ * never sets.
230
228
  *
231
- * The unit is the resource's label rather than the name it deploys
232
- * under. The label is what the rest of the configuration refers to it
233
- * as, the way a logical id is in CloudFormation, and it is what a
234
- * variable pointing at the resource resolves to.
229
+ * The unit is keyed by the resource's label, and the deployed name is
230
+ * not used. The rest of the configuration refers to the resource by its
231
+ * label, as CloudFormation does with a logical id, and a variable that
232
+ * points at the resource resolves to it.
235
233
  */
236
234
  interface DeployableResource {
237
235
  kind: "deployable";
238
- /** Which medium runs it, in the words a deployable unit is keyed by. */
236
+ /** What runs it, as a deployable unit's `deploymentTarget`. */
239
237
  deploymentTarget: DeployableUnit["deploymentTarget"];
240
238
  /** Where each process is written, for a resource that deploys several. */
241
239
  containers?: DeployableContainers;
242
240
  /** Where the environment is written, inside the process. */
243
241
  env?: EnvDeclaration[];
244
- /** What the provider says the process runs. */
242
+ /** Where the provider records the code the process runs. */
245
243
  code?: DeployableCode;
246
- /** The attribute stating the language runtime, verbatim. */
244
+ /** The attribute that contains the language runtime, copied as written. */
247
245
  runtimeAttribute?: string;
248
246
  /**
249
- * Names the platform sets whatever the configuration says. An entry
250
- * that states none takes the list for its deployment target, which
251
- * is the one every other reader of that target uses. A product with
252
- * a list of its own, Cloud Run against a bare container, states it.
247
+ * Variables the platform sets regardless of the configuration. An
248
+ * entry that leaves this out gets the list for its deployment target,
249
+ * which every other reader of that target uses. A product with its own
250
+ * list, such as Cloud Run, sets it here.
253
251
  */
254
252
  platformEnvVars?: readonly string[];
255
253
  }
256
254
  type TerraformResource = StorageResource | MessageBusResource | MetricResource | MetricReadingResource | DeployableResource;
257
255
  /** One resource type, as one version range of one provider declares it. */
258
256
  interface TerraformResourcePattern {
259
- /** The resource type, spelled the way the provider spells it. */
257
+ /** The resource type, as the provider writes it. */
260
258
  resource: string;
261
259
  /**
262
260
  * An attribute that decides whether the entry describes the resource
263
- * at all. A Firestore database in Datastore mode speaks a different
264
- * API, so it is not the store the Firestore entry describes. A value
265
- * outside `equals`, or one built at deploy time, means the resource
266
- * is not read, rather than read as something it may not be.
267
- * `whenUnset` says what an absent attribute means; the default is not
268
- * to read the resource.
261
+ * at all. A Firestore database in Datastore mode uses a different
262
+ * API, so the Firestore entry does not describe it. A value outside
263
+ * `equals`, or one built at deploy time, means the resource is
264
+ * skipped, so it is never read as something it may not be.
265
+ * `whenUnset` gives what an absent attribute means; by default the
266
+ * resource is skipped.
269
267
  */
270
268
  appliesWhen?: {
271
269
  attribute: string;
@@ -277,7 +275,7 @@ interface TerraformResourcePattern {
277
275
  * Which provider versions this describes, as a semver range. A
278
276
  * configuration states its own constraint under `required_providers`,
279
277
  * and an entry outside it is not read. A configuration that states no
280
- * constraint is read by every entry, since nothing said otherwise.
278
+ * constraint is read by every entry, since nothing rules any out.
281
279
  */
282
280
  providerVersions: string;
283
281
  /** What the resource is, once read. */
@@ -297,16 +295,15 @@ interface TerraformPack {
297
295
  /**
298
296
  * A filter written as comparisons joined by AND and OR.
299
297
  *
300
- * Several cloud APIs take a selector as one string in this shape:
298
+ * Several cloud APIs take a selector as one string such as
301
299
  * `metric.type = "x" AND resource.type = "y"`, with quoted or bare
302
300
  * values, parentheses, NOT, and terms written next to each other for
303
301
  * AND. A resource that reads what another resource declares usually
304
- * says which one inside such a string, so reading the string is how
305
- * this reader finds out what a resource refers to.
302
+ * identifies it inside such a string, so the reader parses the string.
306
303
  *
307
- * A pack says which key it wants. This file knows the grammar and no
308
- * provider's key names. What comes back is a tree, since OR and NOT
309
- * change what a term claims and only the caller can judge that.
304
+ * The pack gives the key to look for, and this file contains only the
305
+ * grammar. It returns a tree, because OR and NOT change what a term
306
+ * means and only the caller can decide how to treat that.
310
307
  */
311
308
  /** One comparison, as the filter writes it. */
312
309
  interface FilterTerm {
@@ -322,7 +319,7 @@ interface FilterTerm {
322
319
  /** The right side, with the quotes taken off a quoted value. */
323
320
  value: string;
324
321
  }
325
- /** A call standing where a comparison would, with its arguments. */
322
+ /** A function call in place of a comparison, with its arguments. */
326
323
  interface FilterCall {
327
324
  type: "call";
328
325
  name: string;
@@ -336,7 +333,7 @@ type FilterQuery = FilterTerm | FilterCall | {
336
333
  type: "negation";
337
334
  operand: FilterQuery;
338
335
  };
339
- /** A filter nobody could read says why, rather than reading as empty. */
336
+ /** A parse failure includes the reason, so it is never mistaken for an empty filter. */
340
337
  type FilterParse = {
341
338
  ok: true;
342
339
  query: FilterQuery;
@@ -348,63 +345,61 @@ type FilterParse = {
348
345
  declare function filterValuesFor(query: FilterQuery, key: string): string[];
349
346
  /** Every comparison in the tree, in the order the filter writes them. */
350
347
  declare function filterTerms(query: FilterQuery): FilterTerm[];
351
- /** Every call in the tree, for a caller that reads one of them. */
348
+ /** Every call in the tree, in the order the filter writes them. */
352
349
  declare function filterCalls(query: FilterQuery): FilterCall[];
353
- /** Read one filter string, or say what stopped the reading. */
350
+ /** Parses one filter string, or returns what stopped the parse. */
354
351
  declare function parseFilterQuery(source: string): FilterParse;
355
352
 
356
353
  /**
357
354
  * An attribute whose value is JSON.
358
355
  *
359
- * Several providers take a structure as a string rather than as blocks:
360
- * a BigQuery table's schema, an ECS task's container definitions, an
361
- * IAM policy document. Terraform gives an author two ways to write one,
362
- * a literal string (usually a heredoc) and `jsonencode` over an HCL
363
- * value, and the two arrive at this reader looking nothing alike. This
364
- * turns both into the value the provider will see.
356
+ * Several providers take a structure as a JSON string instead of as
357
+ * blocks, such as a BigQuery table's schema or an ECS task's container
358
+ * definitions. An author can write one as a literal string, usually a
359
+ * heredoc, or as `jsonencode` over an HCL value, and the parser returns
360
+ * the two very differently. This module turns both into the value the
361
+ * provider receives.
365
362
  *
366
- * A schema a file or a variable supplies is not written down anywhere
367
- * this can read, so it comes back as null and the caller records
368
- * nothing rather than guessing.
363
+ * A value that a file or a variable supplies is not written in the
364
+ * configuration, so it comes back as null and the caller records nothing.
369
365
  */
370
366
  /**
371
- * The value an attribute states as JSON, or null when the attribute is
372
- * missing or states something only a deploy can settle.
367
+ * The value an attribute writes as JSON, or null when the attribute is
368
+ * missing or its value is known only at deploy time.
373
369
  */
374
370
  declare function jsonAttributeValue(value: unknown): unknown;
375
371
 
376
372
  /**
377
373
  * The boundaries a Terraform configuration declares.
378
374
  *
379
- * This reads HCL and matches what it finds against the packs a run
380
- * loads. A pack says what `aws_dynamodb_table` is; nothing about any
375
+ * This module reads HCL and matches it against the packs a run loads. A
376
+ * pack describes what `aws_dynamodb_table` is, and nothing about any
381
377
  * provider is written here.
382
378
  *
383
- * A name is usually built at deploy time, and Terraform interpolates
384
- * the same way CloudFormation does, so `"${local.environment}-orders"`
385
- * becomes the pattern `{local.environment}-orders` and pairs with code
386
- * that builds the same name from its own variable. An interpolation
387
- * that refers to another resource in the same configuration resolves
388
- * instead, since that resource states the value. The README says how a
389
- * block that HCL states once or many times is read.
379
+ * Terraform interpolates the same way CloudFormation does, so
380
+ * `"${local.environment}-orders"` becomes the pattern
381
+ * `{local.environment}-orders` and pairs with code that builds the same
382
+ * name from its own variable. A reference to another resource in the
383
+ * same configuration resolves instead, since that resource writes the
384
+ * value. DESIGN.md covers references, module calls and repeated blocks.
390
385
  */
391
386
 
392
387
  interface TerraformReadOptions {
393
- /** The packs that say what this configuration's resources are. */
388
+ /** The packs that describe this configuration's resources. */
394
389
  packs: TerraformPack[];
395
390
  /**
396
- * The directory each deployable unit's code is in, by the instance
397
- * name the unit is keyed by. A configuration says which handler runs
398
- * and never which directory the artifact was built from, so a
399
- * container whose image is built elsewhere has no code at all until
400
- * somebody says where it is.
391
+ * The directory each deployable unit's code is in, by the unit's
392
+ * instance name. A configuration gives the handler that runs but never
393
+ * the directory the artifact was built from, so a container whose
394
+ * image is built elsewhere has no code until the caller gives its
395
+ * location.
401
396
  */
402
397
  codeScopes?: Record<string, string>;
403
398
  }
404
399
  /** Every boundary one Terraform configuration declares. */
405
400
  declare function terraformToSummaries(source: string, sourceFile: string, options: TerraformReadOptions): BehavioralSummary[];
406
401
  /**
407
- * Read one `.tf` file, or every one directly inside a directory, and
402
+ * Reads one `.tf` file, or every one directly inside a directory, and
408
403
  * every local module the configuration calls.
409
404
  */
410
405
  declare function terraformFileToSummaries(target: string, options: TerraformReadOptions): BehavioralSummary[];
package/dist/index.d.ts CHANGED
@@ -1,28 +1,28 @@
1
1
  import { DeployableUnit, MessageBusTechnology, MetricValueShape, MetricAccumulation, BehavioralSummary } from '@suss/behavioral-ir';
2
2
 
3
3
  /**
4
- * What a pack says about the resources one Terraform provider declares.
4
+ * The types a Terraform pack uses to describe the resources one provider
5
+ * declares.
5
6
  *
6
- * A provider states its own resource types, and a version of it states
7
- * their shape, so both belong to a pack rather than to this reader. The
8
- * reader walks HCL and matches; a pack says what `aws_dynamodb_table`
9
- * is and which provider versions it is describing.
7
+ * A provider defines its own resource types, and each version defines
8
+ * their attributes, so both belong in a pack and stay out of this
9
+ * reader. The reader walks HCL and matches resources. A pack describes
10
+ * what `aws_dynamodb_table` is and which provider versions that covers.
10
11
  *
11
- * Every entry is data rather than a function. A resource this shape
12
- * cannot describe is a reason to widen it, rather than a reason for a
13
- * pack to ship code the reader cannot see into.
12
+ * Every entry is data. When these types cannot describe a resource,
13
+ * widen them, so a pack never ships code the reader cannot inspect.
14
14
  */
15
15
 
16
16
  /**
17
- * An attribute whose value the pack translates into one of suss's own
18
- * words. The reader takes the value at `attribute` and looks it up in
19
- * `means`; a value the pack did not list says nothing, and so does one
20
- * the configuration builds at deploy time.
17
+ * An attribute whose value the pack translates into suss's own terms.
18
+ * The reader looks up the value at `attribute` in `means`. A value the
19
+ * pack did not list, or one the configuration builds at deploy time,
20
+ * maps to nothing.
21
21
  */
22
22
  interface AttributeMeaning<T extends string> {
23
- /** The attribute path whose value says which one it is. */
23
+ /** The attribute path to read. */
24
24
  attribute: string;
25
- /** What each value the provider can write there means. */
25
+ /** The suss term for each value the provider can write there. */
26
26
  means: Record<string, T>;
27
27
  /**
28
28
  * Whether a value has to equal a key of `means` or start with one.
@@ -30,45 +30,45 @@ interface AttributeMeaning<T extends string> {
30
30
  * and the releases change every quarter, so a pack lists the prefix.
31
31
  */
32
32
  matches?: "value" | "prefix";
33
- /** What the resource means when it never set the attribute at all. */
33
+ /** The meaning to use when the resource does not set the attribute. */
34
34
  whenUnset?: T;
35
35
  }
36
36
  /** A store a caller addresses by container and key. */
37
37
  interface StorageResource {
38
38
  kind: "storage";
39
39
  /**
40
- * Which store this is: dynamodb, s3. A resource that runs whichever
41
- * engine one of its attributes picks says which attribute instead,
42
- * and a value the pack does not list leaves the store with no engine
43
- * on it rather than dropping the resource.
40
+ * The store, such as `aws.dynamodb` or `s3`. A resource whose engine
41
+ * depends on one of its attributes gives an AttributeMeaning instead.
42
+ * A value the pack does not list leaves the store with no engine, and
43
+ * the resource is still read.
44
44
  */
45
45
  storageSystem: string | AttributeMeaning<string>;
46
- /** How a caller reaches it, when that is not the store's own name. */
46
+ /** How a caller reaches it, when that differs from the store's own name. */
47
47
  transport?: string;
48
48
  /**
49
- * What the resource declares. The default, `"container"`, means the
50
- * resource is the thing code addresses: code passes a bucket's name
51
- * to `bucket()`, so the declared name and the accessed name meet.
52
- * `"store"` means the resource only says the store exists. Code
53
- * splits a Redis cluster into key namespaces, no attribute of the
54
- * cluster lists them, so the summary gets no container name and
55
- * claims no access. Any match on the cluster's own name would be a
56
- * coincidence between a deployment name and a key prefix.
49
+ * What the resource declares. The default, `"container"`, means code
50
+ * addresses the resource directly: code passes a bucket's name to
51
+ * `bucket()`, so the declared name matches the accessed one. `"store"`
52
+ * means the resource only declares that the store exists. Code splits
53
+ * a Redis cluster into key namespaces that no attribute of the cluster
54
+ * lists, so the summary gets no container name and claims no access.
55
+ * A match on the cluster's own name would be a coincidence between a
56
+ * deployment name and a key prefix.
57
57
  */
58
58
  declares?: "container" | "store";
59
- /** The attribute that says what the resource is called once deployed. */
59
+ /** The attribute that contains the resource's deployed name. */
60
60
  nameAttribute?: string;
61
61
  /**
62
- * The attribute naming the namespace a container belongs to. A
62
+ * The attribute that contains the namespace a container belongs to. A
63
63
  * BigQuery table is always addressed through its dataset, so two
64
64
  * tables called `orders` in two datasets are two containers.
65
65
  */
66
66
  scopeAttribute?: string;
67
67
  /** Whether the fields it declares are every field an item has. */
68
68
  fieldSet: "exhaustive" | "partial" | "none";
69
- /** The attributes that state what identifies an item, in key order. */
69
+ /** The attributes that list an item's key fields, in key order. */
70
70
  identifies?: string[];
71
- /** Blocks that declare another way in, each keyed on its own fields. */
71
+ /** Blocks that declare another access path, each keyed on its own fields. */
72
72
  accessPathBlocks?: string[];
73
73
  /** The block that gives each field a type, and its two attributes. */
74
74
  fieldTypes?: {
@@ -77,35 +77,35 @@ interface StorageResource {
77
77
  typeAttribute: string;
78
78
  };
79
79
  /**
80
- * An attribute stating every field as JSON, one object per field. A
81
- * schema written this way is every field the item has, so reading one
82
- * makes the contract exhaustive whatever `fieldSet` says.
80
+ * An attribute that lists every field as JSON, one object per field. A
81
+ * schema written this way lists every field the item has, so reading
82
+ * one makes the contract exhaustive regardless of `fieldSet`.
83
83
  */
84
84
  fieldsFromJson?: {
85
85
  /** The attribute whose value is a JSON list of field objects. */
86
86
  attribute: string;
87
- /** The key of one entry that says what the field is called. */
87
+ /** The key in each entry that contains the field's name. */
88
88
  nameKey: string;
89
- /** The key of one entry that gives the field its type. */
89
+ /** The key in each entry that contains the field's type. */
90
90
  typeKey?: string;
91
- /** The key that says a field is always set, and the values that do. */
91
+ /** The key that marks a field as always set, and the values that do. */
92
92
  requires?: {
93
93
  key: string;
94
94
  values: string[];
95
95
  };
96
96
  };
97
97
  /**
98
- * How another way in says what it can serve. A DynamoDB index copies
99
- * some of an item rather than all of it, and a reader asking for
100
- * anything else gets nothing back and no error, so what it copies is
101
- * every field it has rather than the ones somebody wrote down.
98
+ * Which fields an access path can return. A DynamoDB index copies only
99
+ * part of an item, and a read of any other attribute returns nothing
100
+ * without an error, so the copied attributes are the index's complete
101
+ * field list.
102
102
  */
103
103
  serves?: {
104
- /** The attribute that says which kind of copy it keeps. */
104
+ /** The attribute that contains the projection kind. */
105
105
  kindAttribute: string;
106
- /** The attribute listing what it copies, for the kind that lists. */
106
+ /** The attribute listing the copied fields, for a kind that lists them. */
107
107
  fieldsAttribute: string;
108
- /** The value of `kindAttribute` that means it copies the item. */
108
+ /** The `kindAttribute` value that means the whole item is copied. */
109
109
  everything: string;
110
110
  };
111
111
  }
@@ -116,46 +116,45 @@ interface MessageBusResource {
116
116
  nameAttribute?: string;
117
117
  }
118
118
  /**
119
- * How the deployed metric type is spelled, with each `{...}` standing
120
- * for the value at that attribute. A CloudWatch metric is identified by
121
- * its namespace and its name together, so a template says where both
122
- * come from: `"{metric_transformation.namespace}/{metric_transformation.name}"`.
123
- * A hole the resource leaves unset makes the whole identity unknown, so
124
- * the summary pairs with nothing rather than with a half-spelled name.
119
+ * The pattern of the deployed metric type, where each `{...}` is
120
+ * replaced by the value at that attribute path. A CloudWatch metric is
121
+ * identified by its namespace and its name together, so its template
122
+ * gives both: `"{metric_transformation.namespace}/{metric_transformation.name}"`.
123
+ * When the resource leaves one of those attributes unset, the whole
124
+ * identity is unknown, so the summary does not pair on a partial name.
125
125
  */
126
126
  type MetricTypeTemplate = string;
127
127
  /** A named series of measurements a resource declares. */
128
128
  interface MetricResource {
129
129
  kind: "metric";
130
- /** Which system the series lives in: cloud-monitoring. */
130
+ /** The metrics system, such as `cloud-monitoring`. */
131
131
  metricSystem: string;
132
- /** How the string both sides spell is built from the resource. */
132
+ /** How the identity both sides write is built from the resource. */
133
133
  metricTypeTemplate: MetricTypeTemplate;
134
- /** Which attribute says whether one measurement is a number or a histogram. */
134
+ /** The attribute that decides whether a measurement is a number or a histogram. */
135
135
  values?: AttributeMeaning<MetricValueShape>;
136
- /** Which attribute says what a measurement covers. */
136
+ /** The attribute that decides what one measurement covers. */
137
137
  accumulates?: AttributeMeaning<MetricAccumulation>;
138
138
  }
139
139
  /**
140
- * How a reading says which metric it is about. A CloudWatch alarm
141
- * writes the namespace and the name in attributes of its own; a Cloud
142
- * Monitoring condition states a query, and the metric is one value
143
- * inside it.
140
+ * Where a reading gives the metric it is about. A CloudWatch alarm
141
+ * writes the namespace and the name as attributes. A Cloud Monitoring
142
+ * condition writes a query, and the metric is one value inside it.
144
143
  */
145
144
  type MetricIdentity = {
146
145
  from: "attributes";
147
146
  template: MetricTypeTemplate;
148
147
  } | {
149
148
  from: "query";
150
- /** The attribute whose query says which metric this is about. */
149
+ /** The attribute that contains the query. */
151
150
  attribute: string;
152
151
  /** The key inside that query whose value is the metric's type. */
153
152
  key: string;
154
153
  };
155
154
  /**
156
155
  * A resource that reads a metric another resource declares. One
157
- * resource usually states several readings, each in its own block and
158
- * each about its own metric, so each becomes a boundary of its own.
156
+ * resource often contains several readings, each in its own block and
157
+ * about its own metric, so each becomes its own boundary.
159
158
  */
160
159
  interface MetricReadingResource {
161
160
  kind: "metric-reading";
@@ -165,24 +164,24 @@ interface MetricReadingResource {
165
164
  * list means the resource is itself one reading.
166
165
  */
167
166
  readingBlocks: string[];
168
- /** How the reading spells the metric it is about. */
167
+ /** Where the reading gives the metric it is about. */
169
168
  identifies: MetricIdentity;
170
169
  /**
171
- * The attribute whose presence means the reading compares the series
172
- * against a value of this shape. A condition states a threshold, so
173
- * the number it compares against is the attribute being set at all.
170
+ * An attribute whose presence means the reading compares the series
171
+ * against a value of this shape. A condition that sets a threshold
172
+ * compares against a number, whatever the number is.
174
173
  */
175
174
  comparesTo?: {
176
175
  attribute: string;
177
176
  whenSet: MetricValueShape;
178
177
  };
179
- /** Which attribute says what the reading reduces each window to first. */
178
+ /** The attribute that decides what the reading reduces each window to. */
180
179
  reducesTo?: AttributeMeaning<MetricValueShape>;
181
180
  }
182
181
  /**
183
- * Where a provider writes one process's environment. A map attribute
184
- * gives the names as its keys; repeated entries each say what one
185
- * variable is called and then what supplies it, a literal or a secret.
182
+ * Where a provider writes one process's environment: either a map
183
+ * attribute keyed by variable name, or repeated entries that each give
184
+ * one variable's name and its source, a literal or a secret.
186
185
  */
187
186
  type EnvDeclaration = {
188
187
  style: "map";
@@ -191,81 +190,80 @@ type EnvDeclaration = {
191
190
  style: "entries";
192
191
  /** The block one entry is written as, as a dotted path. */
193
192
  block: string;
194
- /** The attribute stating what the variable is called. */
193
+ /** The attribute that contains the variable's name. */
195
194
  nameAttribute: string;
196
- /** The attribute stating the value, for an entry that writes one. */
195
+ /** The attribute that contains the value, for an entry that writes one. */
197
196
  valueAttribute?: string;
198
- /** The attribute stating which secret supplies the value. */
197
+ /** The attribute that contains the secret supplying the value. */
199
198
  secretAttribute?: string;
200
199
  };
201
200
  /**
202
- * How a platform spells the string that says which code it calls.
203
- * `"module.export"` is a module path and an exported name split at the
204
- * last dot, which is what Lambda takes. `"name"` is an exported name on
205
- * its own, which says nothing about which file it is in.
201
+ * How a platform writes the handler it calls. `"module.export"` is a
202
+ * module path and an exported name joined at the last dot, as Lambda
203
+ * takes it. `"name"` is an exported name alone, with no file.
206
204
  */
207
205
  type HandlerSpelling = "module.export" | "name";
208
- /** What the provider says one process runs. */
206
+ /** Where the provider records the code one process runs. */
209
207
  interface DeployableCode {
210
- /** The attribute that says which function the platform calls. */
208
+ /** The attribute that contains the function the platform calls. */
211
209
  handler?: {
212
210
  attribute: string;
213
211
  spelling: HandlerSpelling;
214
212
  };
215
- /** The attribute that says which image a container runs. */
213
+ /** The attribute that contains the image a container runs. */
216
214
  imageAttribute?: string;
217
215
  }
218
216
  /** A resource that deploys several processes, each with its own environment. */
219
217
  interface DeployableContainers {
220
218
  /** The blocks one container is written inside, outermost first. */
221
219
  blocks: string[];
222
- /** The attribute inside a container that says what it is called. */
220
+ /** The attribute inside a container that contains its name. */
223
221
  nameAttribute?: string;
224
222
  }
225
223
  /**
226
- * Something that gets deployed and runs: a function, a container, a
227
- * job. What it declares is the environment the process starts with, so
228
- * code reading a variable the deployment never sets is a defect either
229
- * side can be shown.
224
+ * Something that gets deployed and runs, such as a function, a
225
+ * container or a job. It declares the environment the process starts
226
+ * with, so suss can report code that reads a variable the deployment
227
+ * never sets.
230
228
  *
231
- * The unit is the resource's label rather than the name it deploys
232
- * under. The label is what the rest of the configuration refers to it
233
- * as, the way a logical id is in CloudFormation, and it is what a
234
- * variable pointing at the resource resolves to.
229
+ * The unit is keyed by the resource's label, and the deployed name is
230
+ * not used. The rest of the configuration refers to the resource by its
231
+ * label, as CloudFormation does with a logical id, and a variable that
232
+ * points at the resource resolves to it.
235
233
  */
236
234
  interface DeployableResource {
237
235
  kind: "deployable";
238
- /** Which medium runs it, in the words a deployable unit is keyed by. */
236
+ /** What runs it, as a deployable unit's `deploymentTarget`. */
239
237
  deploymentTarget: DeployableUnit["deploymentTarget"];
240
238
  /** Where each process is written, for a resource that deploys several. */
241
239
  containers?: DeployableContainers;
242
240
  /** Where the environment is written, inside the process. */
243
241
  env?: EnvDeclaration[];
244
- /** What the provider says the process runs. */
242
+ /** Where the provider records the code the process runs. */
245
243
  code?: DeployableCode;
246
- /** The attribute stating the language runtime, verbatim. */
244
+ /** The attribute that contains the language runtime, copied as written. */
247
245
  runtimeAttribute?: string;
248
246
  /**
249
- * Names the platform sets whatever the configuration says. An entry
250
- * that states none takes the list for its deployment target, which
251
- * is the one every other reader of that target uses. A product with
252
- * a list of its own, Cloud Run against a bare container, states it.
247
+ * Variables the platform sets regardless of the configuration. An
248
+ * entry that leaves this out gets the list for its deployment target,
249
+ * which every other reader of that target uses. A product with its own
250
+ * list, such as Cloud Run, sets it here.
253
251
  */
254
252
  platformEnvVars?: readonly string[];
255
253
  }
256
254
  type TerraformResource = StorageResource | MessageBusResource | MetricResource | MetricReadingResource | DeployableResource;
257
255
  /** One resource type, as one version range of one provider declares it. */
258
256
  interface TerraformResourcePattern {
259
- /** The resource type, spelled the way the provider spells it. */
257
+ /** The resource type, as the provider writes it. */
260
258
  resource: string;
261
259
  /**
262
260
  * An attribute that decides whether the entry describes the resource
263
- * at all. A Firestore database in Datastore mode speaks a different
264
- * API, so it is not the store the Firestore entry describes. A value
265
- * outside `equals`, or one built at deploy time, means the resource
266
- * is not read, rather than read as something it may not be.
267
- * `whenUnset` says what an absent attribute means; the default is not
268
- * to read the resource.
261
+ * at all. A Firestore database in Datastore mode uses a different
262
+ * API, so the Firestore entry does not describe it. A value outside
263
+ * `equals`, or one built at deploy time, means the resource is
264
+ * skipped, so it is never read as something it may not be.
265
+ * `whenUnset` gives what an absent attribute means; by default the
266
+ * resource is skipped.
269
267
  */
270
268
  appliesWhen?: {
271
269
  attribute: string;
@@ -277,7 +275,7 @@ interface TerraformResourcePattern {
277
275
  * Which provider versions this describes, as a semver range. A
278
276
  * configuration states its own constraint under `required_providers`,
279
277
  * and an entry outside it is not read. A configuration that states no
280
- * constraint is read by every entry, since nothing said otherwise.
278
+ * constraint is read by every entry, since nothing rules any out.
281
279
  */
282
280
  providerVersions: string;
283
281
  /** What the resource is, once read. */
@@ -297,16 +295,15 @@ interface TerraformPack {
297
295
  /**
298
296
  * A filter written as comparisons joined by AND and OR.
299
297
  *
300
- * Several cloud APIs take a selector as one string in this shape:
298
+ * Several cloud APIs take a selector as one string such as
301
299
  * `metric.type = "x" AND resource.type = "y"`, with quoted or bare
302
300
  * values, parentheses, NOT, and terms written next to each other for
303
301
  * AND. A resource that reads what another resource declares usually
304
- * says which one inside such a string, so reading the string is how
305
- * this reader finds out what a resource refers to.
302
+ * identifies it inside such a string, so the reader parses the string.
306
303
  *
307
- * A pack says which key it wants. This file knows the grammar and no
308
- * provider's key names. What comes back is a tree, since OR and NOT
309
- * change what a term claims and only the caller can judge that.
304
+ * The pack gives the key to look for, and this file contains only the
305
+ * grammar. It returns a tree, because OR and NOT change what a term
306
+ * means and only the caller can decide how to treat that.
310
307
  */
311
308
  /** One comparison, as the filter writes it. */
312
309
  interface FilterTerm {
@@ -322,7 +319,7 @@ interface FilterTerm {
322
319
  /** The right side, with the quotes taken off a quoted value. */
323
320
  value: string;
324
321
  }
325
- /** A call standing where a comparison would, with its arguments. */
322
+ /** A function call in place of a comparison, with its arguments. */
326
323
  interface FilterCall {
327
324
  type: "call";
328
325
  name: string;
@@ -336,7 +333,7 @@ type FilterQuery = FilterTerm | FilterCall | {
336
333
  type: "negation";
337
334
  operand: FilterQuery;
338
335
  };
339
- /** A filter nobody could read says why, rather than reading as empty. */
336
+ /** A parse failure includes the reason, so it is never mistaken for an empty filter. */
340
337
  type FilterParse = {
341
338
  ok: true;
342
339
  query: FilterQuery;
@@ -348,63 +345,61 @@ type FilterParse = {
348
345
  declare function filterValuesFor(query: FilterQuery, key: string): string[];
349
346
  /** Every comparison in the tree, in the order the filter writes them. */
350
347
  declare function filterTerms(query: FilterQuery): FilterTerm[];
351
- /** Every call in the tree, for a caller that reads one of them. */
348
+ /** Every call in the tree, in the order the filter writes them. */
352
349
  declare function filterCalls(query: FilterQuery): FilterCall[];
353
- /** Read one filter string, or say what stopped the reading. */
350
+ /** Parses one filter string, or returns what stopped the parse. */
354
351
  declare function parseFilterQuery(source: string): FilterParse;
355
352
 
356
353
  /**
357
354
  * An attribute whose value is JSON.
358
355
  *
359
- * Several providers take a structure as a string rather than as blocks:
360
- * a BigQuery table's schema, an ECS task's container definitions, an
361
- * IAM policy document. Terraform gives an author two ways to write one,
362
- * a literal string (usually a heredoc) and `jsonencode` over an HCL
363
- * value, and the two arrive at this reader looking nothing alike. This
364
- * turns both into the value the provider will see.
356
+ * Several providers take a structure as a JSON string instead of as
357
+ * blocks, such as a BigQuery table's schema or an ECS task's container
358
+ * definitions. An author can write one as a literal string, usually a
359
+ * heredoc, or as `jsonencode` over an HCL value, and the parser returns
360
+ * the two very differently. This module turns both into the value the
361
+ * provider receives.
365
362
  *
366
- * A schema a file or a variable supplies is not written down anywhere
367
- * this can read, so it comes back as null and the caller records
368
- * nothing rather than guessing.
363
+ * A value that a file or a variable supplies is not written in the
364
+ * configuration, so it comes back as null and the caller records nothing.
369
365
  */
370
366
  /**
371
- * The value an attribute states as JSON, or null when the attribute is
372
- * missing or states something only a deploy can settle.
367
+ * The value an attribute writes as JSON, or null when the attribute is
368
+ * missing or its value is known only at deploy time.
373
369
  */
374
370
  declare function jsonAttributeValue(value: unknown): unknown;
375
371
 
376
372
  /**
377
373
  * The boundaries a Terraform configuration declares.
378
374
  *
379
- * This reads HCL and matches what it finds against the packs a run
380
- * loads. A pack says what `aws_dynamodb_table` is; nothing about any
375
+ * This module reads HCL and matches it against the packs a run loads. A
376
+ * pack describes what `aws_dynamodb_table` is, and nothing about any
381
377
  * provider is written here.
382
378
  *
383
- * A name is usually built at deploy time, and Terraform interpolates
384
- * the same way CloudFormation does, so `"${local.environment}-orders"`
385
- * becomes the pattern `{local.environment}-orders` and pairs with code
386
- * that builds the same name from its own variable. An interpolation
387
- * that refers to another resource in the same configuration resolves
388
- * instead, since that resource states the value. The README says how a
389
- * block that HCL states once or many times is read.
379
+ * Terraform interpolates the same way CloudFormation does, so
380
+ * `"${local.environment}-orders"` becomes the pattern
381
+ * `{local.environment}-orders` and pairs with code that builds the same
382
+ * name from its own variable. A reference to another resource in the
383
+ * same configuration resolves instead, since that resource writes the
384
+ * value. DESIGN.md covers references, module calls and repeated blocks.
390
385
  */
391
386
 
392
387
  interface TerraformReadOptions {
393
- /** The packs that say what this configuration's resources are. */
388
+ /** The packs that describe this configuration's resources. */
394
389
  packs: TerraformPack[];
395
390
  /**
396
- * The directory each deployable unit's code is in, by the instance
397
- * name the unit is keyed by. A configuration says which handler runs
398
- * and never which directory the artifact was built from, so a
399
- * container whose image is built elsewhere has no code at all until
400
- * somebody says where it is.
391
+ * The directory each deployable unit's code is in, by the unit's
392
+ * instance name. A configuration gives the handler that runs but never
393
+ * the directory the artifact was built from, so a container whose
394
+ * image is built elsewhere has no code until the caller gives its
395
+ * location.
401
396
  */
402
397
  codeScopes?: Record<string, string>;
403
398
  }
404
399
  /** Every boundary one Terraform configuration declares. */
405
400
  declare function terraformToSummaries(source: string, sourceFile: string, options: TerraformReadOptions): BehavioralSummary[];
406
401
  /**
407
- * Read one `.tf` file, or every one directly inside a directory, and
402
+ * Reads one `.tf` file, or every one directly inside a directory, and
408
403
  * every local module the configuration calls.
409
404
  */
410
405
  declare function terraformFileToSummaries(target: string, options: TerraformReadOptions): BehavioralSummary[];
package/dist/index.js CHANGED
@@ -563,9 +563,9 @@ var TokenReader = class {
563
563
  return this.readTerm();
564
564
  }
565
565
  /**
566
- * A call where a comparison would be. Cloud Monitoring writes an SLO
567
- * burn-rate condition that way, and the call says what the condition
568
- * is about instead of any comparison.
566
+ * A call in place of a comparison. Cloud Monitoring writes an SLO
567
+ * burn-rate condition that way, and the call's arguments identify what
568
+ * the condition is about.
569
569
  */
570
570
  readCall() {
571
571
  const name = this.tokens[this.at];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/contract-terraform",
3
- "version": "0.33.1",
3
+ "version": "0.34.0",
4
4
  "type": "module",
5
5
  "description": "Read the storage a Terraform configuration declares and check it against the code that reads and writes those tables.",
6
6
  "keywords": [
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "license": "Apache-2.0",
16
16
  "author": "Nimbus Cloud AI LLC",
17
- "homepage": "https://github.com/nimbuscloud-ai/suss/tree/main/packages/contract/terraform#readme",
17
+ "homepage": "https://suss.sh",
18
18
  "repository": {
19
19
  "type": "git",
20
20
  "url": "git+https://github.com/nimbuscloud-ai/suss.git",
@@ -47,7 +47,7 @@
47
47
  "typecheck": "tsc --noEmit"
48
48
  },
49
49
  "dependencies": {
50
- "@suss/behavioral-ir": "0.33.1",
50
+ "@suss/behavioral-ir": "0.34.0",
51
51
  "hcl2-parser": "^1.0.3",
52
52
  "semver": "^7.6.0"
53
53
  },