@suss/extractor 0.3.2 → 0.5.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.cjs CHANGED
@@ -20,17 +20,33 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ IdMap: () => IdMap,
24
+ IdSet: () => IdSet,
25
+ PathBudgetExceeded: () => PathBudgetExceeded,
26
+ UnmodeledFlow: () => UnmodeledFlow,
27
+ absentReading: () => absentReading,
28
+ ambiguousReading: () => ambiguousReading,
29
+ andThenReading: () => andThenReading,
23
30
  assembleSummary: () => assembleSummary,
24
31
  assessConfidence: () => assessConfidence,
25
32
  detectGaps: () => detectGaps,
26
33
  effectToIR: () => effectToIR,
34
+ enumerateOrDegrade: () => enumerateOrDegrade,
35
+ enumerateStructuredPaths: () => enumerateStructuredPaths,
36
+ firstWrittenReading: () => firstWrittenReading,
27
37
  httpRouteDiscovery: () => httpRouteDiscovery,
28
38
  makeTransitionId: () => makeTransitionId,
39
+ mapReading: () => mapReading,
29
40
  paramToInput: () => paramToInput,
30
- terminalToOutput: () => terminalToOutput
41
+ sharedGatingConditions: () => sharedGatingConditions,
42
+ terminalToOutput: () => terminalToOutput,
43
+ unreadableReading: () => unreadableReading,
44
+ valueToReadFurtherFrom: () => valueToReadFurtherFrom,
45
+ writtenReading: () => writtenReading
31
46
  });
32
47
  module.exports = __toCommonJS(index_exports);
33
48
  var import_node_crypto = require("crypto");
49
+ var import_behavioral_ir = require("@suss/behavioral-ir");
34
50
 
35
51
  // src/inputReads.ts
36
52
  function inputReadsOf(args) {
@@ -124,13 +140,525 @@ function httpRouteDiscovery(opts) {
124
140
  registrationChain: [...opts.methods]
125
141
  },
126
142
  bindingExtraction: {
127
- method: { type: "fromRegistration", position: "methodName" },
143
+ // `.all` registers every method, so it records "*", which the pairing
144
+ // engine treats as agreeing with any method at all.
145
+ method: {
146
+ type: "fromRegistration",
147
+ position: "methodName",
148
+ nameMap: { all: "*" }
149
+ },
128
150
  path: { type: "fromRegistration", position: 0 }
129
151
  },
152
+ ...opts.mount !== void 0 ? { mount: opts.mount } : {},
130
153
  requiresImport: [opts.importModule]
131
154
  }));
132
155
  }
133
156
 
