@suss/ir-core 0.33.0 → 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/dist/index.d.ts CHANGED
@@ -1,22 +1,22 @@
1
1
  import { z } from 'zod';
2
- import { T as TypeShape, M as MessageBusSemantics, D as DeployableUnit, S as Semantics, B as BoundaryBindingSchema, C as ConfidenceInfoSchema, a as ConfidenceLevelSchema, b as ConfidenceSourceSchema, c as CorroborationSchema, d as SourceLocationSchema, e as MessageBusTechnology } from './schemas-CIns0fqF.js';
3
- export { f as DeployableUnitSchema, E as EVERY_FIELD, F as FunctionCallSemantics, g as FunctionCallSemanticsSchema, G as GraphqlOperationSemantics, h as GraphqlOperationSemanticsSchema, i as GraphqlResolverSemantics, j as GraphqlResolverSemanticsSchema, k as MessageBusSemanticsSchema, R as RestSemantics, l as RestSemanticsSchema, m as RuntimeConfigSemantics, n as RuntimeConfigSemanticsSchema, o as SemanticsSchema, p as StorageSemantics, q as StorageSemanticsSchema, r as TypeShapeSchema, s as ecsContainerInstanceName, t as methodsAgree, u as normalizePath, v as pathShape, w as routePathAdmits, x as routePathsMeet, y as storageContainerLabel, z as storageLabel, A as storageSystemLabel, H as typeDefinitionKey, I as withDefinitionsInlined } from './schemas-CIns0fqF.js';
2
+ import { T as TypeShape, M as MessageBusSemantics, D as DeployableUnit, S as Semantics, B as BoundaryBindingSchema, C as ConfidenceInfoSchema, a as ConfidenceLevelSchema, b as ConfidenceSourceSchema, c as CorroborationSchema, d as SourceLocationSchema, e as MessageBusTechnology } from './schemas-CghZNwSE.js';
3
+ export { f as DeployableUnitSchema, E as EVERY_FIELD, F as FunctionCallSemantics, g as FunctionCallSemanticsSchema, G as GraphqlOperationSemantics, h as GraphqlOperationSemanticsSchema, i as GraphqlResolverSemantics, j as GraphqlResolverSemanticsSchema, k as MessageBusSemanticsSchema, R as RestSemantics, l as RestSemanticsSchema, m as RuntimeConfigSemantics, n as RuntimeConfigSemanticsSchema, o as SemanticsSchema, p as StorageSemantics, q as StorageSemanticsSchema, r as TypeShapeSchema, s as ecsContainerInstanceName, t as isModuleName, u as methodsAgree, v as normalizePath, w as pathShape, x as routePathAdmits, y as routePathsMeet, z as storageContainerLabel, A as storageLabel, H as storageSystemLabel, I as typeDefinitionKey, J as withDefinitionsInlined } from './schemas-CghZNwSE.js';
4
4
 
