@suss/ir-core 0.33.1 → 0.34.1

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.cjs CHANGED
@@ -31,6 +31,7 @@ __export(index_exports, {
31
31
  FunctionCallSemanticsSchema: () => FunctionCallSemanticsSchema,
32
32
  GraphqlOperationSemanticsSchema: () => GraphqlOperationSemanticsSchema,
33
33
  GraphqlResolverSemanticsSchema: () => GraphqlResolverSemanticsSchema,
34
+ IoSemanticsSchema: () => IoSemanticsSchema,
34
35
  MessageBusSemanticsSchema: () => MessageBusSemanticsSchema,
35
36
  MetricSemanticsSchema: () => MetricSemanticsSchema,
36
37
  NOTHING_DEPLOYED: () => NOTHING_DEPLOYED,
@@ -43,6 +44,7 @@ __export(index_exports, {
43
44
  StorageSemanticsSchema: () => StorageSemanticsSchema,
44
45
  SuppressionFileSchema: () => SuppressionFileSchema,
45
46
  SuppressionRuleSchema: () => SuppressionRuleSchema,
47
+ TEST_TITLE_SEPARATOR: () => TEST_TITLE_SEPARATOR,
46
48
  TypeShapeSchema: () => TypeShapeSchema,
47
49
  UnitInvocationSemanticsSchema: () => UnitInvocationSemanticsSchema,
48
50
  applySuppressionsToFindings: () => applySuppressionsToFindings,
@@ -52,7 +54,6 @@ __export(index_exports, {
52
54
  boundaryKey: () => boundaryKey,
53
55
  boundaryLabel: () => boundaryLabel,
54
56
  boundaryNameString: () => boundaryNameString,
55
- bucketRank: () => bucketRank,
56
57
  bucketsMeet: () => bucketsMeet,
57
58
  busIdentityKey: () => busIdentityKey,
58
59
  busesAgree: () => busesAgree,
@@ -76,12 +77,18 @@ __export(index_exports, {
76
77
  groundBinding: () => groundBinding,
77
78
  groundedPairingKey: () => groundedPairingKey,
78
79
  hasNameHole: () => hasNameHole,
80
+ hostCanBeOwn: () => hostCanBeOwn,
81
+ ioBinding: () => ioBinding,
82
+ ioIdentityKey: () => ioIdentityKey,
79
83
  isGraphqlOperationBinding: () => isGraphqlOperationBinding,
84
+ isModuleName: () => isModuleName,
85
+ labelWithDetail: () => labelWithDetail,
80
86
  leavesTheProcess: () => leavesTheProcess,
81
87
  messageBusBinding: () => messageBusBinding,
82
88
  methodsAgree: () => methodsAgree,
83
89
  metricBinding: () => metricBinding,
84
90
  metricIdentityKey: () => metricIdentityKey,
91
+ moduleExportBinding: () => moduleExportBinding,
85
92
  namePatternFromSub: () => namePatternFromSub,
86
93
  namePatternKey: () => namePatternKey,
87
94
  nameReference: () => nameReference,
@@ -91,7 +98,9 @@ __export(index_exports, {
91
98
  namesNothing: () => namesNothing,
92
99
  normalizePath: () => normalizePath,
93
100
  normalizeRuleBoundary: () => normalizeRuleBoundary,
101
+ operationKey: () => operationKey,
94
102
  packageExportBinding: () => packageExportBinding,
103
+ pairRank: () => pairRank,
95
104
  pairingKey: () => pairingKey,
96
105
  parseBoundaryName: () => parseBoundaryName,
97
106
  parseChannel: () => parseChannel,
@@ -121,13 +130,15 @@ __export(index_exports, {
121
130
  storageContainerLabel: () => storageContainerLabel,
122
131
  storageLabel: () => storageLabel,
123
132
  storageSystemLabel: () => storageSystemLabel,
133
+ testUnitName: () => testUnitName,
124
134
  typeDefinitionKey: () => typeDefinitionKey,
125
135
  unitIdentityKey: () => unitIdentityKey,
126
136
  unitInvocationBinding: () => unitInvocationBinding,
127
137
  validateRule: () => validateRule,
128
138
  withDefinitionsInlined: () => withDefinitionsInlined,
129
139
  withRewrittenPaths: () => withRewrittenPaths,
130
- withinScope: () => withinScope
140
+ withinScope: () => withinScope,
141
+ withoutOwnHost: () => withoutOwnHost
131
142
  });
132
143
  module.exports = __toCommonJS(index_exports);
133
144
 
@@ -156,15 +167,15 @@ var graphqlOperationSemantics = defineBoundarySemantics({
156
167
  behavior: {
157
168
  /**
158
169
  * An operation gets a data-and-errors document back, which the
159
- * GraphQL contract checker reads. Status codes say nothing here.
170
+ * GraphQL contract checker reads. Status codes do not apply.
160
171
  */
161
172
  exchangesHttpResponses: false,
162
173
  leavesTheProcess: true,
163
174
  reportsUnpairedItself: false,
164
175
  identityKey: () => null,
165
- /** Keyless by design; the dedicated pass pairs it by its document. */
176
+ /** No key on purpose. The dedicated pass pairs an operation by its document. */
166
177
  canPair: () => true,
167
- /** "query GetUser". The contract checker pairs it, the key does not. */
178
+ /** `query GetUser`. Only for display, since the contract checker does the pairing. */
168
179
  displayLabel(semantics) {
169
180
  return `${semantics.operationType} ${semantics.operationName ?? "<anonymous>"}`;
170
181
  }
@@ -187,22 +198,26 @@ function unitIdentityKey(deploymentTarget, instanceName) {
187
198
  function metricIdentityKey(metricSystem, metricType) {
188
199
  return `metric:${metricSystem} ${metricType}`;
189
200
  }
201
+ function ioIdentityKey(target) {
202
+ return `io:${target}`;
203
+ }
190
204
 
191
205
  // src/schemas.ts
192
- var import_zod12 = require("zod");
206
+ var import_zod13 = require("zod");
193
207
 
194
208
  // src/semantics/registry.ts
195
- var import_zod11 = require("zod");
209
+ var import_zod12 = require("zod");
196
210
 
197
211
  // src/semantics/functionCall.ts
198
212
  var import_zod2 = require("zod");
199
213
  var FunctionCallSemanticsSchema = import_zod2.z.object({
200
214
  name: import_zod2.z.literal("function-call"),
201
215
  /**
202
- * Optional module identifier for cross-unit references
203
- * (e.g. `"./components/Button"` for a React component, or the TS
204
- * module path for a bare function export). Packs that don't do
205
- * cross-module pairing can leave it unset.
216
+ * Where the function lives inside this repository. It is either the
217
+ * name of a module the project lists in `suss.json`, such as
218
+ * `"billing"`, or a module path, such as the file a server action is
219
+ * written in. A name has no separator in it and a path always does.
220
+ * Packs that don't do cross-module pairing can leave it unset.
206
221
  */
207
222
  module: import_zod2.z.string().optional(),
208
223
  /** Named export within the module, when applicable. */
@@ -227,35 +242,38 @@ var FunctionCallSemanticsSchema = import_zod2.z.object({
227
242
  */
228
243
  exportPath: import_zod2.z.array(import_zod2.z.string()).optional()
229
244
  });
245
+ function isModuleName(module2) {
246
+ return !module2.includes("/") && !module2.includes("\\");
247
+ }
230
248
  var functionCallSemantics = defineBoundarySemantics({
231
249
  name: "function-call",
232
250
  schema: FunctionCallSemanticsSchema,
233
- // A call that never leaves the process does not produce a span, and
234
- // the conventions do not name one. All of it is ours.
251
+ // A call inside the process does not produce a span, and the
252
+ // conventions have no attributes for it.
235
253
  semconv: {},
236
254
  behavior: {
237
- /** A call returns a value, which is not a status and a body. */
255
+ /** A call returns a value, with no status. */
238
256
  exchangesHttpResponses: false,
239
257
  leavesTheProcess: false,
240
258
  reportsUnpairedItself: false,
241
259
  /**
242
- * In-repo units (components, bare handlers) are keyless by design
243
- * and pair through call edges rather than the keyed pass, so a
244
- * missing key is not a boundary nothing can reach.
260
+ * In-repo units such as components have no key on purpose. They
261
+ * pair through call edges instead of the keyed pass, so a missing
262
+ * key does not make them unreachable.
245
263
  */
246
264
  canPair: () => true,
247
- /**
248
- * `"fn:<package>::<exportPath>"` when both are set, and
249
- * `"fn:<module>::<exportName>"` for an in-repo unit that states its
250
- * module, the way a server action does. Components and bare
251
- * handlers state neither and have no key at all.
252
- */
253
265
  rewritePaths(semantics, rewrite) {
254
- if (semantics.module === void 0) {
266
+ if (semantics.module === void 0 || isModuleName(semantics.module)) {
255
267
  return semantics;
256
268
  }
257
269
  return { ...semantics, module: rewrite(semantics.module) };
258
270
  },
271
+ /**
272
+ * `"fn:<package>::<exportPath>"` when both are set, and
273
+ * `"fn:<module>::<exportName>"` for an in-repo unit that gives its
274
+ * module, as a server action does. Components and plain handlers
275
+ * give neither and have no key.
276
+ */
259
277
  identityKey(semantics) {
260
278
  if (semantics.package !== void 0 && semantics.exportPath !== void 0 && semantics.exportPath.length > 0) {
261
279
  return fnIdentityKey(semantics.package, semantics.exportPath);
@@ -284,14 +302,14 @@ var GraphqlResolverSemanticsSchema = import_zod3.z.object({
284
302
  var graphqlResolverSemantics = defineBoundarySemantics({
285
303
  name: "graphql-resolver",
286
304
  schema: GraphqlResolverSemanticsSchema,
287
- // The conventions name the operation a client sent, not the
288
- // resolver the server ran for one field of it.
305
+ // The conventions describe the operation a client sent. They have no
306
+ // attribute for the resolver the server ran for one field of it.
289
307
  semconv: {},
290
308
  behavior: {
291
- /** A resolver returns a field value, not a status and a body. */
309
+ /** A resolver returns a field value, with no status. */
292
310
  exchangesHttpResponses: false,
293
- // A query somebody sent from outside runs it, even though the
294
- // server calls the function in its own process.
311
+ // A resolver runs because a client outside the process sent a query,
312
+ // even though the server calls it in its own process.
295
313
  leavesTheProcess: true,
296
314
  reportsUnpairedItself: false,
297
315
  /**
@@ -308,8 +326,32 @@ var graphqlResolverSemantics = defineBoundarySemantics({
308
326
  }
309
327
  });
310
328
 
311
- // src/semantics/messageBus.ts
329
+ // src/semantics/io.ts
312
330
  var import_zod4 = require("zod");
331
+ var IoSemanticsSchema = import_zod4.z.object({
332
+ name: import_zod4.z.literal("io"),
333
+ /** The stream, such as `stdout` or `stderr`. Null when the source does not say which. */
334
+ target: import_zod4.z.string().nullable()
335
+ });
336
+ var ioSemantics = defineBoundarySemantics({
337
+ name: "io",
338
+ schema: IoSemanticsSchema,
339
+ // The semantic conventions have no attribute for a process's own streams.
340
+ semconv: {},
341
+ behavior: {
342
+ exchangesHttpResponses: false,
343
+ leavesTheProcess: true,
344
+ reportsUnpairedItself: false,
345
+ identityKey(semantics) {
346
+ return semantics.target === null ? null : ioIdentityKey(semantics.target);
347
+ },
348
+ // Nothing declares the reading side, so a write has nothing to meet.
349
+ canPair: () => false
350
+ }
351
+ });
352
+
353
+ // src/semantics/messageBus.ts
354
+ var import_zod5 = require("zod");
313
355
 
314
356
  // src/channel.ts
315
357
  function parseChannel(channel) {
@@ -335,12 +377,11 @@ function channelsPair(a, b) {
335
377
  }
336
378
 
337
379
  // src/semantics/messageBus.ts
338
- var MessageBusSemanticsSchema = import_zod4.z.object({
339
- name: import_zod4.z.literal("message-bus"),
340
- messageBus: import_zod4.z.enum([
341
- // The conventions spell the two AWS buses this way, one with an
342
- // underscore and one with a dot, so these are their strings and
343
- // not a typo.
380
+ var MessageBusSemanticsSchema = import_zod5.z.object({
381
+ name: import_zod5.z.literal("message-bus"),
382
+ messageBus: import_zod5.z.enum([
383
+ // The conventions write these two AWS buses differently, one with an
384
+ // underscore and one with a dot. Both are as the conventions give them.
344
385
  "aws_sqs",
345
386
  "aws.sns",
346
387
  "s3",
@@ -359,12 +400,13 @@ var MessageBusSemanticsSchema = import_zod4.z.object({
359
400
  "cloudflare-tail"
360
401
  ]),
361
402
  /**
362
- * Stable channel identifier: a CFN logical id, a queue or topic name,
363
- * a subject pattern, or `bus#detailType`. Null when this source does
364
- * not say which channel, as with a send whose queue the code picks at
365
- * runtime, or a receive whose queue the event-source mapping states.
403
+ * A stable channel identifier: a CloudFormation logical id, a queue or
404
+ * topic name, a subject pattern, or `bus#detailType`. Null when this
405
+ * source does not say which channel, as with a send whose queue the
406
+ * code picks at run time, or a receive whose queue is set in the
407
+ * event-source mapping.
366
408
  */
367
- channel: import_zod4.z.string().min(1).nullable()
409
+ channel: import_zod5.z.string().min(1).nullable()
368
410
  });
369
411
  var messageBusSemantics = defineBoundarySemantics({
370
412
  name: "message-bus",
@@ -378,25 +420,26 @@ var messageBusSemantics = defineBoundarySemantics({
378
420
  exchangesHttpResponses: false,
379
421
  leavesTheProcess: true,
380
422
  /**
381
- * `checkMessageBus` looks at every channel, including the unused
382
- * ones, so the generic unmatched lists leave them alone.
423
+ * `checkMessageBus` reports every channel, unused ones included, so
424
+ * the generic unmatched list leaves them out.
383
425
  */
384
426
  reportsUnpairedItself: true,
385
427
  /**
386
- * A channel unnamed here can still pair: the dedicated pass
387
- * collapses env chains before comparing, so a consumer whose
388
- * channel arrives at runtime meets the template that declares it.
428
+ * A channel that is null here can still pair. The dedicated pass
429
+ * follows environment variable chains before comparing, so a
430
+ * consumer whose channel is set at run time meets the template that
431
+ * declares it.
389
432
  */
390
433
  canPair: () => true,
391
434
  /**
392
435
  * `"bus:<messageBus> <subject>"`, or null when the channel is null
393
436
  * or its subject is empty.
394
437
  *
395
- * The key has the subject in it and leaves out the bus, so a
396
- * template writing `default#order.placed` and a handler writing
438
+ * The key has the subject and leaves out the bus, so a template
439
+ * writing `default#order.placed` and a handler writing
397
440
  * `order.placed` land in one bucket, where `sidesAgree` compares the
398
- * buses. A side that cannot know its bus would otherwise be keyed
399
- * away from the side that can.
441
+ * buses. Keying on the bus would separate a side that cannot know
442
+ * its bus from the side that can.
400
443
  *
401
444
  * The bus technology stays in, because a queue and an event router
402
445
  * are different destinations even with the same subject. Case stays
@@ -419,9 +462,9 @@ var messageBusSemantics = defineBoundarySemantics({
419
462
  return channelsPair(a.channel, b.channel);
420
463
  },
421
464
  /**
422
- * The whole channel, bus included, unlike the identity key, which
423
- * has the subject alone. Someone reading a list of unmatched
424
- * channels wants to see which bus each one was on.
465
+ * The whole channel, bus included, where the identity key has only
466
+ * the subject. A reader looking at unmatched channels needs to see
467
+ * which bus each one was on.
425
468
  */
426
469
  displayLabel(semantics) {
427
470
  if (semantics.channel === null) {
@@ -433,25 +476,25 @@ var messageBusSemantics = defineBoundarySemantics({
433
476
  });
434
477
 
435
478
  // src/semantics/metric.ts
436
- var import_zod5 = require("zod");
437
- var MetricSemanticsSchema = import_zod5.z.object({
438
- name: import_zod5.z.literal("metric"),
439
- /** Which system the series lives in: cloud-monitoring, cloudwatch. */
440
- metricSystem: import_zod5.z.string(),
479
+ var import_zod6 = require("zod");
480
+ var MetricSemanticsSchema = import_zod6.z.object({
481
+ name: import_zod6.z.literal("metric"),
482
+ /** The system the series is stored in, such as `cloud-monitoring` or `cloudwatch`. */
483
+ metricSystem: import_zod6.z.string(),
441
484
  /**
442
- * The type string the system knows the metric by, spelled the way
443
- * both sides spell it. Null when the source states one this reader
444
- * could not settle, as with a query it could not read: a metric
445
- * nobody could name pairs with nothing rather than with whatever
446
- * happens to share its source text.
485
+ * The type string the system uses for the metric, written the same
486
+ * way on both sides. Null when the reader could not work the string
487
+ * out, as with a query it could not parse. A null type pairs with
488
+ * nothing, so it cannot pair with an unrelated metric that happens to
489
+ * share its source text.
447
490
  */
448
- metricType: import_zod5.z.string().nullable()
491
+ metricType: import_zod6.z.string().nullable()
449
492
  });
450
493
  var metricSemantics = defineBoundarySemantics({
451
494
  name: "metric",
452
495
  schema: MetricSemanticsSchema,
453
- // OpenTelemetry knows a metric by its instrument name, and has no
454
- // attribute for the system it lives in or the type string it goes by.
496
+ // OpenTelemetry identifies a metric by its instrument name. It has no
497
+ // attribute for the metric's system or its type string.
455
498
  semconv: {},
456
499
  behavior: {
457
500
  exchangesHttpResponses: false,
@@ -467,7 +510,7 @@ var metricSemantics = defineBoundarySemantics({
467
510
  });
468
511
 
469
512
  // src/semantics/rest.ts
470
- var import_zod6 = require("zod");
513
+ var import_zod7 = require("zod");
471
514
 
472
515
  // src/dispatch.ts
473
516
  function dispatchByType(table, value) {
@@ -615,6 +658,38 @@ function pathAfterOrigin(text) {
615
658
  function statesAnOrigin(text) {
616
659
  return SCHEME_ORIGIN.test(text) || PROTOCOL_RELATIVE_ORIGIN.test(text);
617
660
  }
661
+ var PRIVATE_SUFFIXES = [
662
+ "local",
663
+ "internal",
664
+ "localhost",
665
+ "lan",
666
+ "home.arpa",
667
+ "svc",
668
+ "test",
669
+ "example",
670
+ "invalid",
671
+ "example.com",
672
+ "example.net",
673
+ "example.org"
674
+ ];
675
+ var PRIVATE_IPV4 = /^(?:127\.|10\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.|0\.0\.0\.0$)/;
676
+ function hostnameOf(host) {
677
+ const lower = host.toLowerCase();
678
+ const name = lower.startsWith("[") ? lower.slice(0, lower.indexOf("]") + 1) : lower.split(":")[0] ?? lower;
679
+ return name.replace(/\.$/, "");
680
+ }
681
+ function hostCanBeOwn(host) {
682
+ const name = hostnameOf(host);
683
+ if (name === "[::1]" || /^\[f[cd]/.test(name) || PRIVATE_IPV4.test(name)) {
684
+ return true;
685
+ }
686
+ if (!name.includes(".")) {
687
+ return true;
688
+ }
689
+ return PRIVATE_SUFFIXES.some(
690
+ (suffix) => name === suffix || name.endsWith(`.${suffix}`)
691
+ );
692
+ }
618
693
 
619
694
  // src/semantics/pathPattern.ts
620
695
  var HOLE_RANGE_SUFFIX = {
@@ -681,14 +756,18 @@ function shapedItem(segment) {
681
756
  return ".*";
682
757
  }
683
758
  if (part.startsWith("{")) {
684
- return "[^/]+";
759
+ return /[?*]\}$/.test(part) ? "[^/]*" : "[^/]+";
685
760
  }
686
761
  return escapedForRegex(part);
687
762
  }).join("");
763
+ const holes = segment.match(/\{[^}]*\}|\*/g) ?? [];
764
+ const text = segment.replace(/\{[^}]*\}|\*/g, "");
688
765
  return {
689
766
  kind: "shaped",
690
767
  shape: segment.replace(/\{[^}]*\}/g, "{}"),
691
- test: new RegExp(`^${source}$`)
768
+ test: new RegExp(`^${source}$`),
769
+ bare: holes.every((hole) => hole === "*" || /[?*]\}$/.test(hole)) ? text : null,
770
+ textLength: text.length
692
771
  };
693
772
  }
694
773
  function itemsOf(segment) {
@@ -708,10 +787,35 @@ function segmentsOf(alternative) {
708
787
  const trimmed = alternative.startsWith("/") ? alternative.slice(1) : alternative;
709
788
  return trimmed === "" ? [] : trimmed.split("/");
710
789
  }
790
+ function patternSegmentsOf(alternative) {
791
+ const segments = segmentsOf(alternative);
792
+ const first = segments[0];
793
+ const opensWithBaseUrl = !alternative.startsWith("/") && first !== void 0 && HOLE_SEGMENT.test(first);
794
+ return opensWithBaseUrl ? segments.slice(1) : segments;
795
+ }
796
+ function pathAfterBaseUrl(path) {
797
+ const segments = segmentsOf(path);
798
+ const first = segments[0];
799
+ if (path.startsWith("/") || first === void 0 || segments.length < 2 || !HOLE_SEGMENT.test(first)) {
800
+ return path;
801
+ }
802
+ return `/${segments.slice(1).join("/")}`;
803
+ }
804
+ var PATTERNS = /* @__PURE__ */ new Map();
805
+ var PATTERN_CACHE_LIMIT = 5e4;
711
806
  function patternOf(path) {
712
- return alternativesOf(path).map(
713
- (alternative) => segmentsOf(alternative).flatMap(itemsOf)
807
+ const known = PATTERNS.get(path);
808
+ if (known !== void 0) {
809
+ return known;
810
+ }
811
+ const pattern = alternativesOf(path).map(
812
+ (alternative) => patternSegmentsOf(alternative).flatMap(itemsOf)
714
813
  );
814
+ if (PATTERNS.size >= PATTERN_CACHE_LIMIT) {
815
+ PATTERNS.clear();
816
+ }
817
+ PATTERNS.set(path, pattern);
818
+ return pattern;
715
819
  }
716
820
  function literalItemsOf(path) {
717
821
  return segmentsOf(path).map((text) => ({ kind: "text", text }));
@@ -790,17 +894,104 @@ function pathSpansShapes(path) {
790
894
  function countOf(items, kinds) {
791
895
  return items.filter((item) => kinds.includes(item.kind)).length;
792
896
  }
793
- function pathSpecificity(path) {
794
- const alternatives = patternOf(path);
795
- const ranks = alternatives.map((items) => [
897
+ function readingSpecificity(items) {
898
+ return [
796
899
  countOf(items, ["text"]),
797
900
  countOf(items, ["shaped"]),
798
901
  -countOf(items, ["optional", "star"])
799
- ]);
800
- const loosest = ranks.reduce(
801
- (low, rank) => compareRanks(rank, low) < 0 ? rank : low
902
+ ];
903
+ }
904
+ function segmentFit(route, request) {
905
+ if (!meetOnOneSegment(route, request)) {
906
+ return "apart";
907
+ }
908
+ if (request.kind === "text") {
909
+ if (route.kind === "text") {
910
+ return "stated";
911
+ }
912
+ return route.kind === "shaped" ? "shaped" : "free";
913
+ }
914
+ if (absorbsAnything(route)) {
915
+ return "free";
916
+ }
917
+ if (request.kind !== "shaped") {
918
+ return "guess";
919
+ }
920
+ if (route.kind === "shaped") {
921
+ return "free";
922
+ }
923
+ return route.kind === "text" && request.bare === route.text ? "stated" : "guess";
924
+ }
925
+ function shapedText(item) {
926
+ return item.kind === "shaped" ? item.textLength : 0;
927
+ }
928
+ function betterFit(a, b) {
929
+ return a === void 0 || compareRanks(b, a) > 0;
930
+ }
931
+ function bestFit(route, request) {
932
+ const width = request.length + 1;
933
+ const best = new Array((route.length + 1) * width);
934
+ best[0] = [0, 0];
935
+ const offer = (i, j, fit) => {
936
+ const at = i * width + j;
937
+ if (betterFit(best[at], fit)) {
938
+ best[at] = fit;
939
+ }
940
+ };
941
+ for (let reached = 0; reached <= route.length + request.length; reached += 1) {
942
+ for (let i = Math.max(0, reached - request.length); i <= Math.min(route.length, reached); i += 1) {
943
+ const j = reached - i;
944
+ const fit = best[i * width + j];
945
+ if (fit === void 0) {
946
+ continue;
947
+ }
948
+ const left = route[i];
949
+ const right = request[j];
950
+ if (left !== void 0 && skippable(left)) {
951
+ offer(i + 1, j, fit);
952
+ }
953
+ if (right !== void 0 && skippable(right)) {
954
+ offer(i, j + 1, fit);
955
+ }
956
+ if (left === void 0 || right === void 0) {
957
+ continue;
958
+ }
959
+ if (left.kind === "star" && right.kind === "star") {
960
+ continue;
961
+ }
962
+ const kind = segmentFit(left, right);
963
+ if (kind === "apart" || kind === "guess") {
964
+ continue;
965
+ }
966
+ offer(
967
+ left.kind === "star" ? i : i + 1,
968
+ right.kind === "star" ? j : j + 1,
969
+ [
970
+ fit[0] + (kind === "stated" ? 1 : 0),
971
+ fit[1] + (kind === "shaped" ? shapedText(left) : 0)
972
+ ]
973
+ );
974
+ }
975
+ }
976
+ return best[route.length * width + request.length] ?? null;
977
+ }
978
+ function requestRank(route, request) {
979
+ let found = null;
980
+ for (const routeItems of patternOf(route)) {
981
+ for (const requestItems of patternOf(request)) {
982
+ const fit = bestFit(routeItems, requestItems);
983
+ const rank = fit === null ? null : [...fit, ...readingSpecificity(routeItems)];
984
+ if (rank !== null && (found === null || compareRanks(rank, found) > 0)) {
985
+ found = rank;
986
+ }
987
+ }
988
+ }
989
+ return found;
990
+ }
991
+ function isCatchAll(route) {
992
+ return patternOf(route).some(
993
+ (items) => items.length > 0 && items.every(absorbsAnything) && items.some((item) => item.kind === "star")
802
994
  );
803
- return [...loosest, 1 - alternatives.length];
804
995
  }
805
996
  function compareRanks(a, b) {
806
997
  const length = Math.max(a.length, b.length);
@@ -814,23 +1005,34 @@ function compareRanks(a, b) {
814
1005
  }
815
1006
 
816
1007
  // src/semantics/rest.ts
817
- var RestSemanticsSchema = import_zod6.z.object({
818
- name: import_zod6.z.literal("rest"),
1008
+ var RestSemanticsSchema = import_zod7.z.object({
1009
+ name: import_zod7.z.literal("rest"),
819
1010
  /**
820
1011
  * Uppercase HTTP method ("GET", "POST", …), `"*"` for a handler that
821
1012
  * responds to every method, or null when this source does not say
822
1013
  * which method.
823
1014
  */
824
- method: import_zod6.z.string().min(1).nullable(),
1015
+ method: import_zod7.z.string().min(1).nullable(),
825
1016
  /** Normalized route path ("/users/{id}"), or null when this source does not give one. */
826
- path: import_zod6.z.string().min(1).nullable(),
1017
+ path: import_zod7.z.string().min(1).nullable(),
827
1018
  /**
828
- * Status codes the producing source explicitly declared (OpenAPI
829
- * responses, CFN MethodResponses, ts-rest router statuses). They are
830
- * kept here so the pairing layer can see them without unwrapping any
831
- * metadata. Absent, or empty, for a source that was inferred.
1019
+ * Status codes the producing source declared explicitly, such as
1020
+ * OpenAPI responses or CloudFormation MethodResponses. They are kept
1021
+ * here so the pairing layer can see them without unwrapping any
1022
+ * metadata. Absent or empty for an inferred source.
832
1023
  */
833
- declaredResponses: import_zod6.z.array(import_zod6.z.number()).optional()
1024
+ declaredResponses: import_zod7.z.array(import_zod7.z.number()).optional(),
1025
+ /**
1026
+ * The host a client wrote in an absolute URL, with its port
1027
+ * (`api.example.com:8443`). A piece suss could not read is written
1028
+ * `{name}`. Absent for a relative URL and for a route.
1029
+ */
1030
+ host: import_zod7.z.string().min(1).optional(),
1031
+ /**
1032
+ * True for a route the framework also serves with `.{format}` after
1033
+ * its path, the way Rails serves `/search.json` from `get "search"`.
1034
+ */
1035
+ optionalFormat: import_zod7.z.boolean().optional()
834
1036
  });
835
1037
  var OPENING_HOLE = /^\{([^{}]+)\}(.*)$/;
836
1038
  function baseUrlReference(semantics) {
@@ -838,7 +1040,10 @@ function baseUrlReference(semantics) {
838
1040
  return label === void 0 ? null : referenceFromName(patternHole(label));
839
1041
  }
840
1042
  function normalizePath(path) {
841
- let normalized = path.replace(/:([a-zA-Z_]\w*)([?+*]?)/g, "{$1$2}");
1043
+ let normalized = withoutParameterConstraints(path).replace(
1044
+ /:([a-zA-Z_]\w*)([?+*]?)/g,
1045
+ "{$1$2}"
1046
+ );
842
1047
  if (normalized.length > 1 && normalized.endsWith("/")) {
843
1048
  normalized = normalized.slice(0, -1);
844
1049
  }
@@ -848,6 +1053,46 @@ function normalizePath(path) {
848
1053
  );
849
1054
  return normalized;
850
1055
  }
1056
+ function withoutParameterConstraints(path) {
1057
+ let out = "";
1058
+ let i = 0;
1059
+ while (i < path.length) {
1060
+ const name = /^:[a-zA-Z_]\w*\(/.exec(path.slice(i));
1061
+ if (name === null) {
1062
+ out += path[i];
1063
+ i += 1;
1064
+ continue;
1065
+ }
1066
+ const open = i + name[0].length - 1;
1067
+ const close = closingParenthesis(path, open);
1068
+ if (close === -1) {
1069
+ return out + path.slice(i);
1070
+ }
1071
+ out += path.slice(i, open);
1072
+ i = close + 1;
1073
+ }
1074
+ return out;
1075
+ }
1076
+ function closingParenthesis(text, open) {
1077
+ let depth = 0;
1078
+ for (let i = open; i < text.length; i += 1) {
1079
+ const char = text[i];
1080
+ if (char === "\\") {
1081
+ i += 1;
1082
+ continue;
1083
+ }
1084
+ if (char === "(") {
1085
+ depth += 1;
1086
+ }
1087
+ if (char === ")") {
1088
+ depth -= 1;
1089
+ if (depth === 0) {
1090
+ return i;
1091
+ }
1092
+ }
1093
+ }
1094
+ return -1;
1095
+ }
851
1096
  function pathShape(path) {
852
1097
  return normalizePath(path).replace(/\{[^{}]*?([?+*]?)\}/g, "{$1}");
853
1098
  }
@@ -863,6 +1108,40 @@ function routePathAdmits(declaredPath, requestPath) {
863
1108
  function routePathsMeet(a, b) {
864
1109
  return pathsMeet(normalizePath(a), normalizePath(b));
865
1110
  }
1111
+ function readHost(semantics) {
1112
+ const host = semantics.host;
1113
+ return host === void 0 || /[{(]/.test(host) ? void 0 : host;
1114
+ }
1115
+ function hostsAgree(a, b) {
1116
+ return readHost(a) === readHost(b);
1117
+ }
1118
+ function hostUnread(semantics) {
1119
+ if (semantics.host !== void 0) {
1120
+ return readHost(semantics) === void 0;
1121
+ }
1122
+ return baseUrlReference(semantics) !== null;
1123
+ }
1124
+ function servedPath(semantics, path) {
1125
+ const normalized = normalizePath(path);
1126
+ return semantics.optionalFormat === true ? `${normalized}(|.{format})` : normalized;
1127
+ }
1128
+ function withoutHost(semantics) {
1129
+ const { host: _host, ...rest } = semantics;
1130
+ return rest;
1131
+ }
1132
+ function groundedBaseUrl(semantics, deployment) {
1133
+ const reference = baseUrlReference(semantics);
1134
+ if (reference === null) {
1135
+ return null;
1136
+ }
1137
+ const base = deployment.setTo(reference);
1138
+ if (base === null) {
1139
+ return null;
1140
+ }
1141
+ const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
1142
+ const grounded = pathAfterOrigin(`${base}${rest}`);
1143
+ return { ...semantics, path: grounded === "" ? "/" : grounded };
1144
+ }
866
1145
  var restSemantics = defineBoundarySemantics({
867
1146
  name: "rest",
868
1147
  schema: RestSemanticsSchema,
@@ -887,39 +1166,65 @@ var restSemantics = defineBoundarySemantics({
887
1166
  return `${semantics.method.toUpperCase()} ${normalizePath(semantics.path)}`;
888
1167
  },
889
1168
  /**
890
- * The bucket has only the path in it, so a `"*"` route lands with
891
- * the consumers that each specify a method, and `methodsAgree`
892
- * settles it inside the bucket. The identity key stays what a reader
893
- * sees and a suppression targets ("GET /users", "* /users"), and
894
- * this key groups what a reader still tells apart.
1169
+ * The bucket has only the path, so a `"*"` route lands with the
1170
+ * consumers that each give a method, and `methodsAgree` compares the
1171
+ * methods inside the bucket. The identity key is still what a reader
1172
+ * sees and what a suppression targets ("GET /users", "* /users").
895
1173
  *
896
- * A parameter's name goes too. A spec written by hand says
897
- * `{userId}` where the Express route that serves it says `:id`, and
898
- * both match the same requests, so pairing on the name gave two
899
- * boundaries that never met and a run with nothing to report.
1174
+ * Parameter names are dropped too. A hand-written spec writes
1175
+ * `{userId}` where the Express route that serves it writes `:id`,
1176
+ * and both match the same requests. Pairing on the name would leave
1177
+ * two boundaries that never meet.
900
1178
  */
901
1179
  pairingKey(semantics) {
902
1180
  if (semantics.method === null || semantics.path === null) {
903
1181
  return null;
904
1182
  }
905
- return `rest ${pathShape(semantics.path)}`;
1183
+ const shape = pathShape(pathAfterBaseUrl(normalizePath(semantics.path)));
1184
+ const host = readHost(semantics);
1185
+ return host === void 0 ? `rest ${shape}` : `rest //${host}${shape}`;
1186
+ },
1187
+ /**
1188
+ * A spec writes `/teams/{team_id}` where the route that serves it
1189
+ * writes `/teams/:id`. The method stays in, so a `"*"` handler is
1190
+ * never compared with the document for one method.
1191
+ */
1192
+ operationKey(semantics) {
1193
+ if (semantics.method === null || semantics.path === null) {
1194
+ return null;
1195
+ }
1196
+ return `${semantics.method.toUpperCase()} ${pathShape(semantics.path)}`;
906
1197
  },
907
1198
  sidesAgree(a, b) {
908
- return methodsAgree(a.method, b.method);
1199
+ return methodsAgree(a.method, b.method) && hostsAgree(a, b);
909
1200
  },
910
1201
  spansBuckets(semantics) {
911
- return semantics.path !== null && pathSpansShapes(normalizePath(semantics.path));
1202
+ return semantics.path !== null && (semantics.optionalFormat === true || pathSpansShapes(normalizePath(semantics.path)));
912
1203
  },
913
1204
  bucketsMeet(a, b) {
914
- return a.path !== null && b.path !== null && routePathsMeet(a.path, b.path);
1205
+ return a.path !== null && b.path !== null && hostsAgree(a, b) && pathsMeet(servedPath(a, a.path), servedPath(b, b.path));
915
1206
  },
916
- bucketRank(semantics) {
917
- return semantics.path === null ? [] : pathSpecificity(normalizePath(semantics.path));
1207
+ /**
1208
+ * A catch-all route never takes a call whose host suss could not
1209
+ * read, since nothing else ties that call to this app.
1210
+ */
1211
+ pairRank(provider, consumer) {
1212
+ if (provider.path === null || consumer.path === null) {
1213
+ return [];
1214
+ }
1215
+ const route = normalizePath(provider.path);
1216
+ if (hostUnread(consumer) && isCatchAll(route)) {
1217
+ return null;
1218
+ }
1219
+ return requestRank(
1220
+ servedPath(provider, provider.path),
1221
+ normalizePath(consumer.path)
1222
+ );
918
1223
  },
919
1224
  /**
920
- * What the identity key says, with a missing half still readable:
921
- * ANY when no method was stated, and ? when no path was. Null when
922
- * neither was stated, because then there is nothing to show.
1225
+ * The identity key, with a missing half still readable: `ANY` when
1226
+ * the source gave no method, and `?` when it gave no path. Null when
1227
+ * both are missing, since then there is nothing to show.
923
1228
  */
924
1229
  displayLabel(semantics) {
925
1230
  if (semantics.method === null && semantics.path === null) {
@@ -930,11 +1235,11 @@ var restSemantics = defineBoundarySemantics({
930
1235
  return `${method} ${path}`;
931
1236
  },
932
1237
  /**
933
- * A route with both halves stated decides for itself: the method
934
- * through `methodsAgree` (so a `"*"` route matches every method),
935
- * and the path through `routePathAdmits`. A route missing either
936
- * half might still be the one that handles the request, and nothing
937
- * here can settle that, so it abstains instead of refusing.
1238
+ * With both halves present, the method goes through `methodsAgree`
1239
+ * (so a `"*"` route matches every method) and the path through
1240
+ * `routePathAdmits`. A route missing either half might still handle
1241
+ * the request, and this declaration cannot settle that, so the
1242
+ * result is `"unknown"`.
938
1243
  */
939
1244
  servesRequest(semantics, method, path) {
940
1245
  if (semantics.method === null || semantics.path === null) {
@@ -947,43 +1252,41 @@ var restSemantics = defineBoundarySemantics({
947
1252
  },
948
1253
  /**
949
1254
  * A route the registration pattern admits is inside it. A route
950
- * whose path this source never gave is left outside, because
951
- * nothing here can show it is one the pattern covers.
1255
+ * without a path counts as outside, because nothing shows that the
1256
+ * pattern covers it.
952
1257
  */
953
1258
  withinScope(semantics, scope) {
954
1259
  return semantics.path !== null && routePathAdmits(scope, semantics.path);
955
1260
  },
956
1261
  /**
957
1262
  * A call whose base URL the deployment fills in, resolved to the
958
- * path it reaches.
959
- *
960
- * The source cannot settle this on its own, which is why the
961
- * adapter leaves the hole in. `API_BASE` could be
962
- * `http://backend.internal`, and then the path is `/orders`. It
963
- * could equally be `/api/v2`, and then the path is
964
- * `/api/v2/orders`. Putting the deployed value in and reading the
965
- * path back out gets the right one either way.
1263
+ * path it reaches, and a call to a host that can be the project's
1264
+ * own, with the host left out so it meets the route.
966
1265
  *
967
- * Only a hole at the front is a base URL. A hole further along is a
968
- * route parameter, and `/orders/{id}` means every id rather than a
969
- * variable somebody set.
1266
+ * The source cannot settle a base URL on its own, so the adapter
1267
+ * leaves the hole in. `API_BASE` could be `http://backend.internal`,
1268
+ * and then the path is `/orders`. It could equally be `/api/v2`, and
1269
+ * then the path is `/api/v2/orders`. Filling in the deployed value
1270
+ * and reading the path back out gets the right one either way, and
1271
+ * a base the deployment sets counts as the app's own.
970
1272
  */
971
- groundName(semantics, deployment) {
972
- const reference = baseUrlReference(semantics);
973
- if (reference === null) {
1273
+ /** A listed host matches with or without the port the call gives. */
1274
+ withoutOwnHost(semantics, ownHosts) {
1275
+ const host = semantics.host?.toLowerCase();
1276
+ if (host === void 0) {
974
1277
  return null;
975
1278
  }
976
- const base = deployment.setTo(reference);
977
- if (base === null) {
978
- return null;
979
- }
980
- const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
981
- const grounded = pathAfterOrigin(`${base}${rest}`);
982
- return { ...semantics, path: grounded === "" ? "/" : grounded };
1279
+ return ownHosts.has(host) || ownHosts.has(host.replace(/:\d+$/, "")) ? withoutHost(semantics) : null;
1280
+ },
1281
+ groundName(semantics, deployment) {
1282
+ const based = groundedBaseUrl(semantics, deployment);
1283
+ const grounded = based ?? semantics;
1284
+ const host = readHost(grounded);
1285
+ return host !== void 0 && hostCanBeOwn(host) ? withoutHost(grounded) : based;
983
1286
  },
984
1287
  nameReference: baseUrlReference,
985
1288
  ruleBoundary: {
986
- // "METHOD /path": one leading token, then an absolute path.
1289
+ // "METHOD /path": one token, a space, then an absolute path.
987
1290
  claims(raw) {
988
1291
  return /^\S+ +\//.test(raw.trim());
989
1292
  },
@@ -998,12 +1301,12 @@ var restSemantics = defineBoundarySemantics({
998
1301
  });
999
1302
 
1000
1303
  // src/semantics/runtimeConfig.ts
1001
- var import_zod8 = require("zod");
1304
+ var import_zod9 = require("zod");
1002
1305
 
1003
1306
  // src/deployableUnit.ts
1004
- var import_zod7 = require("zod");
1005
- var DeployableUnitSchema = import_zod7.z.object({
1006
- deploymentTarget: import_zod7.z.enum([
1307
+ var import_zod8 = require("zod");
1308
+ var DeployableUnitSchema = import_zod8.z.object({
1309
+ deploymentTarget: import_zod8.z.enum([
1007
1310
  "lambda",
1008
1311
  "ecs-task",
1009
1312
  "container",
@@ -1012,7 +1315,7 @@ var DeployableUnitSchema = import_zod7.z.object({
1012
1315
  ]),
1013
1316
  // An empty name would agree with every other empty name, so a unit
1014
1317
  // with no name has to leave the field off instead.
1015
- instanceName: import_zod7.z.string().min(1)
1318
+ instanceName: import_zod8.z.string().min(1)
1016
1319
  });
1017
1320
  function ecsContainerInstanceName(taskDefinitionLogicalId, containerName) {
1018
1321
  return `${taskDefinitionLogicalId}/${containerName}`;
@@ -1023,14 +1326,14 @@ var RuntimeConfigSemanticsSchema = DeployableUnitSchema.partial({
1023
1326
  deploymentTarget: true,
1024
1327
  instanceName: true
1025
1328
  }).extend({
1026
- name: import_zod8.z.literal("runtime-config")
1329
+ name: import_zod9.z.literal("runtime-config")
1027
1330
  });
1028
1331
  var runtimeConfigSemantics = defineBoundarySemantics({
1029
1332
  name: "runtime-config",
1030
1333
  schema: RuntimeConfigSemanticsSchema,
1031
- // service.name and cloud.resource_id name the same deployable, but
1032
- // instanceName is the deployment template's logical id, which is
1033
- // neither of those strings.
1334
+ // service.name and cloud.resource_id identify the same deployable, but
1335
+ // instanceName is the template's logical id, a different string from
1336
+ // both.
1034
1337
  semconv: {},
1035
1338
  behavior: {
1036
1339
  /** A process reads its config at startup, and nothing replies. */
@@ -1039,46 +1342,53 @@ var runtimeConfigSemantics = defineBoundarySemantics({
1039
1342
  // comes from outside the code.
1040
1343
  leavesTheProcess: true,
1041
1344
  reportsUnpairedItself: false,
1042
- identityKey: () => null
1345
+ identityKey: () => null,
1346
+ // A reader sees which deployable's environment this is. Code that
1347
+ // reads a variable does not say which deployable runs it, so a read
1348
+ // has no name.
1349
+ displayLabel(semantics) {
1350
+ const instance = semantics.instanceName;
1351
+ return instance === void 0 ? null : `runtime-config:${instance}`;
1352
+ }
1043
1353
  }
1044
1354
  });
1045
1355
 
1046
1356
  // src/semantics/storage.ts
1047
- var import_zod9 = require("zod");
1048
- var StorageSemanticsSchema = import_zod9.z.object({
1049
- name: import_zod9.z.literal("storage"),
1357
+ var import_zod10 = require("zod");
1358
+ var StorageSemanticsSchema = import_zod10.z.object({
1359
+ name: import_zod10.z.literal("storage"),
1050
1360
  /**
1051
- * Which store this is: postgresql, mysql, aws.dynamodb, or our own
1052
- * word for one OpenTelemetry never named, such as s3, or null when
1053
- * the source states one this reader could not settle. Two products'
1054
- * containers can share a name, so this keeps them apart. A null
1055
- * keeps none of them apart, and the pairing pass says what it does
1056
- * with one.
1361
+ * The storage system: an OpenTelemetry value such as `postgresql` or
1362
+ * `aws.dynamodb`, or suss's own word for one the conventions do not
1363
+ * cover, such as `s3`. Null when the reader could not work out which
1364
+ * system the source uses. Two products' containers can share a name,
1365
+ * and this field keeps them apart. The pairing pass decides how to
1366
+ * treat a null.
1057
1367
  */
1058
- storageSystem: import_zod9.z.string().nullable(),
1368
+ storageSystem: import_zod10.z.string().nullable(),
1059
1369
  /**
1060
1370
  * The ORM, schema, or deployment scope. A single-database setup uses
1061
1371
  * `"default"`, and a monorepo with several schemas gives each one its
1062
1372
  * own value.
1063
1373
  */
1064
- scope: import_zod9.z.string(),
1374
+ scope: import_zod10.z.string(),
1065
1375
  /**
1066
- * The table, bucket, collection, or index, as the source declares
1067
- * it, or null when the source states one this reader could not
1068
- * settle. A null container pairs with nothing, rather than with
1069
- * whatever its source text happens to spell. The string is in the
1070
- * boundary-name syntax: a literal, a pattern with `{}` holes, or a
1071
- * reference, and `parseBoundaryName` is the one reader of it.
1376
+ * The table, bucket, collection or index, as the source declares it.
1377
+ * Null when the reader could not work out the container, and a null
1378
+ * container pairs with nothing, so it cannot match by accident of
1379
+ * shared source text. The string uses the boundary-name syntax (a
1380
+ * literal, a pattern with `{}` holes, or a reference), and only
1381
+ * `parseBoundaryName` should read it.
1072
1382
  */
1073
- container: import_zod9.z.string().nullable(),
1383
+ container: import_zod10.z.string().nullable(),
1074
1384
  /**
1075
- * A secondary way into the container, a DynamoDB global secondary
1076
- * index or an Elasticsearch alias, each with its own key fields.
1077
- * Null means the container's own primary way in. A query through an
1385
+ * A secondary way into the container, such as a DynamoDB global
1386
+ * secondary index or an Elasticsearch alias, each with its own key
1387
+ * fields. Null means the container's primary key. A query through an
1078
1388
  * index and a query through the table are different accesses, so
1079
1389
  * they pair separately.
1080
1390
  */
1081
- accessPath: import_zod9.z.string().nullable()
1391
+ accessPath: import_zod10.z.string().nullable()
1082
1392
  });
1083
1393
  function containerReference(semantics) {
1084
1394
  return semantics.container === null ? null : referenceFromName(semantics.container);
@@ -1088,21 +1398,21 @@ var storageSemantics = defineBoundarySemantics({
1088
1398
  schema: StorageSemanticsSchema,
1089
1399
  semconv: {
1090
1400
  storageSystem: { name: "db.system.name" },
1091
- // "default" is our word for a source that named no database.
1401
+ // suss writes "default" when the source did not say which database.
1092
1402
  scope: { name: "db.namespace", placeholderValues: ["default"] },
1093
1403
  container: { name: "db.collection.name" }
1094
- // A secondary index has no attribute of its own, so accessPath
1095
- // stays our word.
1404
+ // The conventions have no attribute for a secondary index, so
1405
+ // accessPath is left out.
1096
1406
  },
1097
1407
  behavior: {
1098
- /** A query returns rows or items, not a status and a body. */
1408
+ /** A query returns rows or items, with no status. */
1099
1409
  exchangesHttpResponses: false,
1100
1410
  leavesTheProcess: true,
1101
1411
  reportsUnpairedItself: false,
1102
1412
  identityKey: () => null,
1103
1413
  displayLabel: storageLabel,
1104
1414
  /**
1105
- * A container the source states as a variable is whatever the
1415
+ * A container the source gives as a variable is whatever the
1106
1416
  * deployment sets that variable to. `{SUBSCRIBERS_TABLE}` in the
1107
1417
  * code and `prod-subscribers-v1` in the manifest are one table.
1108
1418
  */
@@ -1132,9 +1442,9 @@ function storageContainerLabel(semantics) {
1132
1442
  }
1133
1443
 
1134
1444
  // src/semantics/unitInvocation.ts
1135
- var import_zod10 = require("zod");
1445
+ var import_zod11 = require("zod");
1136
1446
  var UnitInvocationSemanticsSchema = DeployableUnitSchema.extend({
1137
- name: import_zod10.z.literal("unit-invocation"),
1447
+ name: import_zod11.z.literal("unit-invocation"),
1138
1448
  instanceName: DeployableUnitSchema.shape.instanceName.nullable()
1139
1449
  });
1140
1450
  function calleeReference(semantics) {
@@ -1164,9 +1474,9 @@ var unitInvocationSemantics = defineBoundarySemantics({
1164
1474
  */
1165
1475
  reportsUnpairedItself: false,
1166
1476
  /**
1167
- * A callee the source states only as a variable is not a name for
1168
- * anything until the deployment fills it in, so it keys as nothing
1169
- * rather than as a bucket that would agree with itself alone.
1477
+ * A callee the source gives only as a variable has no name until the
1478
+ * deployment fills it in. It gets no key, since a key built from the
1479
+ * variable would pair only with itself.
1170
1480
  */
1171
1481
  identityKey(semantics) {
1172
1482
  if (semantics.instanceName === null || namesNothing(semantics.instanceName)) {
@@ -1187,11 +1497,10 @@ var unitInvocationSemantics = defineBoundarySemantics({
1187
1497
  );
1188
1498
  },
1189
1499
  /**
1190
- * A call that reads its callee's name out of the environment
1191
- * reaches whichever resource the template wires that variable to.
1192
- * The logical id is the answer rather than the deployed name,
1193
- * because the invoked unit's own summary is keyed by the logical
1194
- * id and neither side ever writes the other's string.
1500
+ * A call that reads its callee's name from the environment reaches
1501
+ * whichever resource the template wires that variable to. The name
1502
+ * is grounded to the logical id and not the deployed name, because
1503
+ * the invoked unit's own summary is keyed by the logical id.
1195
1504
  */
1196
1505
  groundName(semantics, deployment) {
1197
1506
  const reference = calleeReference(semantics);
@@ -1206,7 +1515,7 @@ var unitInvocationSemantics = defineBoundarySemantics({
1206
1515
  });
1207
1516
 
1208
1517
  // src/semantics/registry.ts
1209
- var SemanticsSchema = import_zod11.z.discriminatedUnion("name", [
1518
+ var SemanticsSchema = import_zod12.z.discriminatedUnion("name", [
1210
1519
  restSemantics.schema,
1211
1520
  functionCallSemantics.schema,
1212
1521
  graphqlResolverSemantics.schema,
@@ -1215,7 +1524,8 @@ var SemanticsSchema = import_zod11.z.discriminatedUnion("name", [
1215
1524
  storageSemantics.schema,
1216
1525
  messageBusSemantics.schema,
1217
1526
  metricSemantics.schema,
1218
- unitInvocationSemantics.schema
1527
+ unitInvocationSemantics.schema,
1528
+ ioSemantics.schema
1219
1529
  ]);
1220
1530
  var DEFINITIONS = [
1221
1531
  restSemantics,
@@ -1226,7 +1536,8 @@ var DEFINITIONS = [
1226
1536
  storageSemantics,
1227
1537
  messageBusSemantics,
1228
1538
  metricSemantics,
1229
- unitInvocationSemantics
1539
+ unitInvocationSemantics,
1540
+ ioSemantics
1230
1541
  ];
1231
1542
  var BY_NAME = new Map(
1232
1543
  DEFINITIONS.map((d) => [d.name, d])
@@ -1249,90 +1560,95 @@ function allBehaviors() {
1249
1560
  }
1250
1561
 
1251
1562
  // src/schemas.ts
1252
- var ConfidenceSourceSchema = import_zod12.z.enum([
1563
+ var ConfidenceSourceSchema = import_zod13.z.enum([
1253
1564
  "inferred_static",
1254
1565
  "inferred_ai",
1255
1566
  "declared",
1256
1567
  "derived"
1257
1568
  ]);
1258
- var ConfidenceLevelSchema = import_zod12.z.enum(["high", "medium", "low"]);
1259
- var CorroborationSchema = import_zod12.z.object({
1260
- outcome: import_zod12.z.enum(["observed", "refuted", "untested"]),
1569
+ var ConfidenceLevelSchema = import_zod13.z.enum(["high", "medium", "low"]);
1570
+ var CorroborationSchema = import_zod13.z.object({
1571
+ outcome: import_zod13.z.enum(["observed", "refuted", "untested"]),
1261
1572
  /** Executions that produced a verdict for this claim. */
1262
- runs: import_zod12.z.number(),
1573
+ runs: import_zod13.z.number(),
1263
1574
  /** Present when refuted: the input and observation that disagreed. */
1264
- counterexample: import_zod12.z.unknown().optional(),
1575
+ counterexample: import_zod13.z.unknown().optional(),
1265
1576
  /** Present when untested: why no verdict was reachable. */
1266
- reason: import_zod12.z.string().optional()
1577
+ reason: import_zod13.z.string().optional()
1267
1578
  });
1268
- var ConfidenceInfoSchema = import_zod12.z.object({
1579
+ var ConfidenceInfoSchema = import_zod13.z.object({
1269
1580
  source: ConfidenceSourceSchema,
1270
1581
  level: ConfidenceLevelSchema,
1271
1582
  corroboration: CorroborationSchema.optional()
1272
1583
  });
1273
- var SourceLocationSchema = import_zod12.z.object({
1274
- file: import_zod12.z.string(),
1584
+ var SourceLocationSchema = import_zod13.z.object({
1585
+ file: import_zod13.z.string(),
1275
1586
  /** Line numbers, for a person reading the summary or an editor link. */
1276
- range: import_zod12.z.object({ start: import_zod12.z.number(), end: import_zod12.z.number() }),
1587
+ range: import_zod13.z.object({ start: import_zod13.z.number(), end: import_zod13.z.number() }),
1277
1588
  /**
1278
- * Character offsets of the unit in its file. Identity and joins
1279
- * measure with these, since two functions can share a line and never
1280
- * an offset range; the line `range` above is the presentation of the
1281
- * same place. Absent on summaries no source position backs, a
1282
- * contract artifact's say.
1589
+ * Character offsets of the unit in its file. Identity and joins use
1590
+ * these, because two functions can share a line but never an offset
1591
+ * range. `range` gives the same place in lines. Absent on a summary
1592
+ * with no source position, such as one read from a contract.
1283
1593
  */
1284
- span: import_zod12.z.object({ start: import_zod12.z.number(), end: import_zod12.z.number() }).optional(),
1285
- exportName: import_zod12.z.string().nullable(),
1594
+ span: import_zod13.z.object({ start: import_zod13.z.number(), end: import_zod13.z.number() }).optional(),
1595
+ exportName: import_zod13.z.string().nullable(),
1286
1596
  /**
1287
- * The project the extract was pointed at, by the name that project
1288
- * calls itself.
1597
+ * The project the extract ran on, by the name the project gives
1598
+ * itself.
1289
1599
  *
1290
1600
  * Paths are relative to wherever the extract ran, so two services in
1291
- * one repository both report `src/handlers.ts`, and merging their
1292
- * summaries puts them on top of each other. This field is what tells
1293
- * them apart, and what a reader groups by.
1601
+ * one repository can both report `src/handlers.ts`, and merging their
1602
+ * summaries would mix them up. This field tells them apart, and a
1603
+ * reader groups by it.
1294
1604
  */
1295
- workspace: import_zod12.z.string().optional()
1605
+ workspace: import_zod13.z.string().optional(),
1606
+ /**
1607
+ * The module of the application the unit belongs to, by the name the
1608
+ * project's `suss.json` gives it. Set only when the project lists its
1609
+ * modules, and then on every unit whose file is under a module's root.
1610
+ */
1611
+ module: import_zod13.z.string().optional()
1296
1612
  });
1297
- var BoundaryBindingSchema = import_zod12.z.object({
1298
- transport: import_zod12.z.string(),
1613
+ var BoundaryBindingSchema = import_zod13.z.object({
1614
+ transport: import_zod13.z.string(),
1299
1615
  semantics: SemanticsSchema,
1300
- recognition: import_zod12.z.string()
1616
+ recognition: import_zod13.z.string()
1301
1617
  });
1302
1618
  function typeDefinitionKey(ref) {
1303
1619
  return ref.def ?? null;
1304
1620
  }
1305
- var TypeShapeSchema = import_zod12.z.lazy(
1306
- () => import_zod12.z.discriminatedUnion("type", [
1307
- import_zod12.z.object({
1308
- type: import_zod12.z.literal("record"),
1309
- properties: import_zod12.z.record(import_zod12.z.string(), TypeShapeSchema),
1310
- spreads: import_zod12.z.array(import_zod12.z.object({ sourceText: import_zod12.z.string() })).optional()
1621
+ var TypeShapeSchema = import_zod13.z.lazy(
1622
+ () => import_zod13.z.discriminatedUnion("type", [
1623
+ import_zod13.z.object({
1624
+ type: import_zod13.z.literal("record"),
1625
+ properties: import_zod13.z.record(import_zod13.z.string(), TypeShapeSchema),
1626
+ spreads: import_zod13.z.array(import_zod13.z.object({ sourceText: import_zod13.z.string() })).optional()
1311
1627
  }),
1312
- import_zod12.z.object({ type: import_zod12.z.literal("dictionary"), values: TypeShapeSchema }),
1313
- import_zod12.z.object({ type: import_zod12.z.literal("array"), items: TypeShapeSchema }),
1314
- import_zod12.z.object({
1315
- type: import_zod12.z.literal("literal"),
1316
- value: import_zod12.z.union([import_zod12.z.string(), import_zod12.z.number(), import_zod12.z.boolean()]),
1317
- raw: import_zod12.z.string().optional()
1628
+ import_zod13.z.object({ type: import_zod13.z.literal("dictionary"), values: TypeShapeSchema }),
1629
+ import_zod13.z.object({ type: import_zod13.z.literal("array"), items: TypeShapeSchema }),
1630
+ import_zod13.z.object({
1631
+ type: import_zod13.z.literal("literal"),
1632
+ value: import_zod13.z.union([import_zod13.z.string(), import_zod13.z.number(), import_zod13.z.boolean()]),
1633
+ raw: import_zod13.z.string().optional()
1318
1634
  }),
1319
- import_zod12.z.object({ type: import_zod12.z.literal("text") }),
1320
- import_zod12.z.object({ type: import_zod12.z.literal("integer") }),
1321
- import_zod12.z.object({ type: import_zod12.z.literal("number") }),
1322
- import_zod12.z.object({ type: import_zod12.z.literal("boolean") }),
1323
- import_zod12.z.object({ type: import_zod12.z.literal("null") }),
1324
- import_zod12.z.object({ type: import_zod12.z.literal("undefined") }),
1325
- import_zod12.z.object({
1326
- type: import_zod12.z.literal("union"),
1327
- variants: import_zod12.z.array(TypeShapeSchema)
1635
+ import_zod13.z.object({ type: import_zod13.z.literal("text") }),
1636
+ import_zod13.z.object({ type: import_zod13.z.literal("integer") }),
1637
+ import_zod13.z.object({ type: import_zod13.z.literal("number") }),
1638
+ import_zod13.z.object({ type: import_zod13.z.literal("boolean") }),
1639
+ import_zod13.z.object({ type: import_zod13.z.literal("null") }),
1640
+ import_zod13.z.object({ type: import_zod13.z.literal("undefined") }),
1641
+ import_zod13.z.object({
1642
+ type: import_zod13.z.literal("union"),
1643
+ variants: import_zod13.z.array(TypeShapeSchema)
1328
1644
  }),
1329
- import_zod12.z.object({
1330
- type: import_zod12.z.literal("ref"),
1331
- name: import_zod12.z.string(),
1332
- from: import_zod12.z.string().optional(),
1333
- def: import_zod12.z.string().optional()
1645
+ import_zod13.z.object({
1646
+ type: import_zod13.z.literal("ref"),
1647
+ name: import_zod13.z.string(),
1648
+ from: import_zod13.z.string().optional(),
1649
+ def: import_zod13.z.string().optional()
1334
1650
  }),
1335
- import_zod12.z.object({ type: import_zod12.z.literal("unknown") })
1651
+ import_zod13.z.object({ type: import_zod13.z.literal("unknown") })
1336
1652
  ])
1337
1653
  );
1338
1654
  var MAX_DEFINITION_DEPTH = 6;
@@ -1412,6 +1728,13 @@ function withRewrittenPaths(binding, rewrite) {
1412
1728
  semantics: behavior.rewritePaths(binding.semantics, rewrite)
1413
1729
  };
1414
1730
  }
1731
+ function withoutOwnHost(binding, ownHosts) {
1732
+ const semantics = behaviorOf(binding.semantics).withoutOwnHost?.(
1733
+ binding.semantics,
1734
+ ownHosts
1735
+ );
1736
+ return semantics === void 0 || semantics === null ? binding : { ...binding, semantics };
1737
+ }
1415
1738
  function groundedPairingKey(binding, deployment) {
1416
1739
  return pairingKey(groundBinding(binding, deployment));
1417
1740
  }
@@ -1427,6 +1750,10 @@ function pairingKey(binding) {
1427
1750
  const behavior = behaviorOf(binding.semantics);
1428
1751
  return (behavior.pairingKey ?? behavior.identityKey)(binding.semantics);
1429
1752
  }
1753
+ function operationKey(binding) {
1754
+ const behavior = behaviorOf(binding.semantics);
1755
+ return (behavior.operationKey ?? behavior.identityKey)(binding.semantics);
1756
+ }
1430
1757
  function semanticsAgree(a, b) {
1431
1758
  if (a.name !== b.name) {
1432
1759
  return false;
@@ -1444,8 +1771,12 @@ function bucketsMeet(a, b) {
1444
1771
  const meet = behaviorOf(a.semantics).bucketsMeet;
1445
1772
  return meet !== void 0 && meet(a.semantics, b.semantics);
1446
1773
  }
1447
- function bucketRank(binding) {
1448
- return behaviorOf(binding.semantics).bucketRank?.(binding.semantics) ?? [];
1774
+ function pairRank(provider, consumer) {
1775
+ if (provider.semantics.name !== consumer.semantics.name) {
1776
+ return null;
1777
+ }
1778
+ const rank = behaviorOf(provider.semantics).pairRank;
1779
+ return rank === void 0 ? [] : rank(provider.semantics, consumer.semantics);
1449
1780
  }
1450
1781
  function boundaryLabel(binding) {
1451
1782
  const behavior = behaviorOf(binding.semantics);
@@ -1454,6 +1785,9 @@ function boundaryLabel(binding) {
1454
1785
  function displayLabel(binding) {
1455
1786
  return boundaryLabel(binding) ?? `${binding.semantics.name}:${binding.recognition}`;
1456
1787
  }
1788
+ function labelWithDetail(binding, detail) {
1789
+ return `${boundaryLabel(binding) ?? binding.semantics.name} ${detail}`;
1790
+ }
1457
1791
  function normalizeRuleBoundary(raw) {
1458
1792
  const trimmed = raw.trim();
1459
1793
  for (const behavior of allBehaviors()) {
@@ -1617,8 +1951,8 @@ var PLATFORM_INJECTED_ENV_VARS = {
1617
1951
  };
1618
1952
 
1619
1953
  // src/relation.ts
1620
- var import_zod13 = require("zod");
1621
- var RelationSchema = import_zod13.z.enum([
1954
+ var import_zod14 = require("zod");
1955
+ var RelationSchema = import_zod14.z.enum([
1622
1956
  "provides",
1623
1957
  "reads",
1624
1958
  "writes",
@@ -1627,71 +1961,78 @@ var RelationSchema = import_zod13.z.enum([
1627
1961
  var EffectRelationSchema = RelationSchema.exclude(["provides"]);
1628
1962
 
1629
1963
  // src/suppressions.ts
1630
- var import_zod14 = require("zod");
1631
- var SuppressionSideSchema = import_zod14.z.object({
1632
- summary: import_zod14.z.string().optional(),
1633
- transitionId: import_zod14.z.string().optional()
1964
+ var import_zod15 = require("zod");
1965
+ var SuppressionSideSchema = import_zod15.z.object({
1966
+ summary: import_zod15.z.string().optional(),
1967
+ transitionId: import_zod15.z.string().optional()
1634
1968
  }).optional();
1635
- var SuppressionRuleSchema = import_zod14.z.object({
1969
+ var SuppressionRuleSchema = import_zod15.z.object({
1636
1970
  /**
1637
- * The finding kind to match, behavioural or intent. It is an open
1638
- * string here because this package is below both IRs, and the loader
1639
- * is what validates it against the published kind enums.
1971
+ * The finding kind to match, behavioral or intent. The schema accepts
1972
+ * any string because ir-core cannot import either IR's kind enum. The
1973
+ * CLI's loader checks it against both.
1640
1974
  */
1641
- kind: import_zod14.z.string().optional(),
1975
+ kind: import_zod15.z.string().optional(),
1642
1976
  /**
1643
- * Boundary as a human-readable key, e.g. "GET /pet/{petId}" or
1644
- * "fn:@acme/api::getUser". REST keys are normalized via the same
1645
- * path normalizer the checkers use, so `:id` and `{id}` compare
1646
- * equal.
1977
+ * The boundary's human-readable key, such as "GET /pet/{petId}" or
1978
+ * "fn:@acme/api::getUser". A REST key goes through the same path
1979
+ * normalizer the checkers use, so `:id` and `{id}` compare equal.
1647
1980
  */
1648
- boundary: import_zod14.z.string().optional(),
1981
+ boundary: import_zod15.z.string().optional(),
1649
1982
  /**
1650
- * Consumer-side discriminators, the narrowest useful match. These
1651
- * mean something only for behavioural findings: a rule that
1652
- * specifies `consumer` never matches an intent finding, because an
1653
- * intent finding has no consumer side.
1983
+ * Consumer-side discriminators, the narrowest useful match. They
1984
+ * apply only to behavioral findings. A rule that sets `consumer`
1985
+ * never matches an intent finding, because an intent finding has no
1986
+ * consumer side.
1654
1987
  */
1655
1988
  consumer: SuppressionSideSchema,
1656
1989
  /**
1657
- * Provider-side discriminators, the mirror of `consumer`. A finding
1658
- * about a status the provider produces has its transition id on this
1659
- * side, and that id is the only handle narrow enough to pick out
1660
- * that one finding. As with `consumer`, a rule that specifies
1661
- * `provider` never matches an intent finding.
1990
+ * Provider-side discriminators, matching the same fields as
1991
+ * `consumer`. A finding about a status the provider produces has its
1992
+ * transition id on this side, and that id is the only value narrow
1993
+ * enough to pick out that one finding. As with `consumer`, a rule
1994
+ * that sets `provider` never matches an intent finding.
1662
1995
  */
1663
1996
  provider: SuppressionSideSchema,
1664
1997
  /**
1665
- * "narrow", the default, requires kind plus one of boundary,
1666
- * consumer.transitionId, or provider.transitionId, which is enough
1667
- * to target a specific class of finding. "broad" opts in to
1668
- * kind-only or boundary-only matches, which also silence future
1669
- * regressions in that category.
1998
+ * The title of one PRD scenario. A PRD finding's boundary is the
1999
+ * whole PRD, so without this a rule accepts every scenario in it,
2000
+ * including one added later. A rule that sets `scenario` matches
2001
+ * only intent findings about a scenario with that title.
1670
2002
  */
1671
- scope: import_zod14.z.enum(["narrow", "broad"]).default("narrow"),
2003
+ scenario: import_zod15.z.string().optional(),
2004
+ /**
2005
+ * "narrow", the default, requires `kind` plus one of `boundary`,
2006
+ * `scenario`, `consumer.transitionId` or `provider.transitionId`, so
2007
+ * the rule targets a specific class of finding. "broad" allows a
2008
+ * match on kind alone or boundary alone, and such a rule also
2009
+ * silences future regressions in that category.
2010
+ */
2011
+ scope: import_zod15.z.enum(["narrow", "broad"]).default("narrow"),
1672
2012
  /** Required human-written justification. */
1673
- reason: import_zod14.z.string().min(1),
2013
+ reason: import_zod15.z.string().min(1),
1674
2014
  /** What to do when a finding matches. */
1675
- effect: import_zod14.z.enum(["mark", "downgrade", "hide"]).default("mark")
2015
+ effect: import_zod15.z.enum(["mark", "downgrade", "hide"]).default("mark")
1676
2016
  }).strict();
1677
- var SuppressionFileSchema = import_zod14.z.object({
1678
- version: import_zod14.z.literal(1),
1679
- rules: import_zod14.z.array(SuppressionRuleSchema)
2017
+ var SuppressionFileSchema = import_zod15.z.object({
2018
+ version: import_zod15.z.literal(1),
2019
+ rules: import_zod15.z.array(SuppressionRuleSchema)
1680
2020
  }).strict();
1681
2021
  function validateRule(rule) {
1682
2022
  if (rule.scope === "broad") {
1683
- if (rule.kind === void 0 && rule.boundary === void 0 && rule.consumer === void 0 && rule.provider === void 0) {
1684
- return "broad-scope rule must constrain at least one field (kind, boundary, consumer, or provider)";
2023
+ if (rule.kind === void 0 && rule.boundary === void 0 && rule.scenario === void 0 && rule.consumer === void 0 && rule.provider === void 0) {
2024
+ return "broad-scope rule must constrain at least one field (kind, boundary, scenario, consumer, or provider)";
1685
2025
  }
1686
2026
  return null;
1687
2027
  }
1688
2028
  const hasKind = rule.kind !== void 0;
1689
2029
  const hasBoundary = rule.boundary !== void 0;
2030
+ const hasScenario = rule.scenario !== void 0;
1690
2031
  const hasTransition = rule.consumer?.transitionId !== void 0 || rule.provider?.transitionId !== void 0;
1691
- if (hasKind && (hasBoundary || hasTransition)) {
2032
+ if (hasKind && (hasBoundary || hasScenario || hasTransition)) {
1692
2033
  return null;
1693
2034
  }
1694
- return "narrow-scope rule must specify kind AND (boundary OR consumer.transitionId OR provider.transitionId); set scope: 'broad' to silence wider categories";
2035
+ return "narrow-scope rule must specify kind AND (boundary OR scenario OR consumer.transitionId OR provider.transitionId); set scope: 'broad' to silence wider categories";
1695
2036
  }
1696
2037
  function ruleBoundaryMatchesKey(ruleBoundary, key) {
1697
2038
  if (key === null) {
@@ -1743,6 +2084,12 @@ function countsForThreshold(finding) {
1743
2084
  return finding.suppressed.effect === "downgrade";
1744
2085
  }
1745
2086
 
2087
+ // src/testName.ts
2088
+ var TEST_TITLE_SEPARATOR = " > ";
2089
+ function testUnitName(titles) {
2090
+ return titles.join(TEST_TITLE_SEPARATOR);
2091
+ }
2092
+
1746
2093
  // src/typeShapeMatch.ts
1747
2094
  function bodyShapesMatch(actual, declared) {
1748
2095
  if (actual.type === "unknown" || declared.type === "unknown") {
@@ -1904,7 +2251,9 @@ function restBinding(opts) {
1904
2251
  name: "rest",
1905
2252
  method: method === null ? null : method.toUpperCase(),
1906
2253
  path: namedOrNull(opts.path, "rest path"),
1907
- ...opts.declaredResponses !== void 0 ? { declaredResponses: opts.declaredResponses } : {}
2254
+ ...opts.declaredResponses !== void 0 ? { declaredResponses: opts.declaredResponses } : {},
2255
+ ...opts.host !== void 0 && opts.host !== "" ? { host: opts.host } : {},
2256
+ ...opts.optionalFormat === true ? { optionalFormat: true } : {}
1908
2257
  },
1909
2258
  recognition: opts.recognition
1910
2259
  };
@@ -1930,6 +2279,14 @@ function packageExportBinding(opts) {
1930
2279
  exportPath: opts.exportPath
1931
2280
  });
1932
2281
  }
2282
+ function moduleExportBinding(opts) {
2283
+ return functionCallBinding({
2284
+ transport: "in-process",
2285
+ recognition: opts.recognition,
2286
+ module: opts.module,
2287
+ exportName: opts.exportName
2288
+ });
2289
+ }
1933
2290
  function graphqlResolverBinding(opts) {
1934
2291
  return {
1935
2292
  transport: opts.transport,
@@ -2012,6 +2369,13 @@ function metricBinding(opts) {
2012
2369
  recognition: opts.recognition
2013
2370
  };
2014
2371
  }
2372
+ function ioBinding(opts) {
2373
+ return {
2374
+ transport: "in-process",
2375
+ semantics: { name: "io", target: namedOrNull(opts.target, "io target") },
2376
+ recognition: opts.recognition
2377
+ };
2378
+ }
2015
2379
  // Annotate the CommonJS export names for ESM import in node:
2016
2380
  0 && (module.exports = {
2017
2381
  BoundaryBindingSchema,
@@ -2025,6 +2389,7 @@ function metricBinding(opts) {
2025
2389
  FunctionCallSemanticsSchema,
2026
2390
  GraphqlOperationSemanticsSchema,
2027
2391
  GraphqlResolverSemanticsSchema,
2392
+ IoSemanticsSchema,
2028
2393
  MessageBusSemanticsSchema,
2029
2394
  MetricSemanticsSchema,
2030
2395
  NOTHING_DEPLOYED,
@@ -2037,6 +2402,7 @@ function metricBinding(opts) {
2037
2402
  StorageSemanticsSchema,
2038
2403
  SuppressionFileSchema,
2039
2404
  SuppressionRuleSchema,
2405
+ TEST_TITLE_SEPARATOR,
2040
2406
  TypeShapeSchema,
2041
2407
  UnitInvocationSemanticsSchema,
2042
2408
  applySuppressionsToFindings,
@@ -2046,7 +2412,6 @@ function metricBinding(opts) {
2046
2412
  boundaryKey,
2047
2413
  boundaryLabel,
2048
2414
  boundaryNameString,
2049
- bucketRank,
2050
2415
  bucketsMeet,
2051
2416
  busIdentityKey,
2052
2417
  busesAgree,
@@ -2070,12 +2435,18 @@ function metricBinding(opts) {
2070
2435
  groundBinding,
2071
2436
  groundedPairingKey,
2072
2437
  hasNameHole,
2438
+ hostCanBeOwn,
2439
+ ioBinding,
2440
+ ioIdentityKey,
2073
2441
  isGraphqlOperationBinding,
2442
+ isModuleName,
2443
+ labelWithDetail,
2074
2444
  leavesTheProcess,
2075
2445
  messageBusBinding,
2076
2446
  methodsAgree,
2077
2447
  metricBinding,
2078
2448
  metricIdentityKey,
2449
+ moduleExportBinding,
2079
2450
  namePatternFromSub,
2080
2451
  namePatternKey,
2081
2452
  nameReference,
@@ -2085,7 +2456,9 @@ function metricBinding(opts) {
2085
2456
  namesNothing,
2086
2457
  normalizePath,
2087
2458
  normalizeRuleBoundary,
2459
+ operationKey,
2088
2460
  packageExportBinding,
2461
+ pairRank,
2089
2462
  pairingKey,
2090
2463
  parseBoundaryName,
2091
2464
  parseChannel,
@@ -2115,11 +2488,13 @@ function metricBinding(opts) {
2115
2488
  storageContainerLabel,
2116
2489
  storageLabel,
2117
2490
  storageSystemLabel,
2491
+ testUnitName,
2118
2492
  typeDefinitionKey,
2119
2493
  unitIdentityKey,
2120
2494
  unitInvocationBinding,
2121
2495
  validateRule,
2122
2496
  withDefinitionsInlined,
2123
2497
  withRewrittenPaths,
2124
- withinScope
2498
+ withinScope,
2499
+ withoutOwnHost
2125
2500
  });