157
+ // src/paths/enumeratePaths.ts
158
+ var MAX_PATHS = 256;
159
+ var UnmodeledFlow = class extends Error {
160
+ };
161
+ var PathBudgetExceeded = class extends Error {
162
+ };
163
+ function branchCond(branchStmt, condition, polarity, oppositeExit) {
164
+ return {
165
+ info: {
166
+ sourceText: condition.sourceText,
167
+ polarity,
168
+ source: "explicit",
169
+ expression: condition.expression
170
+ },
171
+ branchStmt,
172
+ oppositeExit
173
+ };
174
+ }
175
+ function fixedCond(condition, polarity, source) {
176
+ return {
177
+ info: {
178
+ sourceText: condition.sourceText,
179
+ polarity,
180
+ source,
181
+ expression: condition.expression
182
+ },
183
+ branchStmt: null,
184
+ oppositeExit: null
185
+ };
186
+ }
187
+ var loopIterationCond = (headerText) => fixedCond(
188
+ { sourceText: `some iteration of: ${headerText}`, expression: null },
189
+ "positive",
190
+ "explicit"
191
+ );
192
+ var loopCompletedCond = (headerText, exit) => fixedCond(
193
+ {
194
+ sourceText: `loop exited via ${exit ?? "return"}: ${headerText}`,
195
+ expression: null
196
+ },
197
+ "negative",
198
+ exit === "throw" ? "earlyThrow" : "earlyReturn"
199
+ );
200
+ var catchEntryCond = () => fixedCond(
201
+ { sourceText: "catch", expression: null },
202
+ "positive",
203
+ "catchBlock"
204
+ );
205
+ function classify(path, home, isAncestorOrSelf) {
206
+ return path.map((cond) => {
207
+ if (cond.branchStmt === null) {
208
+ return cond.info;
209
+ }
210
+ const encloses = home !== null && isAncestorOrSelf(cond.branchStmt, home);
211
+ if (encloses || cond.info.polarity === "positive") {
212
+ return { ...cond.info, source: "explicit" };
213
+ }
214
+ const source = cond.oppositeExit === "throw" ? "earlyThrow" : "earlyReturn";
215
+ return { ...cond.info, source };
216
+ });
217
+ }
218
+ function childrenOf(stmt) {
219
+ if (stmt.kind === "if") {
220
+ return stmt.elseBody === null ? stmt.thenBody : [...stmt.thenBody, ...stmt.elseBody];
221
+ }
222
+ if (stmt.kind === "switch") {
223
+ return stmt.groups.flatMap((g) => g.body);
224
+ }
225
+ if (stmt.kind === "loop") {
226
+ return stmt.body;
227
+ }
228
+ if (stmt.kind === "try") {
229
+ return [
230
+ ...stmt.tryBody,
231
+ ...stmt.catchBody ?? [],
232
+ ...stmt.finallyBody ?? []
233
+ ];
234
+ }
235
+ return [];
236
+ }
237
+ function hasStrayBreak(nodes) {
238
+ return nodes.some((node) => {
239
+ if (node.kind === "exit" && node.exit === "break") {
240
+ return true;
241
+ }
242
+ if (node.kind === "switch" || node.kind === "loop") {
243
+ return false;
244
+ }
245
+ return hasStrayBreak(childrenOf(node));
246
+ });
247
+ }
248
+ function exitKindOfList(stmts) {
249
+ let sawReturn = false;
250
+ for (const stmt of stmts) {
251
+ if (stmt.exitKind === "throw") {
252
+ return "throw";
253
+ }
254
+ if (stmt.exitKind === "return") {
255
+ sawReturn = true;
256
+ }
257
+ }
258
+ return sawReturn ? "return" : null;
259
+ }
260
+ function chargeBudget(state) {
261
+ state.pathCount++;
262
+ if (state.pathCount > MAX_PATHS) {
263
+ throw new PathBudgetExceeded(
264
+ `path budget exceeded, more than ${MAX_PATHS} paths`
265
+ );
266
+ }
267
+ }
268
+ function recordTerminal(ctx, terminal, home, path) {
269
+ chargeBudget(ctx.state);
270
+ const info = classify(path, home, ctx.isAncestorOrSelf);
271
+ const existing = ctx.state.byTerminal.get(terminal) ?? [];
272
+ existing.push(info);
273
+ ctx.state.byTerminal.set(terminal, existing);
274
+ }
275
+ function containsTerminal(ctx, stmt) {
276
+ if ((ctx.terminalsByStmt.get(stmt)?.length ?? 0) > 0) {
277
+ return true;
278
+ }
279
+ return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
280
+ }
281
+ function stepIf(stmt, path, ctx) {
282
+ const armsExit = stmt.exitKind !== null;
283
+ const armsHaveTerminals = containsTerminal(ctx, stmt);
284
+ if (!armsExit && !armsHaveTerminals) {
285
+ return [path];
286
+ }
287
+ const thenExit = exitKindOfList(stmt.thenBody);
288
+ const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
289
+ const thenPath = [
290
+ ...path,
291
+ branchCond(stmt, stmt.condition, "positive", elseExit)
292
+ ];
293
+ const elsePath = [
294
+ ...path,
295
+ branchCond(stmt, stmt.condition, "negative", thenExit)
296
+ ];
297
+ const out = [];
298
+ out.push(...enumerate(ctx, stmt.thenBody, thenPath));
299
+ if (stmt.elseBody !== null) {
300
+ out.push(...enumerate(ctx, stmt.elseBody, elsePath));
301
+ } else {
302
+ out.push(elsePath);
303
+ }
304
+ return out;
305
+ }
306
+ function stepSwitch(stmt, path, ctx) {
307
+ const out = [];
308
+ const negations = [];
309
+ let defaultRanBody = false;
310
+ for (let i = 0; i < stmt.groups.length; i++) {
311
+ const group = stmt.groups[i];
312
+ const fallthroughSafe = i === stmt.groups.length - 1;
313
+ if (hasStrayBreak(group.body)) {
314
+ throw new UnmodeledFlow("non-trailing break in switch clause");
315
+ }
316
+ const groupPath = group.condition === null ? [...path, ...negations] : [...path, fixedCond(group.condition, "positive", "explicit")];
317
+ const conts = enumerate(ctx, group.body, groupPath);
318
+ if (conts.length > 0) {
319
+ if (!group.hasTrailingBreak && !fallthroughSafe) {
320
+ throw new UnmodeledFlow("fallthrough into a non-empty switch clause");
321
+ }
322
+ out.push(...conts);
323
+ }
324
+ if (group.condition === null) {
325
+ defaultRanBody = true;
326
+ } else {
327
+ const exit = exitKindOfList(group.body);
328
+ const negationSource = exit === "throw" ? "earlyThrow" : exit === "return" ? "earlyReturn" : "explicit";
329
+ negations.push(fixedCond(group.condition, "negative", negationSource));
330
+ }
331
+ }
332
+ if (!defaultRanBody) {
333
+ out.push([...path, ...negations]);
334
+ }
335
+ return out;
336
+ }
337
+ function stepLoop(stmt, path, ctx) {
338
+ const bodyExit = exitKindOfList(stmt.body);
339
+ if (containsTerminal(ctx, stmt)) {
340
+ enumerate(ctx, stmt.body, [
341
+ ...path,
342
+ loopIterationCond(stmt.condition.sourceText)
343
+ ]);
344
+ }
345
+ if (bodyExit !== null) {
346
+ return [[...path, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
347
+ }
348
+ return [path];
349
+ }
350
+ function stepTry(stmt, path, ctx) {
351
+ if (stmt.finallyBody !== null) {
352
+ const finallyHasTerminal = stmt.finallyBody.some(
353
+ (s) => containsTerminal(ctx, s)
354
+ );
355
+ if (exitKindOfList(stmt.finallyBody) !== null || finallyHasTerminal) {
356
+ throw new UnmodeledFlow("finally block with exits or terminals");
357
+ }
358
+ }
359
+ const out = [];
360
+ out.push(...enumerate(ctx, stmt.tryBody, path));
361
+ if (stmt.catchBody !== null) {
362
+ const catchPath = [...path, catchEntryCond()];
363
+ out.push(...enumerate(ctx, stmt.catchBody, catchPath));
364
+ }
365
+ return out;
366
+ }
367
+ function stepExit(_stmt, _path, _ctx) {
368
+ return [];
369
+ }
370
+ function stepOpaque(_stmt, path, _ctx) {
371
+ return [path];
372
+ }
373
+ var stepHandlers = {
374
+ if: stepIf,
375
+ switch: stepSwitch,
376
+ loop: stepLoop,
377
+ try: stepTry,
378
+ exit: stepExit,
379
+ opaque: stepOpaque
380
+ };
381
+ function stepStatement(ctx, stmt, path) {
382
+ const ownTerminals = ctx.terminalsByStmt.get(stmt) ?? [];
383
+ for (const terminal of ownTerminals) {
384
+ recordTerminal(ctx, terminal, stmt, path);
385
+ }
386
+ const handler = stepHandlers[stmt.kind];
387
+ return handler(
388
+ stmt,
389
+ path,
390
+ ctx
391
+ );
392
+ }
393
+ function soleDisagreement(left, right) {
394
+ if (left.length !== right.length) {
395
+ return null;
396
+ }
397
+ let found = null;
398
+ for (let at = 0; at < left.length; at++) {
399
+ const one = left[at];
400
+ const other = right[at];
401
+ if (one === other) {
402
+ continue;
403
+ }
404
+ if (found !== null) {
405
+ return null;
406
+ }
407
+ if (one === void 0 || other === void 0 || one.branchStmt === null || one.branchStmt !== other.branchStmt || one.info.polarity === other.info.polarity) {
408
+ return null;
409
+ }
410
+ found = at;
411
+ }
412
+ return found;
413
+ }
414
+ function mergeRejoined(paths) {
415
+ let current = paths;
416
+ for (let round = 0; round < current.length; round++) {
417
+ const out = [];
418
+ const taken = /* @__PURE__ */ new Set();
419
+ let mergedAny = false;
420
+ for (let i = 0; i < current.length; i++) {
421
+ if (taken.has(i)) {
422
+ continue;
423
+ }
424
+ let combined = current[i];
425
+ for (let j = i + 1; j < current.length; j++) {
426
+ if (taken.has(j)) {
427
+ continue;
428
+ }
429
+ const at = soleDisagreement(combined, current[j]);
430
+ if (at === null) {
431
+ continue;
432
+ }
433
+ combined = [...combined.slice(0, at), ...combined.slice(at + 1)];
434
+ taken.add(j);
435
+ mergedAny = true;
436
+ break;
437
+ }
438
+ out.push(combined);
439
+ }
440
+ current = out;
441
+ if (!mergedAny) {
442
+ break;
443
+ }
444
+ }
445
+ return current;
446
+ }
447
+ function enumerate(ctx, stmts, prefix) {
448
+ let frontiers = [[...prefix]];
449
+ for (const stmt of stmts) {
450
+ const nextFrontiers = [];
451
+ for (const path of frontiers) {
452
+ nextFrontiers.push(...stepStatement(ctx, stmt, path));
453
+ }
454
+ frontiers = mergeRejoined(nextFrontiers);
455
+ if (frontiers.length > MAX_PATHS) {
456
+ throw new PathBudgetExceeded(
457
+ `path budget exceeded, more than ${MAX_PATHS} paths`
458
+ );
459
+ }
460
+ if (frontiers.length === 0) {
461
+ break;
462
+ }
463
+ }
464
+ return frontiers;
465
+ }
466
+ function buildParentMap(statements) {
467
+ const parentOf = /* @__PURE__ */ new Map();
468
+ const visit = (stmt, parent) => {
469
+ parentOf.set(stmt, parent);
470
+ for (const child of childrenOf(stmt)) {
471
+ visit(child, stmt);
472
+ }
473
+ };
474
+ for (const stmt of statements) {
475
+ visit(stmt, null);
476
+ }
477
+ return parentOf;
478
+ }
479
+ function enumerateStructuredPaths(input) {
480
+ const parentOf = buildParentMap(input.statements);
481
+ const isAncestorOrSelf = (a, b) => {
482
+ let current = b;
483
+ while (current !== null) {
484
+ if (current === a) {
485
+ return true;
486
+ }
487
+ current = parentOf.get(current) ?? null;
488
+ }
489
+ return false;
490
+ };
491
+ const state = {
492
+ byTerminal: /* @__PURE__ */ new Map(),
493
+ fallthrough: [],
494
+ pathCount: 0
495
+ };
496
+ const ctx = {
497
+ terminalsByStmt: input.terminalsByStmt,
498
+ state,
499
+ isAncestorOrSelf
500
+ };
501
+ const continuations = enumerate(ctx, input.statements, []);
502
+ for (const path of continuations) {
503
+ chargeBudget(state);
504
+ state.fallthrough.push(classify(path, null, isAncestorOrSelf));
505
+ }
506
+ return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
507
+ }
508
+ function enumerateOrDegrade(input, terminals) {
509
+ try {
510
+ return { ...enumerateStructuredPaths(input), degraded: null };
511
+ } catch (error) {
512
+ const reason = reasonToDegrade(error);
513
+ if (reason === null) {
514
+ throw error;
515
+ }
516
+ const marker = {
517
+ sourceText: `unmodeled control flow (${reason})`,
518
+ polarity: "positive",
519
+ source: "explicit",
520
+ expression: null
521
+ };
522
+ const byTerminal = /* @__PURE__ */ new Map();
523
+ for (const terminal of terminals) {
524
+ byTerminal.set(terminal, [[marker]]);
525
+ }
526
+ return { byTerminal, fallthrough: [[marker]], degraded: reason };
527
+ }
528
+ }
529
+ function reasonToDegrade(error) {
530
+ if (error instanceof PathBudgetExceeded) {
531
+ return error.message === "" ? "path budget exceeded" : error.message;
532
+ }
533
+ if (error instanceof UnmodeledFlow) {
534
+ return error.message;
535
+ }
536
+ return null;
537
+ }
538
+
539
+ // src/paths/gatingConditions.ts
540
+ var keyOf = (condition) => `${condition.polarity}:${condition.sourceText}`;
541
+ function sharedGatingConditions(paths, readPredicate) {
542
+ const [first, ...rest] = paths ?? [];
543
+ if (first === void 0) {
544
+ return [];
545
+ }
546
+ const shared = new Set(first.map(keyOf));
547
+ for (const path of rest) {
548
+ const here = new Set(path.map(keyOf));
549
+ for (const value of shared) {
550
+ if (!here.has(value)) {
551
+ shared.delete(value);
552
+ }
553
+ }
554
+ }
555
+ return first.filter((condition) => shared.has(keyOf(condition))).map((condition) => ({
556
+ sourceText: condition.sourceText,
557
+ structured: readPredicate === void 0 || condition.expression === null ? null : readPredicate(condition.expression),
558
+ polarity: condition.polarity,
559
+ source: condition.source
560
+ }));
561
+ }
562
+
563
+ // src/paths/nodeIdentity.ts
564
+ var IdSet = class {
565
+ byKey = /* @__PURE__ */ new Map();
566
+ constructor(nodes = []) {
567
+ for (const node of nodes) {
568
+ this.add(node);
569
+ }
570
+ }
571
+ add(node) {
572
+ this.byKey.set(node.id, node);
573
+ return this;
574
+ }
575
+ has(node) {
576
+ return this.byKey.has(node.id);
577
+ }
578
+ /** The node this set was built with, which is the caller's own handle for it. */
579
+ get(node) {
580
+ return this.byKey.get(node.id);
581
+ }
582
+ get size() {
583
+ return this.byKey.size;
584
+ }
585
+ [Symbol.iterator]() {
586
+ return this.byKey.values();
587
+ }
588
+ };
589
+ var IdMap = class {
590
+ entries = /* @__PURE__ */ new Map();
591
+ set(node, value) {
592
+ this.entries.set(node.id, [node, value]);
593
+ return this;
594
+ }
595
+ get(node) {
596
+ return this.entries.get(node.id)?.[1];
597
+ }
598
+ has(node) {
599
+ return this.entries.has(node.id);
600
+ }
601
+ get size() {
602
+ return this.entries.size;
603
+ }
604
+ [Symbol.iterator]() {
605
+ return this.entries.values();
606
+ }
607
+ };
608
+
609
+ // src/reading.ts
610
+ function writtenReading(value, range) {
611
+ return { kind: "written", value, range };
612
+ }
613
+ var absentReading = { kind: "absent" };
614
+ function unreadableReading(reason, range) {
615
+ return { kind: "unreadable", reason, range };
616
+ }
617
+ function ambiguousReading(candidates, reason, range) {
618
+ return { kind: "ambiguous", candidates, reason, range };
619
+ }
620
+ function dispatchReading(table, reading) {
621
+ return table[reading.kind](reading);
622
+ }
623
+ function mapReading(reading, f) {
624
+ const table = {
625
+ written: (r) => writtenReading(f(r.value), r.range),
626
+ absent: () => absentReading,
627
+ unreadable: (r) => unreadableReading(r.reason, r.range),
628
+ ambiguous: (r) => ambiguousReading(r.candidates.map(f), r.reason, r.range)
629
+ };
630
+ return dispatchReading(table, reading);
631
+ }
632
+ function andThenReading(reading, f) {
633
+ const table = {
634
+ written: (r) => f(r.value, r.range),
635
+ absent: () => absentReading,
636
+ unreadable: (r) => unreadableReading(r.reason, r.range),
637
+ ambiguous: (r) => ambiguousReading(
638
+ r.candidates.flatMap((candidate) => {
639
+ const next = f(candidate, r.range);
640
+ return next.kind === "written" ? [next.value] : [];
641
+ }),
642
+ r.reason,
643
+ r.range
644
+ )
645
+ };
646
+ return dispatchReading(table, reading);
647
+ }
648
+ function firstWrittenReading(readings) {
649
+ const written = readings.find((reading) => reading.kind === "written");
650
+ const chosen = written ?? readings.find((reading) => reading.kind !== "absent") ?? absentReading;
651
+ return {
652
+ reading: chosen,
653
+ passedOver: readings.filter(
654
+ (reading) => reading !== chosen && (reading.kind === "unreadable" || reading.kind === "ambiguous")
655
+ )
656
+ };
657
+ }
658
+ function valueToReadFurtherFrom(reading) {
659
+ return reading.kind === "written" ? reading.value : null;
660
+ }
661
+
134
662
  // src/index.ts
135
663
  function valuesOfOutput(output) {
136
664
  if (output.type === "response" && output.statusCode !== null) {
@@ -146,8 +674,69 @@ function makeTransitionId(functionName, branch) {
146
674
  const conditionHash = (0, import_node_crypto.createHash)("sha1").update(conditionSig).digest("hex").slice(0, 7);
147
675
  return `${functionName}:${terminal.kind}:${statusKey}:${conditionHash}`;
148
676
  }
677
+ function collapseReading(table, reading) {
678
+ return table[reading.kind](reading);
679
+ }
680
+ function claimedValue(defaulted) {
681
+ const table = {
682
+ written: (r) => r.value,
683
+ absent: () => defaulted.libraryDefault ?? null,
684
+ unreadable: () => null,
685
+ ambiguous: () => null
686
+ };
687
+ return collapseReading(table, defaulted.reading);
688
+ }
689
+ function unreadReasonOf(reading) {
690
+ const table = {
691
+ written: () => null,
692
+ absent: () => null,
693
+ unreadable: (r) => r.reason,
694
+ ambiguous: (r) => r.reason
695
+ };
696
+ return collapseReading(table, reading);
697
+ }
698
+ function bodyWithClaimedShape(terminal, reading) {
699
+ return {
700
+ typeText: terminal.body?.typeText ?? null,
701
+ shape: claimedValue(reading)
702
+ };
703
+ }
704
+ function branchWithCollapsedReadings(branch) {
705
+ const { statusCodeReading, bodyShapeReading } = branch;
706
+ if (statusCodeReading === void 0 && bodyShapeReading === void 0) {
707
+ return branch;
708
+ }
709
+ const status = statusCodeReading !== void 0 ? claimedValue(statusCodeReading) : null;
710
+ return {
711
+ ...branch,
712
+ terminal: {
713
+ ...branch.terminal,
714
+ ...statusCodeReading !== void 0 ? {
715
+ statusCode: status !== null ? { type: "literal", value: status } : null
716
+ } : {},
717
+ ...bodyShapeReading !== void 0 ? { body: bodyWithClaimedShape(branch.terminal, bodyShapeReading) } : {}
718
+ }
719
+ };
720
+ }
721
+ function readingsOfBranch(branch) {
722
+ return [
723
+ ...branch.bodyShapeReading !== void 0 ? [branch.bodyShapeReading.reading] : [],
724
+ ...branch.statusCodeReading !== void 0 ? [branch.statusCodeReading.reading] : []
725
+ ];
726
+ }
727
+ function unreadSentences(raw) {
728
+ const handedOver = [
729
+ ...raw.readings ?? [],
730
+ ...raw.branches.flatMap(readingsOfBranch)
731
+ ];
732
+ return [
733
+ ...raw.unreadBinding !== void 0 ? [raw.unreadBinding] : [],
734
+ ...handedOver.map(unreadReasonOf).filter((reason) => reason !== null)
735
+ ];
736
+ }
149
737
  function assembleSummary(raw, options = DEFAULT_OPTIONS) {
150
- const transitions = raw.branches.map((branch) => {
738
+ const transitions = raw.branches.map((rawBranch) => {
739
+ const branch = branchWithCollapsedReadings(rawBranch);
151
740
  const conditions = branch.conditions.map(
152
741
  rawConditionToPredicate
153
742
  );
@@ -184,6 +773,7 @@ function assembleSummary(raw, options = DEFAULT_OPTIONS) {
184
773
  },
185
774
  identity: {
186
775
  name: raw.identity.name,
776
+ ...raw.identity.nameKind !== void 0 ? { nameKind: raw.identity.nameKind } : {},
187
777
  exportPath: raw.identity.exportPath,
188
778
  boundaryBinding: raw.boundaryBinding ?? null,
189
779
  ...raw.deployableUnit !== void 0 ? { deployableUnit: raw.deployableUnit } : {}
@@ -198,21 +788,25 @@ function assembleSummary(raw, options = DEFAULT_OPTIONS) {
198
788
  };
199
789
  }
200
790
  function buildMetadata(raw) {
201
- const metadata = {};
202
- const http = buildHttpMetadata(raw);
791
+ let metadata = {};
792
+ const http = buildHttpMetadataValue(raw);
203
793
  if (http !== null) {
204
- metadata.http = http;
794
+ metadata = (0, import_behavioral_ir.withHttpMetadata)(metadata, http);
205
795
  }
206
- const graphql = buildGraphqlMetadata(raw);
796
+ const graphql = buildGraphqlMetadataValue(raw);
207
797
  if (graphql !== null) {
208
- metadata.graphql = graphql;
798
+ metadata = (0, import_behavioral_ir.withGraphqlMetadata)(metadata, graphql);
209
799
  }
210
800
  return Object.keys(metadata).length > 0 ? metadata : null;
211
801
  }
212
- function buildHttpMetadata(raw) {
802
+ function buildHttpMetadataValue(raw) {
213
803
  const http = {};
214
804
  if (raw.declaredContract !== null) {
215
- http.declaredContract = raw.declaredContract;
805
+ http.declaredContract = {
806
+ framework: raw.declaredContract.framework,
807
+ responses: raw.declaredContract.responses,
808
+ provenance: raw.declaredContract.provenance ?? "independent"
809
+ };
216
810
  }
217
811
  if (raw.bodyAccessors !== void 0 && raw.bodyAccessors.length > 0) {
218
812
  http.bodyAccessors = raw.bodyAccessors;
@@ -222,7 +816,7 @@ function buildHttpMetadata(raw) {
222
816
  }
223
817
  return Object.keys(http).length > 0 ? http : null;
224
818
  }
225
- function buildGraphqlMetadata(raw) {
819
+ function buildGraphqlMetadataValue(raw) {
226
820
  const graphql = {};
227
821
  if (raw.graphqlDocument !== void 0) {
228
822
  graphql.document = raw.graphqlDocument;
@@ -238,6 +832,12 @@ function buildGraphqlMetadata(raw) {
238
832
  }
239
833
  return Object.keys(graphql).length > 0 ? graphql : null;
240
834
  }
835
+ function answersWithHttpResponses(raw) {
836
+ if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
837
+ return true;
838
+ }
839
+ return (0, import_behavioral_ir.exchangesHttpResponses)(raw.boundaryBinding);
840
+ }
241
841
  function detectGaps(raw, transitions, options) {
242
842
  if (options.gapHandling === "silent") {
243
843
  return [];
@@ -246,20 +846,18 @@ function detectGaps(raw, transitions, options) {
246
846
  if (raw.unmatchedReturns !== void 0 && raw.unmatchedReturns > 0) {
247
847
  const count = raw.unmatchedReturns;
248
848
  gaps.push({
249
- // Not a contract violation: the handler may be answering
250
- // correctly, in a shape nobody taught the pack.
251
849
  type: "unreadOutcome",
252
850
  conditions: [],
253
851
  consequence: "unknown",
254
852
  description: count === 1 ? "One return in this function matches none of the terminal shapes this pack looks for, so what it produces is not described here" : `${count} returns in this function match none of the terminal shapes this pack looks for, so what they produce is not described here`
255
853
  });
256
854
  }
257
- if (raw.unreadBinding !== void 0) {
855
+ for (const sentence of unreadSentences(raw)) {
258
856
  gaps.push({
259
857
  type: "unreadOutcome",
260
858
  conditions: [],
261
859
  consequence: "unknown",
262
- description: raw.unreadBinding
860
+ description: sentence
263
861
  });
264
862
  }
265
863
  const unreadBody = describeUnreadBody(raw);
@@ -271,7 +869,7 @@ function detectGaps(raw, transitions, options) {
271
869
  description: unreadBody
272
870
  });
273
871
  }
274
- if (raw.declaredContract) {
872
+ if (raw.declaredContract && answersWithHttpResponses(raw)) {
275
873
  const producedStatuses = new Set(
276
874
  transitions.flatMap((t) => {
277
875
  if (t.output.type !== "response") {
@@ -451,12 +1049,27 @@ function paramToInput(param) {
451
1049
  }
452
1050
  // Annotate the CommonJS export names for ESM import in node:
453
1051
  0 && (module.exports = {
1052
+ IdMap,
1053
+ IdSet,
1054
+ PathBudgetExceeded,
1055
+ UnmodeledFlow,
1056
+ absentReading,
1057
+ ambiguousReading,
1058
+ andThenReading,
454
1059
  assembleSummary,
455
1060
  assessConfidence,
456
1061
  detectGaps,
457
1062
  effectToIR,
1063
+ enumerateOrDegrade,
1064
+ enumerateStructuredPaths,
1065
+ firstWrittenReading,
458
1066
  httpRouteDiscovery,
459
1067
  makeTransitionId,
1068
+ mapReading,
460
1069
  paramToInput,
461
- terminalToOutput
1070
+ sharedGatingConditions,
1071
+ terminalToOutput,
1072
+ unreadableReading,
1073
+ valueToReadFurtherFrom,
1074
+ writtenReading
462
1075
  });