5
5
  /**
6
6
  * The boundary-name syntax, parsed and printed in one place.
7
7
  *
8
- * A name in a summary means one of three things, told apart by the
9
- * braces alone: a literal (`orders-v1`), a pattern with deploy-time
10
- * holes (`{stage}-orders-v1`), or a reference saying where to go and
11
- * ask (`{location.bucket}`, `{ORDER_TABLE}`). A second parser can
12
- * disagree about which is which, and a second printer can spell a
13
- * value this one cannot read back, so everything that touches the
14
- * braces calls this module: `parseBoundaryName` and
15
- * `boundaryNameString` are the two directions, and the helpers below
16
- * are views over them. The package README tells the longer story,
17
- * including why REST route paths and message-bus channels stay apart.
18
- */
19
- /** One piece of a pattern: text the writer stated, or a hole. */
8
+ * A name in a summary is one of three things, and the braces alone tell
9
+ * them apart: a literal (`orders-v1`), a pattern with holes a deployment
10
+ * fills (`{stage}-orders-v1`), or a reference that says where to look
11
+ * the name up (`{location.bucket}`, `{ORDER_TABLE}`). A second parser
12
+ * could classify a string differently, and a second printer could write
13
+ * a string this parser cannot read back. So all code that reads or
14
+ * writes the braces goes through `parseBoundaryName` and
15
+ * `boundaryNameString`, and the other helpers here are built on those
16
+ * two. The package README has the longer explanation, including why
17
+ * REST route paths and message-bus channels are handled separately.
18
+ */
19
+ /** One piece of a pattern: fixed text, or a hole. */
20
20
  type NamePart = {
21
21
  type: "text";
22
22
  text: string;
@@ -25,10 +25,10 @@ type NamePart = {
25
25
  label: string;
26
26
  };
27
27
  /**
28
- * What a name string means. A `reference` keeps its raw dot-separated
29
- * path, and `referenceOf` is the view that checks the parts are all
30
- * present, so a malformed spelling still classifies as a reference and
31
- * still pairs with nothing.
28
+ * A parsed boundary name. A `reference` keeps its raw dot-separated
29
+ * path, and `referenceOf` checks that every part is present. A
30
+ * malformed reference still classifies as a reference, so it still
31
+ * pairs with nothing.
32
32
  */
33
33
  type BoundaryName = {
34
34
  type: "literal";
@@ -40,97 +40,88 @@ type BoundaryName = {
40
40
  type: "reference";
41
41
  path: string[];
42
42
  };
43
- /** What a name string means. The exact inverse of `boundaryNameString`. */
43
+ /** Parse a name string. The exact inverse of `boundaryNameString`. */
44
44
  declare function parseBoundaryName(name: string): BoundaryName;
45
- /** The one serializer. Everything a summary spells goes through here. */
45
+ /** The name as a summary writes it. The exact inverse of `parseBoundaryName`. */
46
46
  declare function boundaryNameString(name: BoundaryName): string;
47
47
  /**
48
- * How a hole is spelled. A reader that assembles a pattern a part at a
49
- * time, the way the adapter's name reader does, mints each hole here
50
- * so the spelling cannot drift from the parse above.
48
+ * The string for one hole. Code that builds a pattern one part at a
49
+ * time calls this for each hole, so the result always parses back.
51
50
  */
52
51
  declare function patternHole(label: string): string;
53
52
  /**
54
- * The name a CloudFormation `Fn::Sub` value states. The array form
55
- * takes its template from the first element, and the variable map only
56
- * says where a substitution comes from, which does not change the name.
53
+ * The name pattern a CloudFormation `Fn::Sub` value produces, with each
54
+ * `${X}` turned into a hole. In the array form the template is the
55
+ * first element. The variable map only gives each substitution's
56
+ * source, which does not change the pattern.
57
57
  */
58
58
  declare function namePatternFromSub(value: unknown): string | null;
59
59
  /**
60
- * Whether a name says only that somebody else knows it. A wrapper that
61
- * takes its bucket as an argument states `{location.bucket}` and
62
- * nothing more, which says which parameter to ask rather than which
63
- * bucket. A name like that agrees with nothing until something grounds
64
- * it, since otherwise it would agree with every name there is.
60
+ * Whether a name is a reference, which gives where to look the name up
61
+ * and not the name itself. A wrapper that takes its bucket as an
62
+ * argument writes `{location.bucket}`. Such a name agrees with nothing
63
+ * until it is grounded, since otherwise it would agree with every name.
65
64
  */
66
65
  declare function namesNothing(name: string): boolean;
67
- /** Whether a name has anything a source left for deploy time to fill. */
66
+ /** Whether a name has any hole left for the deployment to fill. */
68
67
  declare function hasNameHole(name: string): boolean;
69
68
  /**
70
- * The form two patterns are compared in: every hole reduced to the same
71
- * token, since the two sides pick their own name for the parameter.
69
+ * The name with every hole label blanked, for comparing two patterns.
70
+ * Each side chooses its own label for a parameter, so labels never
71
+ * decide a match.
72
72
  */
73
73
  declare function namePatternKey(name: string): string;
74
- /** How much of a name the writer stated rather than left for deploy time. */
74
+ /** How many characters of fixed text a name has. A reference has none. */
75
75
  declare function fixedTextLength(name: string): number;
76
76
  /**
77
77
  * Whether two names are the same name. Two patterns agree when their
78
78
  * fixed parts line up, since a hole on one side meets a hole on the
79
- * other. A pattern and a concrete name agree when the fixed parts are
80
- * where the pattern says they are, which is what happens when one side
81
- * hardcoded what the other parameterized. A reference agrees with
82
- * nothing on either side.
79
+ * other. A pattern and a concrete name agree when the fixed text is
80
+ * where the pattern puts it. That happens when one side hardcoded what
81
+ * the other parameterized. A reference agrees with nothing.
83
82
  */
84
83
  declare function namesAgree(a: string, b: string): boolean;
85
- /** Where a reference says to go and ask. */
84
+ /** A parsed reference: the value the code starts from, and the fields it reads inside it. */
86
85
  interface Reference {
87
86
  /**
88
- * The value the code starts from: a parameter of the unit the
89
- * reference was written in, or a variable the deployment sets. Which
90
- * of those it is depends on the unit's inputs, so the reader that
91
- * grounds a reference decides, not the string.
87
+ * A parameter of the unit the reference was written in, or a variable
88
+ * the deployment sets. The string does not record which. The grounding
89
+ * pass works that out from the unit's inputs.
92
90
  */
93
91
  root: string;
94
- /** The fields to read inside it, outermost first. */
92
+ /** The fields read inside the root, outermost first. */
95
93
  fields: string[];
96
94
  }
97
95
  /**
98
- * How a reference is written. Null when a part of it is empty, since a
99
- * reference has to say what to ask about.
96
+ * The string for a reference, such as `{location.bucket}`. Null when
97
+ * any part is empty, since such a reference could never be settled.
100
98
  */
101
99
  declare function referenceName(reference: Reference): string | null;
102
100
  /**
103
- * The place a parsed reference says to ask, or null for a name that is
104
- * not a reference, or for a reference with a part missing, which says
105
- * nothing anybody could answer.
101
+ * The `Reference` in a parsed name. Null when the name is not a
102
+ * reference, or when a part of it is empty and nothing could settle it.
106
103
  */
107
104
  declare function referenceOf(name: BoundaryName): Reference | null;
108
- /**
109
- * The reference a name states, or null when the name states a name
110
- * rather than where to go and ask.
111
- */
105
+ /** The `Reference` in a name string, or null when the name is a literal or a pattern. */
112
106
  declare function referenceFromName(name: string): Reference | null;
113
107
 
114
108
  /**
115
- * What the deployment running a piece of code fills its variables in
116
- * with.
109
+ * The values a deployment sets for the variables its code reads.
117
110
  *
118
111
  * A queue URL, a function name and a table name only exist once a stack
119
112
  * is deployed, so the source reaches them through a variable and the
120
- * template says what that variable is. Every protocol whose name has a
121
- * hole in it asks the same two questions here, and whoever knows which
122
- * deployment runs the code fills them in.
113
+ * template sets that variable. A protocol whose boundary name has a
114
+ * hole in it looks the hole up through this interface.
123
115
  *
124
- * The interface is declared next to the protocols because a protocol
125
- * has to ask without knowing what a summary is. `@suss/behavioral-ir`
126
- * works the values out from a set of summaries.
116
+ * The interface is declared in ir-core so a protocol can use it without
117
+ * depending on the summary types. `@suss/behavioral-ir` implements it
118
+ * from a set of summaries.
127
119
  */
128
120
 
129
121
  interface Deployment {
130
122
  /**
131
- * Which variable a reference asks about, or null when what settles it
132
- * is an argument a caller passes rather than anything the deployment
133
- * says.
123
+ * The variable a reference points at. Null when a caller's argument
124
+ * settles the reference and the deployment has no say in it.
134
125
  */
135
126
  variableFor(reference: Reference): string | null;
136
127
  /**
@@ -140,11 +131,10 @@ interface Deployment {
140
131
  */
141
132
  setTo(reference: Reference): string | null;
142
133
  /**
143
- * The declared resource this deployment wires that variable to, by
144
- * the name the resource is declared under. A template that writes
145
- * `!Ref ArchiveWorker` says which unit an invoke reaches without ever
146
- * writing the deployed function's name, and that logical id is what
147
- * the unit's own summary is keyed by.
134
+ * The logical id of the declared resource this deployment wires the
135
+ * variable to. A template that writes `!Ref ArchiveWorker` never gives
136
+ * the deployed function's name, but the unit's own summary is keyed by
137
+ * that logical id, so an invoke can still be matched to its unit.
148
138
  */
149
139
  pointsAt(reference: Reference): string | null;
150
140
  }
@@ -152,56 +142,49 @@ interface Deployment {
152
142
  declare const NOTHING_DEPLOYED: Deployment;
153
143
 
154
144
  /**
155
- * Comparing two TypeShapes structurally.
145
+ * Structural comparison of two TypeShapes.
156
146
  *
157
- * Both checkers do this. The behavioural checker compares a provider
158
- * body against a declared contract, and the intent checker compares a
159
- * code body against declared intent, and the two have to reach the same
160
- * verdict for the same pair. The comparison is here, next to the
161
- * TypeShape it works on, so neither checker owns it and the two cannot
162
- * drift apart.
147
+ * The behavioral checker compares a provider body against a declared
148
+ * contract, and the intent checker compares a code body against declared
149
+ * intent. The two have to reach the same verdict for the same pair, so
150
+ * the comparison is defined next to TypeShape and both checkers import
151
+ * it.
163
152
  *
164
- * The comparison is asymmetric, and the answer has three values rather
165
- * than two. `unknown` is what keeps a shape nobody can see into from
166
- * being reported as agreement.
153
+ * The comparison is asymmetric and has three results. `unknown` keeps a
154
+ * shape that cannot be inspected from being reported as a match.
167
155
  */
168
156
 
169
157
  /**
170
- * A comparison result with three values:
158
+ * The result of comparing two shapes:
171
159
  * - "match": `actual` satisfies `declared`
172
- * - "nomatch": there is a concrete, verifiable incompatibility
173
- * - "unknown": uncertainty that would otherwise mask a mismatch
174
- * (spreads, refs, unknown shapes)
160
+ * - "nomatch": the shapes have a specific, checkable incompatibility
161
+ * - "unknown": part of a shape cannot be inspected (a spread, a ref, an
162
+ * unknown shape), and calling it a match could hide a mismatch
175
163
  */
176
164
  type MatchResult = "match" | "nomatch" | "unknown";
177
165
  /**
178
- * Compare an actual body against a declared body and say whether the
179
- * actual satisfies the declared.
166
+ * Whether an actual body satisfies a declared body.
180
167
  *
181
- * The two sides are not interchangeable. `actual` is the value that gets
168
+ * The arguments are not interchangeable. `actual` is the value that is
182
169
  * produced (a response body, a return value), and `declared` is the
183
- * contract it has to conform to. `unknown` is a soft signal, and the
184
- * caller decides whether to show it.
170
+ * contract it has to conform to. The caller decides whether to report
171
+ * an `"unknown"` result.
185
172
  */
186
173
  declare function bodyShapesMatch(actual: TypeShape, declared: TypeShape): MatchResult;
187
174
 
188
175
  /**
189
- * The contract a boundary protocol implements.
176
+ * The interface a boundary protocol implements.
190
177
  *
191
- * A protocol (REST, message-bus, one side of GraphQL) is a single
192
- * module in this directory. Its schema says what the identity fields
193
- * are, its behavior says how those fields key and agree, and the two
194
- * travel together so neither can be added without the other. Nothing
195
- * outside a protocol's own module decides how its boundaries behave.
178
+ * Each protocol (REST, message-bus, one side of GraphQL) is one module
179
+ * in this directory. Its schema lists the identity fields, and its
180
+ * behavior defines how those fields key and agree. The two are defined
181
+ * together, so neither can be added without the other.
196
182
  *
197
- * These definitions ship with ir-core rather than with the packs,
198
- * because a published summary has to mean the same thing to a reader
199
- * who never installed the pack that wrote it. If a pack needs a
200
- * protocol nobody has defined yet, add a module here.
201
- *
202
- * A protocol also says which of its words come from OpenTelemetry's
203
- * semantic conventions, in `semconv`. The README says why that
204
- * matters and where it stops.
183
+ * The definitions ship with ir-core and not with the packs, because a
184
+ * published summary has to mean the same thing to a reader who never
185
+ * installed the pack that wrote it. A pack that needs a new protocol
186
+ * adds a module here. Each protocol also maps its fields to
187
+ * OpenTelemetry attributes in `semconv`, as the package README explains.
205
188
  */
206
189
 
207
190
  /** One identity field written as an OpenTelemetry attribute. */
@@ -209,16 +192,16 @@ interface SemconvAttribute {
209
192
  /** The attribute name, as the semantic conventions spell it. */
210
193
  name: string;
211
194
  /**
212
- * Values suss writes where the source named none. A span never says
213
- * them, so the projection leaves the attribute off rather than
214
- * emitting a string that could only ever mismatch.
195
+ * Values suss writes when the source gave none. A span never has
196
+ * them, so the projection leaves the attribute out instead of writing
197
+ * a value that could never match.
215
198
  */
216
199
  placeholderValues?: readonly string[];
217
200
  }
218
201
  /**
219
202
  * Which identity fields the semantic conventions have an attribute
220
- * for. A field is absent when they have no attribute for it, or when
221
- * our value is our own string rather than the one a span gets.
203
+ * for. A field is absent when the conventions have no attribute for it,
204
+ * or when suss writes its own value there instead of the one a span gets.
222
205
  */
223
206
  type SemconvMapping<S extends {
224
207
  name: string;
@@ -226,9 +209,9 @@ type SemconvMapping<S extends {
226
209
  readonly [K in Exclude<keyof S, "name">]?: SemconvAttribute;
227
210
  };
228
211
  /**
229
- * How one semantics variant keys its boundaries and pairs them up. Each
230
- * variant fills this in inside its own module, and nothing else in the
231
- * tree gets a say in how a boundary keys or agrees.
212
+ * How one protocol keys its boundaries and pairs them up. Each protocol
213
+ * implements this in its own module, and every rule for how its
214
+ * boundaries key or agree is defined there.
232
215
  */
233
216
  interface BoundaryBehavior<S extends {
234
217
  name: string;
@@ -244,8 +227,8 @@ interface BoundaryBehavior<S extends {
244
227
  */
245
228
  pairingKey?(semantics: S): string | null;
246
229
  /**
247
- * Settles whatever `pairingKey` left out of the bucket. A variant that
248
- * does not define it always agrees.
230
+ * Compares the fields `pairingKey` left out of the bucket. When a
231
+ * protocol does not define it, every pair in a bucket agrees.
249
232
  */
250
233
  sidesAgree?(a: S, b: S): boolean;
251
234
  /**
@@ -261,84 +244,81 @@ interface BoundaryBehavior<S extends {
261
244
  bucketsMeet?(a: S, b: S): boolean;
262
245
  /**
263
246
  * How narrowly this bucket states what it serves, compared
264
- * lexicographically. When a consumer meets more than one bucket, the
265
- * one ranking highest is the one it reaches, and an even contest is
266
- * reported rather than paired.
247
+ * lexicographically. When a consumer meets more than one bucket, it
248
+ * reaches the one ranked highest, and a tie is reported instead of
249
+ * paired.
267
250
  */
268
251
  bucketRank?(semantics: S): readonly number[];
269
- /** The line a reader sees for this boundary. Defaults to `identityKey`. */
252
+ /** The label a reader sees for this boundary. Defaults to `identityKey`. */
270
253
  displayLabel?(semantics: S): string | null;
271
254
  /**
272
- * The semantics with any filesystem path it states rewritten, for
273
- * the pass that makes a summary's paths project-relative. A
274
- * protocol that states no path declares nothing here.
255
+ * The semantics with every filesystem path in it rewritten, for the
256
+ * pass that makes a summary's paths project-relative. A protocol
257
+ * without paths leaves this undefined.
275
258
  */
276
259
  rewritePaths?(semantics: S, rewrite: (path: string) => string): S;
277
260
  /**
278
- * The first protocol whose `claims` returns true normalizes a
279
- * hand-written suppression boundary, and a string nobody claims is
280
- * compared byte for byte. A protocol whose keys are exact declares
281
- * nothing here and stays verbatim.
261
+ * How a hand-written suppression boundary is normalized. The first
262
+ * protocol whose `claims` returns true normalizes the string, and a
263
+ * string no protocol claims is compared byte for byte. A protocol
264
+ * whose keys compare exactly leaves this undefined.
282
265
  */
283
266
  ruleBoundary?: {
284
267
  claims(raw: string): boolean;
285
268
  normalize(raw: string): string;
286
269
  };
287
270
  /**
288
- * A protocol whose boundaries have no URL leaves this undefined, which
289
- * is different from an "unknown" result: undefined means the question
290
- * does not apply, and unknown means this declaration cannot settle it.
271
+ * Whether this boundary serves a request with the given method and
272
+ * path. A protocol whose boundaries have no URL leaves this undefined.
273
+ * Undefined means the question does not apply, and an `"unknown"`
274
+ * result means this declaration cannot settle it.
291
275
  */
292
276
  servesRequest?(semantics: S, method: string, path: string): MatchResult;
293
277
  /**
294
278
  * Whether every request this boundary takes falls inside a pattern
295
279
  * something else was registered for. Middleware registered for
296
280
  * `/v1/*` runs for `/v1/tenants/{id}` and not for `/health`, so
297
- * composing it into a unit asks this first.
281
+ * composing middleware into a unit checks this first.
298
282
  *
299
283
  * A protocol whose boundaries no pattern addresses leaves this
300
284
  * undefined, and nothing registered with a pattern reaches them.
301
285
  */
302
286
  withinScope?(semantics: S, scope: string): boolean;
303
287
  /**
304
- * The same boundary with a name the deployment fills in put in.
288
+ * The same boundary with the deployment's values filled into its name.
305
289
  *
306
290
  * A call written as `fetch(`${process.env.API_BASE}/orders`)` gets
307
291
  * part of its boundary from the source and part from whatever runs
308
- * the code. Both parts have to be in before anybody can see that the
309
- * two sides describe one boundary.
292
+ * the code. Both parts are needed before the two sides can be matched
293
+ * as one boundary.
310
294
  *
311
295
  * `deployment` is already scoped to the unit this boundary belongs
312
- * to. A protocol hands over the reference its name states and gets
313
- * back what fills it in, or null. Return null to leave it as it is,
314
- * which is right whenever nothing needs filling in or nothing can
315
- * fill it.
316
- *
317
- * A protocol whose names are settled in the source leaves this
318
- * undefined.
296
+ * to. The protocol looks up the reference in its name through it.
297
+ * Return null to leave the semantics unchanged, when nothing needs
298
+ * filling in or nothing can fill it. A protocol whose names are fixed
299
+ * in the source leaves this undefined.
319
300
  */
320
301
  groundName?(semantics: S, deployment: Deployment): S | null;
321
302
  /**
322
- * Where this boundary's name says to go and ask, when it says that
323
- * rather than a name. A caller that has to explain why two sides did
324
- * not meet reads it to say which input would settle them.
303
+ * The reference in this boundary's name, when the name is a
304
+ * reference. A caller explaining why two sides did not pair uses it
305
+ * to say which input would settle the name.
325
306
  *
326
- * A protocol that defines `groundName` defines this too, and the
327
- * same reference is what `groundName` hands over.
307
+ * A protocol that defines `groundName` defines this too, and
308
+ * `groundName` looks up the same reference.
328
309
  */
329
310
  nameReference?(semantics: S): Reference | null;
330
311
  /**
331
312
  * Whether a provider produces a status and a body that a consumer
332
- * reads back. Every protocol has to state this, so that one added
333
- * later says what it is instead of landing in the HTTP-style checks
334
- * because nobody remembered to exclude it.
313
+ * reads back. It is required, so a new protocol cannot end up in the
314
+ * HTTP-style checks because nobody remembered to exclude it.
335
315
  */
336
316
  exchangesHttpResponses: boolean;
337
317
  /**
338
- * Whether crossing this boundary leaves the process. A report about
339
- * what a change means for somebody outside the code covers the ones
340
- * that do; a call from one function in a project to another is a
341
- * boundary as well, and a reader has the source diff for that.
318
+ * Whether crossing this boundary leaves the process. A report on what
319
+ * a change means for callers outside the code covers only these. A
320
+ * call between two functions in one project is a boundary too, and a
321
+ * reader has the source diff for it.
342
322
  */
343
323
  leavesTheProcess: boolean;
344
324
  /**
@@ -366,8 +346,8 @@ interface BoundarySemanticsDefinition<Z extends z.ZodType<{
366
346
  schema: Z;
367
347
  behavior: BoundaryBehavior<z.infer<Z>>;
368
348
  /**
369
- * Every protocol declares this, empty included, so that adding one
370
- * means deciding where its words come from.
349
+ * Required even when empty, so adding a protocol means deciding which
350
+ * of its fields are OpenTelemetry attributes.
371
351
  */
372
352
  semconv: SemconvMapping<z.infer<Z>>;
373
353
  }
@@ -383,7 +363,7 @@ type FnIdentityKey = `fn:${string}::${string}` & {
383
363
  readonly [IdentityKeyBrand]: "fn";
384
364
  };
385
365
  declare function fnIdentityKey(packageName: string, exportPath: readonly string[]): FnIdentityKey;
386
- /** `bus:technology subject`, with the technology closed off the schema. */
366
+ /** `bus:technology subject`, where the technology is one the message-bus schema allows. */
387
367
  type BusIdentityKey = `bus:${MessageBusSemantics["messageBus"]} ${string}` & {
388
368
  readonly [IdentityKeyBrand]: "bus";
389
369
  };
@@ -402,6 +382,17 @@ type MetricIdentityKey = `metric:${string} ${string}` & {
402
382
  readonly [IdentityKeyBrand]: "metric";
403
383
  };
404
384
  declare function metricIdentityKey(metricSystem: string, metricType: string): MetricIdentityKey;
385
+ /** `io:stream`, the key a write to one of the process's streams has. */
386
+ type IoIdentityKey = `io:${string}` & {
387
+ readonly [IdentityKeyBrand]: "io";
388
+ };
389
+ declare function ioIdentityKey(target: string): IoIdentityKey;
390
+
391
+ declare const IoSemanticsSchema: z.ZodObject<{
392
+ name: z.ZodLiteral<"io">;
393
+ target: z.ZodNullable<z.ZodString>;
394
+ }, z.core.$strip>;
395
+ type IoSemantics = z.infer<typeof IoSemanticsSchema>;
405
396
 
406
397
  declare const MetricSemanticsSchema: z.ZodObject<{
407
398
  name: z.ZodLiteral<"metric">;
@@ -412,9 +403,9 @@ type MetricSemantics = z.infer<typeof MetricSemanticsSchema>;
412
403
 
413
404
  /**
414
405
  * `instanceName` is nullable here and required on the unit itself,
415
- * because only the provider always knows the name. A call that works
416
- * its callee out at run time still happened, and recording it with no
417
- * name says so without pairing it against everything.
406
+ * because only the provider always knows the name. A call that picks
407
+ * its callee at run time still happened. Recording it with a null name
408
+ * keeps the call without pairing it against every unit.
418
409
  */
419
410
  declare const UnitInvocationSemanticsSchema: z.ZodObject<{
420
411
  deploymentTarget: z.ZodEnum<{
@@ -432,31 +423,33 @@ type UnitInvocationSemantics = z.infer<typeof UnitInvocationSemanticsSchema>;
432
423
  /**
433
424
  * A boundary binding read as OpenTelemetry attributes.
434
425
  *
435
- * A summary says what a unit can reach and a span says what it did
436
- * reach. Joining the two means both sides spelling the boundary the
437
- * same way, so suss writes the value a span gets wherever the semantic
438
- * conventions have a word for it, and this projection puts those
439
- * values under the attributes a trace store already indexes.
426
+ * A summary records what a unit can reach and a span records what it
427
+ * did reach. Joining the two means both spell the boundary the same
428
+ * way. suss writes the value a span gets wherever the semantic
429
+ * conventions have a word for it, and this projection puts those values
430
+ * under the attribute names a trace store already indexes.
440
431
  *
441
- * Which fields those are is each protocol's own declaration, in its
442
- * module under `semantics/`. Nothing here knows about any protocol.
432
+ * Each protocol declares its own field-to-attribute mapping next to its
433
+ * definition, so this module has no protocol-specific code.
443
434
  */
444
435
 
445
436
  /**
446
- * The attributes a boundary states, by name. A field the source never
447
- * gave, a placeholder, and a field the conventions have no attribute
448
- * for are all left out, so every pair here compares byte for byte
449
- * against the same attribute on a span.
437
+ * The binding's OpenTelemetry attributes, keyed by attribute name.
438
+ *
439
+ * A field the source never gave is left out, and so is a placeholder
440
+ * value or a field the conventions have no attribute for. Every value
441
+ * returned compares byte for byte against the same attribute on a span.
450
442
  */
451
443
  declare function semconvAttributes(binding: BoundaryBinding): Record<string, string>;
452
444
 
453
445
  /**
454
- * What a pack says about itself, for everything that has to know a pack
455
- * exists without reading its patterns.
446
+ * What a pack declares about itself, for tools that list or suggest
447
+ * packs without loading their patterns.
456
448
  *
457
- * `suss init` suggests a pack when it sees the library in a project's
458
- * manifest, and the packages page lists every pack with a line about
459
- * what it reads. Both come from here, so nobody maintains a table.
449
+ * `suss init` suggests a pack when a project's manifest lists the
450
+ * library, and the packages page lists every pack with a line about
451
+ * what it reads. Both read these declarations, so there is no separate
452
+ * table to keep in step with the packs.
460
453
  */
461
454
  /** The manifest a dependency is declared in. */
462
455
  type Ecosystem = "npm" | "pypi" | "rubygems";
@@ -474,6 +467,18 @@ interface PackConfiguration {
474
467
  required: boolean;
475
468
  /** What the value is, in a sentence. */
476
469
  why: string;
470
+ /**
471
+ * What `suss init` writes to `file` when it sets a project up: the
472
+ * values a project the library's own generator made has. Absent when no
473
+ * value suits every project.
474
+ */
475
+ defaults?: Record<string, unknown>;
476
+ /**
477
+ * Reads the values from a file the project already has, so init can
478
+ * write them when there are no defaults. Null when the project does
479
+ * not say.
480
+ */
481
+ readFromProject?: (projectRoot: string) => Record<string, unknown> | null;
477
482
  }
478
483
  interface PackDeclaration {
479
484
  /**
@@ -507,23 +512,22 @@ interface PackDeclaration {
507
512
  }
508
513
 
509
514
  /**
510
- * Keys for a boundary binding, and whether two of them agree. Every
511
- * function here asks the binding's protocol and returns whatever it
512
- * says, so the rules themselves are in the protocol's own module under
513
- * `semantics/` and not in this file.
515
+ * Keys for a boundary binding, and whether two bindings agree. Each
516
+ * function looks up the binding's protocol and delegates to it, so the
517
+ * rules are defined with each protocol's semantics.
514
518
  *
515
- * The sharing is the point. The behavioural checker pairs code
516
- * summaries by these keys, and the intent checker pairs intent against
517
- * code by the same keys. If the two ever keyed differently, intent and
518
- * code would stop lining up, so the keying is here next to the binding
519
- * rather than in either checker.
519
+ * The behavioral checker pairs code summaries by these keys, and the
520
+ * intent checker pairs intent against code by the same keys. If the two
521
+ * keyed differently, intent and code would stop lining up, so the
522
+ * keying is defined next to the binding and neither checker has its
523
+ * own.
520
524
  */
521
525
 
522
526
  /**
523
- * Whether a binding is of one protocol, narrowed to that protocol's
524
- * own semantics when it is. A caller reaching through the binding to
525
- * compare the name by hand gets `string | undefined` and no narrowing,
526
- * so a name that no protocol uses compiles and quietly never matches.
527
+ * Whether a binding is of one protocol, narrowing its semantics to that
528
+ * protocol's type. `name` only accepts a protocol that exists. Comparing
529
+ * `binding?.semantics.name` by hand does not narrow, and a misspelled
530
+ * protocol name there compiles and never matches.
527
531
  */
528
532
  declare function bindingIs<N extends Semantics["name"]>(binding: BoundaryBinding | null | undefined, name: N): binding is BoundaryBinding & {
529
533
  semantics: Extract<Semantics, {
@@ -532,35 +536,34 @@ declare function bindingIs<N extends Semantics["name"]>(binding: BoundaryBinding
532
536
  };
533
537
  declare function boundaryKey(binding: BoundaryBinding): string | null;
534
538
  /**
535
- * The binding with any filesystem path its semantics state rewritten.
536
- * The CLI uses it to make a summary's paths project-relative, and each
537
- * protocol says for itself whether it states one.
539
+ * The binding with every filesystem path in its semantics rewritten.
540
+ * The CLI uses it to make a summary's paths project-relative. A binding
541
+ * whose protocol has no paths comes back unchanged.
538
542
  */
539
543
  declare function withRewrittenPaths(binding: BoundaryBinding, rewrite: (path: string) => string): BoundaryBinding;
540
544
  /**
541
- * The bucket a boundary pairs in, with any name the deployment fills in
542
- * put in first.
545
+ * The pairing key after the deployment's values are filled into the
546
+ * boundary's name.
543
547
  *
544
- * A consumer that gets its base URL from the runtime states a different
545
- * string from the provider it reaches, and the two are one boundary.
546
- * Every protocol that has such a name says how to fill it in, and one
547
- * whose names are settled in the source keys exactly as before.
548
+ * A consumer that gets its base URL at run time writes a different
549
+ * string from the provider it reaches, though the two are one boundary.
550
+ * A protocol whose names are fixed in the source gets the same key as
551
+ * `pairingKey` would give.
548
552
  */
549
553
  declare function groundedPairingKey(binding: BoundaryBinding, deployment: Deployment): string | null;
550
554
  /**
551
- * The same boundary, with whatever the deployment fills in put in.
555
+ * The same boundary, with the deployment's values filled into its name.
552
556
  *
553
- * Everything that reads a boundary's name for a person to see goes
554
- * through here first: the pairing pass, the drafter that writes an
555
- * intent document, and the intent checker that reads one back. A step
556
- * only one of them took would have the drafter write a name the
557
- * checker then argued with.
557
+ * The pairing pass, the drafter that writes an intent document and the
558
+ * intent checker that reads one back all ground a binding here before
559
+ * reading its name. If only one of them grounded it, the drafter could
560
+ * write a name the checker then disputes.
558
561
  */
559
562
  declare function groundBinding(binding: BoundaryBinding, deployment: Deployment): BoundaryBinding;
560
563
  /**
561
- * Where this boundary's name says to go and ask, or null when the
562
- * source stated a name outright. A report that has to say why two
563
- * sides did not meet reads it to say which input would settle them.
564
+ * The reference in this boundary's name, or null when the source gave
565
+ * the name outright. A report that explains why two sides did not pair
566
+ * uses it to say which input would settle the name.
564
567
  */
565
568
  declare function nameReference(binding: BoundaryBinding): Reference | null;
566
569
  declare function pairingKey(binding: BoundaryBinding): string | null;
@@ -574,9 +577,18 @@ declare function bucketRank(binding: BoundaryBinding): readonly number[];
574
577
  declare function boundaryLabel(binding: BoundaryBinding): string | null;
575
578
  declare function displayLabel(binding: BoundaryBinding): string;
576
579
  /**
577
- * A boundary string that no protocol claims keeps its case, because
578
- * message-bus keys are case-sensitive and uppercasing one would break
579
- * the rule without saying so.
580
+ * The boundary and the name a read took across it, as a report prints
581
+ * them: `runtime-config ACCOUNTS_REGION`. A boundary with no label of its
582
+ * own prints as its protocol, because the name already says what was
583
+ * read, and the recognizer the fallback label adds is a package name
584
+ * nobody reading the report can act on.
585
+ */
586
+ declare function labelWithDetail(binding: BoundaryBinding, detail: string): string;
587
+ /**
588
+ * A suppression rule's boundary string, normalized by the protocol that
589
+ * recognizes it. A string no protocol recognizes keeps its case,
590
+ * because message-bus keys are case-sensitive and uppercasing one would
591
+ * make the rule stop matching without any error.
580
592
  */
581
593
  declare function normalizeRuleBoundary(raw: string): string;
582
594
  declare function exchangesHttpResponses(binding: BoundaryBinding): boolean;
@@ -589,9 +601,11 @@ declare function leavesTheProcess(binding: BoundaryBinding): boolean;
589
601
  declare function canPair(binding: BoundaryBinding): boolean;
590
602
  declare function reportsUnpairedItself(binding: BoundaryBinding): boolean;
591
603
  /**
604
+ * Whether this binding serves a request with the given method and path.
605
+ *
592
606
  * Null means the protocol does not address its boundaries by method and
593
- * path at all, which a caller has to tell apart from an unknown answer:
594
- * unknown means it does, but this declaration cannot settle the question.
607
+ * path at all. A caller has to tell that apart from `"unknown"`, which
608
+ * means the protocol does, but this declaration cannot settle it.
595
609
  */
596
610
  declare function servesRequest(binding: BoundaryBinding, method: string, path: string): MatchResult | null;
597
611
  /**
@@ -602,64 +616,61 @@ declare function servesRequest(binding: BoundaryBinding, method: string, path: s
602
616
  declare function withinScope(binding: BoundaryBinding, scope: string): boolean;
603
617
 
604
618
  /**
605
- * How somebody spells a boundary they want to talk about, and whether
606
- * what they wrote picks out a given one.
619
+ * How a person writes a boundary they want to ask about, and whether
620
+ * what they wrote picks out a given boundary.
607
621
  *
608
- * The words get cut into tokens, the boundary does too, and a boundary
609
- * matches when it has every token somebody wrote. So
622
+ * The text and the boundary are both split into tokens, and a boundary
623
+ * matches when it has every token in the text. So
610
624
  * `aws.dynamodb:editions` matches the table and every index on it, and
611
625
  * adding `#by-publication` narrows it to the one index.
612
626
  *
613
- * `suss ask`, `suss check --at` and an intent document that says which
614
- * store a write reaches all read this, so the question and the
615
- * assertion are spelled and resolved the same way.
627
+ * `suss ask`, `suss check --at` and an intent document that gives the
628
+ * store a write reaches all match through these functions, so a
629
+ * question and an assertion resolve the same way.
616
630
  */
617
631
 
618
632
  /**
619
- * The name inside a fully-qualified cloud resource id, and anything
620
- * else unchanged.
633
+ * The name inside a fully qualified cloud resource id. Any other string
634
+ * comes back unchanged.
621
635
  *
622
636
  * `arn:aws:lambda:us-east-1:1234:function:prod-worker` and
623
- * `projects/p/locations/l/functions/prod-worker` say the same thing as
624
- * `prod-worker` plus an account and a region. Keeping the whole string
625
- * makes one deployment's spelling of a resource disagree with
626
- * another's, so every reader that meets one reduces it here and the two
627
- * sides compare the part they can both know. A Lambda ARN's trailing
628
- * alias or version comes off with the rest: the function is the
629
- * boundary whichever published copy a call reaches.
637
+ * `projects/p/locations/l/functions/prod-worker` are `prod-worker` plus
638
+ * an account and a region. Two deployments of one resource have
639
+ * different full ids, so readers reduce an id to its name here and the
640
+ * two sides compare the part they both know. A Lambda ARN's trailing
641
+ * alias or version is dropped too, since the function is the boundary
642
+ * whichever published version a call reaches.
630
643
  */
631
644
  declare function resourceNameIn(spelling: string): string;
632
645
  /**
633
- * The words in a boundary spelling. Separators between parts of a name
634
- * are cut, and the characters inside one part are left alone, so
635
- * `by-publication` stays one word and `{id}` and `:id` both come out as
636
- * `id`.
646
+ * The lowercase words in a boundary spelling. The text is split at the
647
+ * separators between parts of a name, and each part is kept whole, so
648
+ * `by-publication` stays one word and `{id}` and `:id` both become `id`.
637
649
  */
638
650
  declare function spellingTokens(text: string): string[];
639
- /** Every word this boundary can be asked about by. */
651
+ /** Every word a person can use to ask about this boundary. */
640
652
  declare function bindingTokens(binding: BoundaryBinding): Set<string>;
641
653
  /**
642
- * Whether what somebody typed is the whole of this boundary's name
643
- * rather than part of it. `POST /articles` is exactly the collection
644
- * route and only part of `POST /articles/{slug}/comments`.
654
+ * Whether the typed text has every word of this boundary's label and
655
+ * nothing more. `POST /articles` matches the collection route exactly,
656
+ * and only partly matches `POST /articles/{slug}/comments`.
645
657
  */
646
658
  declare function namesBoundaryExactly(subject: string, binding: BoundaryBinding): boolean;
647
- /** Whether what somebody typed picks out this boundary. */
659
+ /** Whether the typed text picks out this boundary: every word in it is one of the boundary's words. */
648
660
  declare function namesBoundary(subject: string, binding: BoundaryBinding): boolean;
649
661
 
650
662
  /**
651
- * Deciding when two message-bus channel strings mean the same thing.
663
+ * When two message-bus channel strings mean the same channel.
652
664
  *
653
- * A channel is a subject, optionally qualified by the bus that carries
654
- * it, written `${bus}#${subject}`. The two sides of a pairing rarely
655
- * know the same amount, so they pair on the subject and the buses have
656
- * to agree only when both sides know one. The package README works
657
- * through why, with examples.
665
+ * A channel is a subject, optionally qualified by the bus it travels
666
+ * on, written `${bus}#${subject}`. The two sides of a pairing rarely
667
+ * know the same amount, so they pair on the subject, and the buses have
668
+ * to match only when both sides know one. The package README explains
669
+ * why, with examples.
658
670
  *
659
- * Everything that writes or reads that string goes through
660
- * `formatChannel` and `parseChannel`, so the wire format has a single
661
- * author. A template reader that hand-writes the `#` would be a second
662
- * author, and the two would drift the first time either one changed.
671
+ * All code that writes or reads the string goes through `formatChannel`
672
+ * and `parseChannel`. A template reader that wrote the `#` by hand would
673
+ * drift from them the first time either one changed.
663
674
  */
664
675
  /** A channel string, written `${bus}#${subject}`, split into its two parts. */
665
676
  interface ParsedChannel {
@@ -669,31 +680,33 @@ interface ParsedChannel {
669
680
  declare function parseChannel(channel: string): ParsedChannel;
670
681
  declare const ChannelBrand: unique symbol;
671
682
  /**
672
- * `bus#subject`, or the bare subject when no source said the bus. The
673
- * unqualified form is legal, so fields keep the type string and the
674
- * mint is the whole win: a channel assembled by hand cannot claim the
675
- * brand, and the format has one spelling (#167).
683
+ * `bus#subject`, or the bare subject when no source gave the bus.
684
+ *
685
+ * Fields that store a channel stay typed as `string`, since a bare
686
+ * subject is legal there. The brand exists so that a `Channel` value
687
+ * can only come from `formatChannel` and never from a string built by
688
+ * hand (#167).
676
689
  */
677
690
  type Channel = string & {
678
691
  readonly [ChannelBrand]: "channel";
679
692
  };
680
693
  declare function formatChannel(bus: string | null, subject: string): Channel;
694
+ /** A side that does not know its bus agrees with any bus. */
681
695
  declare function busesAgree(a: string | null, b: string | null): boolean;
682
696
  declare function channelsPair(a: string, b: string): boolean;
683
697
 
684
698
  /**
685
- * One convention for the source directory a deployable unit is built
686
- * from, one test for whether a file is inside it, and one reading of
687
- * the handler string that says which code the platform calls.
699
+ * The source directory a deployable unit is built from, and the handler
700
+ * string that says which code the platform calls.
688
701
  *
689
- * A deploy template gives a directory per unit, and a summary stores it
702
+ * A deploy template gives each unit a directory, and a summary stores it
690
703
  * as `metadata.codeScope.path`. Producers write that path and the
691
- * checker reads it back as a prefix test, so the two sides have to
692
- * agree on whether it ends in a slash. The test also has to stop at a
693
- * segment boundary, or `src/foo` would cover `src/foobar` and a handler
694
- * would pair with the wrong function. A CloudFormation template and a
695
- * Terraform configuration spell a handler the same way, so they read it
696
- * through the same function rather than through two of them.
704
+ * checker reads it back as a prefix test, so both sides normalize it
705
+ * through `codeScopePath` and agree on the trailing slash. The test
706
+ * stops at a segment boundary, or `src/foo` would cover `src/foobar` and
707
+ * a handler would pair with the wrong function. CloudFormation and
708
+ * Terraform write a handler the same way, so both read it through
709
+ * `parseHandler`.
697
710
  */
698
711
  /**
699
712
  * The canonical form of a code-scope directory: no leading `./`, no
@@ -706,8 +719,8 @@ declare function codeScopePath(raw: string): string;
706
719
  * at a segment boundary, so `src/foo` covers `src/foo/a.ts` and never
707
720
  * `src/foobar/a.ts`. A scope that is the project root covers every file.
708
721
  *
709
- * Both arguments go through `codeScopePath`, so a scope stored with a
710
- * trailing slash and one stored without it are read the same way.
722
+ * Both arguments are normalized with `codeScopePath`, so a trailing
723
+ * slash on either one makes no difference.
711
724
  */
712
725
  declare function fileInCodeScope(file: string, scope: string): boolean;
713
726
  interface ParsedHandler {
@@ -723,13 +736,12 @@ interface ParsedHandler {
723
736
  declare function parseHandler(handler: string): ParsedHandler | null;
724
737
 
725
738
  /**
726
- * The DispatchTable idiom: dispatch on a discriminated union through a
727
- * mapped-type Record, so a missing variant is a type error rather than a
728
- * runtime fall-through. It is decision #8 in design/docs-internal/style.md.
739
+ * Dispatch on a discriminated union through a mapped-type Record, so a
740
+ * table that misses a variant fails to compile. This is decision #8 in
741
+ * the internal style guide.
729
742
  *
730
- * It is here rather than in one package because the decision applies
731
- * repo-wide, and every package that walks an IR union needs it. Three
732
- * of them had written their own copy before this one existed.
743
+ * Every package that walks an IR union needs it, so it lives in ir-core
744
+ * and packages do not keep their own copies.
733
745
  */
734
746
  type DispatchTable<T extends {
735
747
  type: string;
@@ -743,17 +755,16 @@ declare function dispatchByType<T extends {
743
755
  }, R>(table: DispatchTable<T, R>, value: T): R;
744
756
 
745
757
  /**
746
- * The variables a deployment medium puts in a process's environment
758
+ * The variables a deployment platform puts in a process's environment
747
759
  * whatever its configuration says.
748
760
  *
749
- * Code reads them and nothing declares them, so a reader that left them
750
- * out would accuse every deployment of failing to supply `AWS_REGION`.
751
- * Two readers describe the same Lambda, a CloudFormation template and a
752
- * Terraform configuration, and a variable one of them knew about and
753
- * the other did not would be an error in one and not the other, so the
754
- * list is stated once here. A medium whose vendor documents a different
755
- * set per product, Cloud Run's `K_SERVICE` against a plain container's
756
- * nothing, states its own list in the pack that describes it.
761
+ * Code reads them and no template declares them, so without this list
762
+ * every deployment would be reported for failing to supply
763
+ * `AWS_REGION`. A CloudFormation template and a Terraform configuration
764
+ * can describe the same Lambda, and both use this one list so they
765
+ * agree on which variables are present. A platform whose vendor
766
+ * documents a different set per product, such as Cloud Run with its
767
+ * `K_SERVICE`, lists its variables in the pack that describes it.
757
768
  */
758
769
 
759
770
  /**
@@ -767,16 +778,17 @@ declare function dispatchByType<T extends {
767
778
  declare const PLATFORM_INJECTED_ENV_VARS: Record<DeployableUnit["deploymentTarget"], ReadonlyArray<string>>;
768
779
 
769
780
  /**
770
- * What a unit does at a boundary, in the words `suss ask` asks with.
781
+ * What a unit does at a boundary, in the words `suss ask` uses.
771
782
  *
772
- * `what writes postgresql:invoices` is the question and "this outcome
773
- * writes postgresql:invoices" is the assertion, so the report, the
774
- * question parser and an intent doc all have to spell the verbs the
775
- * same way. They are here rather than beside any one of them.
783
+ * A user asks `what writes postgresql:invoices`, and an intent doc
784
+ * asserts "this outcome writes postgresql:invoices". The report, the
785
+ * question parser and the intent reader all have to spell the verbs the
786
+ * same way, so the verbs are defined here once.
776
787
  *
777
- * `provides` is the unit's own boundary; `reads`, `writes` and
778
- * `invokes` are what a call site does at somebody else's. Calling a
779
- * deployed unit by name is one act, so it gets a verb of its own.
788
+ * `provides` is a boundary the unit serves. `reads`, `writes` and
789
+ * `invokes` are what a call site does at a boundary somebody else
790
+ * serves. Calling a deployed unit by name is its own act, so it gets
791
+ * its own verb.
780
792
  */
781
793
 
782
794
  declare const RelationSchema: z.ZodEnum<{
@@ -798,7 +810,7 @@ declare const EffectRelationSchema: z.ZodEnum<{
798
810
  type EffectRelation = z.infer<typeof EffectRelationSchema>;
799
811
 
800
812
  /**
801
- * pathPattern.ts: whether two route paths describe a request in common.
813
+ * Whether two route paths describe a request in common.
802
814
  *
803
815
  * A normalized path is text with holes in it. `{id}` is exactly one
804
816
  * segment, `{tenant?}` is zero or one, `{rest+}` is one or more and
@@ -811,32 +823,31 @@ type EffectRelation = z.infer<typeof EffectRelationSchema>;
811
823
  * segments, and walks the two segment lists with a reachability table
812
824
  * in which a hole absorbs as many segments as its range allows.
813
825
  */
814
- /** How many segments a hole takes, in the words the value domain uses. */
826
+ /** How many segments a hole takes: exactly one, zero or one, one or more, or any number. */
815
827
  type HoleRange = "one" | "optional" | "many" | "any";
816
- /** How a hole taking `range` segments is spelled in a path. */
828
+ /** The path text for a hole that takes `range` segments, such as `{rest+}`. */
817
829
  declare function rangedHole(name: string, range: HoleRange): string;
818
830
  /**
819
- * How a piece that is one of several texts is spelled in a path, or
820
- * null when an option would be read as something else, such as a set
821
- * boundary or the start of a query.
831
+ * The path text for a piece that matches any one of `options`, such as
832
+ * `(v1|v2)`. Null when an option would parse as something else, such as
833
+ * a set boundary or the start of a query.
822
834
  */
823
835
  declare function setPiece(options: readonly string[]): string | null;
824
836
  /** Negative when `a` ranks below `b`, positive above, zero when equal. */
825
837
  declare function compareRanks(a: readonly number[], b: readonly number[]): number;
826
838
 
827
839
  /**
828
- * The suppression pipeline, over the smallest finding both checkers share.
840
+ * Suppression rules and how they apply to findings from either checker.
829
841
  *
830
- * Behavioural `Finding` and intent `IntentFinding` both have `kind`,
831
- * `severity`, and an optional `suppressed`, which is all the pipeline
832
- * needs. The rule schema, first-match-wins matching, effect application,
833
- * and threshold counting are here so the two checkers share one
834
- * implementation without depending on each other. Only the matching of a
835
- * rule's discriminators differs, and a caller passes that in.
842
+ * Behavioral `Finding` and intent `IntentFinding` both have `kind`,
843
+ * `severity` and an optional `suppressed`, and the pipeline needs
844
+ * nothing else. The rule schema, first-match-wins matching, effect
845
+ * application and threshold counting are here so the two checkers share
846
+ * one implementation without depending on each other. Each checker
847
+ * passes in its own matcher for a rule's discriminators past `kind`.
836
848
  *
837
- * The module owns rules and matching, not file I/O. The CLI reads a
838
- * .sussignore file, checks each kind against the published enums, and
839
- * hands the parsed rules over.
849
+ * The CLI reads the `.sussignore` file, checks each kind against the
850
+ * published enums, and passes the parsed rules in.
840
851
  */
841
852
 
842
853
  declare const SuppressionRuleSchema: z.ZodObject<{
@@ -850,6 +861,7 @@ declare const SuppressionRuleSchema: z.ZodObject<{
850
861
  summary: z.ZodOptional<z.ZodString>;
851
862
  transitionId: z.ZodOptional<z.ZodString>;
852
863
  }, z.core.$strip>>;
864
+ scenario: z.ZodOptional<z.ZodString>;
853
865
  scope: z.ZodDefault<z.ZodEnum<{
854
866
  narrow: "narrow";
855
867
  broad: "broad";
@@ -875,6 +887,7 @@ declare const SuppressionFileSchema: z.ZodObject<{
875
887
  summary: z.ZodOptional<z.ZodString>;
876
888
  transitionId: z.ZodOptional<z.ZodString>;
877
889
  }, z.core.$strip>>;
890
+ scenario: z.ZodOptional<z.ZodString>;
878
891
  scope: z.ZodDefault<z.ZodEnum<{
879
892
  narrow: "narrow";
880
893
  broad: "broad";
@@ -897,11 +910,11 @@ interface FindingSuppression {
897
910
  originalSeverity?: SuppressableSeverity | undefined;
898
911
  }
899
912
  /**
900
- * The structural base that both finding types satisfy. Behavioural
901
- * `Finding` and intent `IntentFinding` each declare these fields in
902
- * their own schemas, kept structurally identical, and the pipeline needs
903
- * nothing more. The `| undefined` unions match what zod infers for
904
- * `.optional()` fields under exactOptionalPropertyTypes.
913
+ * The fields both finding types share. Behavioral `Finding` and intent
914
+ * `IntentFinding` each declare these fields in their own schemas, and
915
+ * the two declarations have to stay structurally identical. The
916
+ * `| undefined` unions match what zod infers for `.optional()` fields
917
+ * under exactOptionalPropertyTypes.
905
918
  */
906
919
  interface SuppressibleFinding {
907
920
  kind: string;
@@ -909,10 +922,11 @@ interface SuppressibleFinding {
909
922
  suppressed?: FindingSuppression | undefined;
910
923
  }
911
924
  /**
912
- * Check that a narrow rule constrains something. A bare rule with only
913
- * a `reason` would suppress every finding in the codebase, which is
914
- * almost always a mistake. Broad-scope rules are allowed to match less
915
- * specifically, on purpose.
925
+ * An error message when a rule does not constrain enough, or null when
926
+ * it is valid. A rule with only a `reason` would suppress every finding
927
+ * in the codebase, which is almost always a mistake. A broad rule needs
928
+ * one constraint of any kind, and a narrow rule needs `kind` plus a
929
+ * boundary or transition id.
916
930
  */
917
931
  declare function validateRule(rule: SuppressionRule): string | null;
918
932
 
@@ -923,60 +937,69 @@ declare function validateRule(rule: SuppressionRule): string | null;
923
937
  */
924
938
  declare function ruleBoundaryMatchesKey(ruleBoundary: string, key: string | null): boolean;
925
939
  /**
926
- * Apply suppression rules to a list of findings. This works for either
927
- * finding type: `matches` decides whether a rule's discriminators past
928
- * `kind` (boundary, consumer, and so on) match a finding, and the kind
929
- * check itself is the same everywhere, so it happens here.
940
+ * Apply suppression rules to a list of findings of either type. This
941
+ * function checks `kind`, and `matches` checks the rest of a rule's
942
+ * discriminators (boundary, consumer, and so on) against a finding.
930
943
  *
931
- * Returns a new array. Findings with `effect: "hide"` are omitted from
932
- * the output entirely unless `keepHidden` is set. Findings with
933
- * `effect: "mark"` or `"downgrade"` are included with an added
934
- * `suppressed` field. First matching rule wins.
944
+ * Returns a new array. The first matching rule applies. A finding a
945
+ * `hide` rule matches is left out unless `keepHidden` is set. A finding
946
+ * a `mark` or `downgrade` rule matches is kept with a `suppressed` field
947
+ * added.
935
948
  */
936
949
  declare function applySuppressionsToFindings<T extends SuppressibleFinding>(findings: T[], rules: SuppressionRule[], matches: (rule: SuppressionRule, finding: T) => boolean, opts?: {
937
950
  keepHidden?: boolean;
938
951
  }): T[];
939
952
  /**
940
- * `hide` and `mark` findings are left out of the exit-code threshold,
941
- * and a `downgrade` finding counts at the severity it was downgraded
942
- * to. Callers use this to decide whether a finding contributes to
943
- * `hasErrors`-style gating.
953
+ * Whether a finding counts toward the exit-code threshold. A `hide` or
954
+ * `mark` finding does not count, and a `downgrade` finding counts at the
955
+ * severity it was downgraded to.
944
956
  */
945
957
  declare function countsForThreshold(finding: SuppressibleFinding): boolean;
946
958
 
947
959
  /**
948
- * urlPath.ts: the path part of a URL, however the URL was written.
960
+ * How a `test` unit is named. Every test pack names a test by its title
961
+ * path, the suite titles outermost first and then the test's own, and a
962
+ * PRD scenario's `coveredBy` entry is matched against that name, so the
963
+ * adapters that write it and the check that reads it share this one
964
+ * spelling.
965
+ */
966
+ /** What joins the titles in a test unit's name, and the file and titles in a `coveredBy` entry. */
967
+ declare const TEST_TITLE_SEPARATOR = " > ";
968
+ declare function testUnitName(titles: readonly string[]): string;
969
+
970
+ /**
971
+ * The path part of a URL, however the URL was written.
949
972
  *
950
- * Two sides of one boundary spell it differently. A provider says
951
- * `/orders`, because a route is mounted at a path. A consumer says
952
- * `http://backend.internal/orders`, because a call has to say which
953
- * host. They are the same boundary, and pairing them means reading the
954
- * path out of both.
973
+ * The two sides of one boundary spell a URL differently. A provider
974
+ * writes `/orders`, because a route is mounted at a path. A consumer
975
+ * writes `http://backend.internal/orders`, because a call has to give a
976
+ * host. Pairing them means reading the path out of both.
955
977
  *
956
- * The query and the fragment come off with the origin. Neither picks a
957
- * route: `/orders?page=2` reaches the same handler as `/orders`.
978
+ * The query and the fragment are removed along with the origin, because
979
+ * neither one picks a route: `/orders?page=2` reaches the same handler
980
+ * as `/orders`.
958
981
  */
959
982
  /**
960
983
  * The path a URL states, with any origin, query, and fragment removed.
961
984
  *
962
- * Text that states no origin comes back with only the query and the
985
+ * Text without an origin comes back with only the query and the
963
986
  * fragment removed, since a relative URL is already a path. A URL that
964
- * is nothing but an origin comes back as an empty string, which is what
965
- * a caller wanting a path should treat as "the root".
987
+ * is only an origin comes back as an empty string, and a caller should
988
+ * treat that as the root.
966
989
  */
967
990
  declare function pathAfterOrigin(text: string): string;
968
- /** Whether the text opens with an origin rather than with a path. */
991
+ /** Whether the text starts with an origin (a scheme and host, or `//host`). */
969
992
  declare function statesAnOrigin(text: string): boolean;
970
993
 
971
994
  /**
972
995
  * @suss/ir-core: primitives shared across suss IRs.
973
996
  *
974
- * The schemas are the single source of truth, and the types come from
975
- * them. Each boundary protocol's schema and behavior are in one module
976
- * under `./semantics`, and everything else is in `./schemas`. The
977
- * boundary-binding constructors are here too, so that any package that
978
- * produces an IR object (pattern packs, contract readers, intent docs,
979
- * tests) can build a binding without depending on a specific IR.
997
+ * The zod schemas are the source of truth, and the types are derived
998
+ * from them. Each boundary protocol defines its schema and its pairing
999
+ * behavior together in one module. The boundary-binding constructors
1000
+ * are here too, so any package that produces an IR object, such as a
1001
+ * pattern pack or a contract reader, can build a binding without
1002
+ * depending on a specific IR.
980
1003
  */
981
1004
 
982
1005
  type ConfidenceSource = z.infer<typeof ConfidenceSourceSchema>;
@@ -1000,8 +1023,8 @@ declare function restBinding(opts: {
1000
1023
  declaredResponses?: number[];
1001
1024
  }): BoundaryBinding;
1002
1025
  /**
1003
- * Build a function-call-semantics binding. Used by in-process packs
1004
- * (React components, custom-hook boundaries, bare TS function exports).
1026
+ * Build a function-call-semantics binding, for an in-process boundary
1027
+ * such as a React component or an exported TypeScript function.
1005
1028
  */
1006
1029
  declare function functionCallBinding(opts: {
1007
1030
  transport: string;
@@ -1022,6 +1045,17 @@ declare function packageExportBinding(opts: {
1022
1045
  packageName: string;
1023
1046
  exportPath: string[];
1024
1047
  }): BoundaryBinding;
1048
+ /**
1049
+ * Build a function-call binding for a public export of one of the
1050
+ * modules a project lists in `suss.json`. Its key is
1051
+ * `fn:<module>::<exportName>`, so an intent document or a PRD scenario
1052
+ * can point at it by the module's name.
1053
+ */
1054
+ declare function moduleExportBinding(opts: {
1055
+ recognition: string;
1056
+ module: string;
1057
+ exportName: string;
1058
+ }): BoundaryBinding;
1025
1059
  /**
1026
1060
  * Build a graphql-resolver-semantics binding. Transport varies by
1027
1061
  * deployment: `"http"` for Apollo Server, `"aws-https"` for AppSync.
@@ -1044,20 +1078,20 @@ declare function graphqlOperationBinding(opts: {
1044
1078
  operationName?: string;
1045
1079
  }): BoundaryBinding;
1046
1080
  /**
1047
- * Whether a binding is the consumer side of a GraphQL boundary,
1048
- * settled by the protocol's own schema so callers do not compare the
1049
- * semantics tag themselves.
1081
+ * Whether a binding is the consumer side of a GraphQL boundary, checked
1082
+ * against the protocol's own schema so callers do not compare the
1083
+ * semantics tag by hand.
1050
1084
  */
1051
1085
  declare function isGraphqlOperationBinding(binding: BoundaryBinding | null | undefined): boolean;
1052
1086
  /**
1053
- * Build a runtime-config binding for a runtime configuration channel
1054
- * (env vars on a Lambda, ECS task, container, or k8s pod). Transport is
1055
- * `"os"` because the OS hands env vars to the process at startup no
1056
- * matter what the deployment medium is.
1087
+ * Build a runtime-config binding, for the environment variables a
1088
+ * deployed unit starts with. Transport is `"os"` because the operating
1089
+ * system passes environment variables to the process at startup,
1090
+ * whatever platform deployed it.
1057
1091
  *
1058
- * A provider says which deployment it is. A recognizer standing at a
1059
- * read in the code leaves both off, since nothing in the code says
1060
- * which deployment will run it.
1092
+ * A provider passes its deployment target and instance name. A
1093
+ * recognizer at a read in the code leaves both out, since the code does
1094
+ * not say which deployment runs it.
1061
1095
  */
1062
1096
  declare function runtimeConfigBinding(opts: {
1063
1097
  recognition: string;
@@ -1077,11 +1111,11 @@ declare function unitInvocationBinding(opts: {
1077
1111
  instanceName: string | null;
1078
1112
  }): BoundaryBinding;
1079
1113
  /**
1080
- * Build a storage binding, the side of a store that both a schema
1081
- * reader and a call site can spell. `transport` defaults to the store's
1082
- * own name, which is right for a database whose product and wire
1083
- * protocol are the same word; a store reached over an SDK passes its
1084
- * wire instead, and one whose engine nobody settled has none to pass.
1114
+ * Build a storage binding, which describes a store in terms both a
1115
+ * schema reader and a call site can write. `transport` defaults to the
1116
+ * storage system's name, which suits a database whose product and wire
1117
+ * protocol share a name. A store reached through an SDK passes its wire
1118
+ * protocol. With no storage system either, the transport is `"storage"`.
1085
1119
  */
1086
1120
  declare function storageBinding(opts: {
1087
1121
  recognition: string;
@@ -1114,5 +1148,15 @@ declare function metricBinding(opts: {
1114
1148
  /** Null when this source states a metric it could not name. */
1115
1149
  metricType: string | null;
1116
1150
  }): BoundaryBinding;
1151
+ /**
1152
+ * Build an io binding, for a write to one of the process's own streams.
1153
+ * The transport is `"in-process"` because the write is a call the
1154
+ * program makes, and the runtime moves the bytes on from there.
1155
+ */
1156
+ declare function ioBinding(opts: {
1157
+ recognition: string;
1158
+ /** Null when this source does not say which stream. */
1159
+ target: string | null;
1160
+ }): BoundaryBinding;
1117
1161
 
1118
- export { type BoundaryBehavior, type BoundaryBinding, BoundaryBindingSchema, type BoundaryName, type BoundarySemanticsDefinition, type BusIdentityKey, type Channel, type ConfidenceInfo, ConfidenceInfoSchema, type ConfidenceLevel, ConfidenceLevelSchema, type ConfidenceSource, ConfidenceSourceSchema, type Corroboration, CorroborationSchema, DeployableUnit, type Deployment, type DispatchTable, type Ecosystem, type EffectRelation, EffectRelationSchema, type FindingSuppression, type FnIdentityKey, type GqlIdentityKey, type HoleRange, type MatchResult, MessageBusSemantics, MessageBusTechnology, type MetricIdentityKey, type MetricSemantics, MetricSemanticsSchema, NOTHING_DEPLOYED, type NamePart, PLATFORM_INJECTED_ENV_VARS, type PackConfiguration, type PackDeclaration, type ParsedChannel, type ParsedHandler, type Reference, type Relation, RelationSchema, Semantics, type SemconvAttribute, type SemconvMapping, type SourceLocation, SourceLocationSchema, type SuppressableSeverity, type SuppressibleFinding, type SuppressionFile, SuppressionFileSchema, type SuppressionRule, SuppressionRuleSchema, TypeShape, type UnitIdentityKey, type UnitInvocationSemantics, UnitInvocationSemanticsSchema, applySuppressionsToFindings, bindingIs, bindingTokens, bodyShapesMatch, boundaryKey, boundaryLabel, boundaryNameString, bucketRank, bucketsMeet, busIdentityKey, busesAgree, canPair, channelsPair, codeScopePath, compareRanks, countsForThreshold, dispatchByType, displayLabel, exchangesHttpResponses, fileInCodeScope, fixedTextLength, fnIdentityKey, formatChannel, functionCallBinding, gqlIdentityKey, graphqlOperationBinding, graphqlResolverBinding, groundBinding, groundedPairingKey, hasNameHole, isGraphqlOperationBinding, leavesTheProcess, messageBusBinding, metricBinding, metricIdentityKey, namePatternFromSub, namePatternKey, nameReference, namesAgree, namesBoundary, namesBoundaryExactly, namesNothing, normalizeRuleBoundary, packageExportBinding, pairingKey, parseBoundaryName, parseChannel, parseHandler, pathAfterOrigin, patternHole, rangedHole, referenceFromName, referenceName, referenceOf, reportsUnpairedItself, resourceNameIn, restBinding, ruleBoundaryMatchesKey, runtimeConfigBinding, semanticsAgree, semconvAttributes, servesRequest, setPiece, spansBuckets, spellingTokens, statesAnOrigin, storageBinding, unitIdentityKey, unitInvocationBinding, validateRule, withRewrittenPaths, withinScope };
1162
+ export { type BoundaryBehavior, type BoundaryBinding, BoundaryBindingSchema, type BoundaryName, type BoundarySemanticsDefinition, type BusIdentityKey, type Channel, type ConfidenceInfo, ConfidenceInfoSchema, type ConfidenceLevel, ConfidenceLevelSchema, type ConfidenceSource, ConfidenceSourceSchema, type Corroboration, CorroborationSchema, DeployableUnit, type Deployment, type DispatchTable, type Ecosystem, type EffectRelation, EffectRelationSchema, type FindingSuppression, type FnIdentityKey, type GqlIdentityKey, type HoleRange, type IoIdentityKey, type IoSemantics, IoSemanticsSchema, type MatchResult, MessageBusSemantics, MessageBusTechnology, type MetricIdentityKey, type MetricSemantics, MetricSemanticsSchema, NOTHING_DEPLOYED, type NamePart, PLATFORM_INJECTED_ENV_VARS, type PackConfiguration, type PackDeclaration, type ParsedChannel, type ParsedHandler, type Reference, type Relation, RelationSchema, Semantics, type SemconvAttribute, type SemconvMapping, type SourceLocation, SourceLocationSchema, type SuppressableSeverity, type SuppressibleFinding, type SuppressionFile, SuppressionFileSchema, type SuppressionRule, SuppressionRuleSchema, TEST_TITLE_SEPARATOR, TypeShape, type UnitIdentityKey, type UnitInvocationSemantics, UnitInvocationSemanticsSchema, applySuppressionsToFindings, bindingIs, bindingTokens, bodyShapesMatch, boundaryKey, boundaryLabel, boundaryNameString, bucketRank, bucketsMeet, busIdentityKey, busesAgree, canPair, channelsPair, codeScopePath, compareRanks, countsForThreshold, dispatchByType, displayLabel, exchangesHttpResponses, fileInCodeScope, fixedTextLength, fnIdentityKey, formatChannel, functionCallBinding, gqlIdentityKey, graphqlOperationBinding, graphqlResolverBinding, groundBinding, groundedPairingKey, hasNameHole, ioBinding, ioIdentityKey, isGraphqlOperationBinding, labelWithDetail, leavesTheProcess, messageBusBinding, metricBinding, metricIdentityKey, moduleExportBinding, namePatternFromSub, namePatternKey, nameReference, namesAgree, namesBoundary, namesBoundaryExactly, namesNothing, normalizeRuleBoundary, packageExportBinding, pairingKey, parseBoundaryName, parseChannel, parseHandler, pathAfterOrigin, patternHole, rangedHole, referenceFromName, referenceName, referenceOf, reportsUnpairedItself, resourceNameIn, restBinding, ruleBoundaryMatchesKey, runtimeConfigBinding, semanticsAgree, semconvAttributes, servesRequest, setPiece, spansBuckets, spellingTokens, statesAnOrigin, storageBinding, testUnitName, unitIdentityKey, unitInvocationBinding, validateRule, withRewrittenPaths, withinScope };