@suss/checker-intent 0.32.0 → 0.33.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/README.md CHANGED
@@ -100,8 +100,8 @@ It does **not** depend on
100
100
 
101
101
  ## More
102
102
 
103
- - [Documentation](https://nimbuscloud-ai.github.io/suss/)
104
- - [Every package and pack suss ships](https://nimbuscloud-ai.github.io/suss/reference/packages)
103
+ - [Documentation](https://suss.sh/)
104
+ - [Every package and pack suss ships](https://suss.sh/packs/catalog)
105
105
  - [Source and issues](https://github.com/nimbuscloud-ai/suss)
106
106
 
107
107
  ## Coverage
package/dist/index.cjs CHANGED
@@ -25,15 +25,109 @@ __export(index_exports, {
25
25
  whatWouldKeyIt: () => whatWouldKeyIt
26
26
  });
27
27
  module.exports = __toCommonJS(index_exports);
28
- var import_behavioral_ir = require("@suss/behavioral-ir");
28
+ var import_behavioral_ir2 = require("@suss/behavioral-ir");
29
29
  var import_ir_core = require("@suss/ir-core");
30
+
31
+ // src/receivedInput.ts
32
+ var import_behavioral_ir = require("@suss/behavioral-ir");
33
+ function overlaps(a, b) {
34
+ const left = comparable(a);
35
+ const right = comparable(b);
36
+ const shared = Math.min(left.length, right.length);
37
+ for (let i = 0; i < shared; i += 1) {
38
+ if (left[i] !== right[i]) {
39
+ return false;
40
+ }
41
+ }
42
+ return true;
43
+ }
44
+ function comparable(path) {
45
+ if (path[0] !== "headers") {
46
+ return [...path];
47
+ }
48
+ return path.map(
49
+ (segment, index) => index === 1 ? segment.toLowerCase() : segment
50
+ );
51
+ }
52
+ function checkReceivesBlock(pairing) {
53
+ const declared = pairing.intent.receives;
54
+ if (declared.length === 0) {
55
+ return [];
56
+ }
57
+ const result = (0, import_behavioral_ir.boundaryInputReads)(
58
+ pairing.impl,
59
+ pairing.intent.boundary,
60
+ pairing.wrappers
61
+ );
62
+ if (!result.read) {
63
+ return [];
64
+ }
65
+ const read = result.reads.paths;
66
+ const unread = declared.filter(
67
+ (field) => !read.some((path) => overlaps(path, field.path))
68
+ );
69
+ const alreadyDescribed = DESCRIBED_WITHOUT_LISTING[pairing.intent.boundary.semantics.name];
70
+ const undeclared = read.filter(
71
+ (path) => !declared.some((field) => overlaps(path, field.path)) && !alreadyDescribed(declared, path)
72
+ );
73
+ return [
74
+ ...unread.map((field) => unreadFinding(pairing, field)),
75
+ ...undeclaredFindings(pairing, undeclared)
76
+ ];
77
+ }
78
+ var LISTS_EVERYTHING = () => false;
79
+ var DESCRIBED_WITHOUT_LISTING = {
80
+ rest: (declared, path) => path[0] === "body" && declared.some((field) => field.path[0] === "body"),
81
+ "function-call": LISTS_EVERYTHING,
82
+ "message-bus": LISTS_EVERYTHING,
83
+ storage: LISTS_EVERYTHING,
84
+ "unit-invocation": LISTS_EVERYTHING,
85
+ "graphql-resolver": LISTS_EVERYTHING,
86
+ "graphql-operation": LISTS_EVERYTHING,
87
+ "runtime-config": LISTS_EVERYTHING,
88
+ metric: LISTS_EVERYTHING
89
+ };
90
+ function unreadFinding(pairing, field) {
91
+ const needsIt = field.required ? " and needs it" : "";
92
+ return {
93
+ kind: "unreadInputField",
94
+ severity: field.required ? "warning" : "info",
95
+ boundary: pairing.boundary,
96
+ intent: { name: pairing.intent.name },
97
+ code: pairing.code,
98
+ message: `Intent "${pairing.intent.name}" says ${pairing.boundary} receives ${(0, import_behavioral_ir.formatPath)(field.path)}${needsIt}; ${pairing.impl.identity.name} never reads it.`
99
+ };
100
+ }
101
+ function undeclaredFindings(pairing, undeclared) {
102
+ const said = /* @__PURE__ */ new Set();
103
+ const findings = [];
104
+ for (const path of undeclared) {
105
+ const spelled = (0, import_behavioral_ir.formatPath)(path);
106
+ if (said.has(spelled)) {
107
+ continue;
108
+ }
109
+ said.add(spelled);
110
+ findings.push({
111
+ kind: "undeclaredInputRead",
112
+ severity: "info",
113
+ boundary: pairing.boundary,
114
+ intent: { name: pairing.intent.name },
115
+ code: pairing.code,
116
+ message: `${pairing.impl.identity.name} reads ${spelled} off what it was handed at ${pairing.boundary}; intent "${pairing.intent.name}" does not declare it under receives.`
117
+ });
118
+ }
119
+ return findings;
120
+ }
121
+
122
+ // src/index.ts
30
123
  function checkIntentAgreement(intents, code) {
31
124
  const findings = [];
32
125
  const checked = [];
33
126
  const unchecked = [];
34
127
  const codeByBoundary = indexCodeByBoundary(code);
35
128
  const boundaryByName = indexBoundaryIntentsByName(intents);
36
- const deploymentOfUnit = (0, import_behavioral_ir.deploymentOf)(code);
129
+ const deploymentOfUnit = (0, import_behavioral_ir2.deploymentOf)(code);
130
+ const wrappersOfUnit = (0, import_behavioral_ir2.wrappersAround)(code);
37
131
  for (const intent of intents) {
38
132
  if (intent.kind === "prd") {
39
133
  const result2 = checkPrdCoverage(intent, boundaryByName);
@@ -44,7 +138,8 @@ function checkIntentAgreement(intents, code) {
44
138
  const result = checkBoundaryIntent(
45
139
  intent,
46
140
  codeByBoundary,
47
- deploymentOfUnit
141
+ deploymentOfUnit,
142
+ wrappersOfUnit
48
143
  );
49
144
  findings.push(...withProvenance(result.findings, intent.source));
50
145
  checked.push(...result.checked);
@@ -88,7 +183,7 @@ function checkOutcomesDescribed(intents) {
88
183
  }
89
184
  return findings;
90
185
  }
91
- function checkBoundaryIntent(intent, codeByBoundary, deploymentOfUnit) {
186
+ function checkBoundaryIntent(intent, codeByBoundary, deploymentOfUnit, wrappersOfUnit) {
92
187
  const key = (0, import_ir_core.pairingKey)(intent.boundary);
93
188
  if (key === null) {
94
189
  return {
@@ -142,7 +237,13 @@ function checkBoundaryIntent(intent, codeByBoundary, deploymentOfUnit) {
142
237
  const findings = [];
143
238
  for (const impl of impls) {
144
239
  findings.push(
145
- ...compareIntentToImpl(intent, impl, label, deploymentOfUnit(impl))
240
+ ...compareIntentToImpl(
241
+ intent,
242
+ impl,
243
+ label,
244
+ deploymentOfUnit(impl),
245
+ wrappersOfUnit(impl)
246
+ )
146
247
  );
147
248
  }
148
249
  return {
@@ -159,7 +260,7 @@ function checkBoundaryIntent(intent, codeByBoundary, deploymentOfUnit) {
159
260
  };
160
261
  }
161
262
  function codeRef(impl) {
162
- return (0, import_behavioral_ir.summaryRef)(impl);
263
+ return (0, import_behavioral_ir2.summaryRef)(impl);
163
264
  }
164
265
  function checkPrdCoverage(prd, boundaryByName) {
165
266
  const findings = [];
@@ -303,8 +404,8 @@ function applyIntentSuppressions(findings, rules, opts = {}) {
303
404
  }
304
405
  function indexCodeByBoundary(code) {
305
406
  const byKey = /* @__PURE__ */ new Map();
306
- for (const summary of (0, import_behavioral_ir.withDeclaredDelivery)(code)) {
307
- if (import_behavioral_ir.BOUNDARY_ROLE[summary.kind] !== "provider") {
407
+ for (const summary of (0, import_behavioral_ir2.withDeclaredDelivery)(code)) {
408
+ if (import_behavioral_ir2.BOUNDARY_ROLE[summary.kind] !== "provider") {
308
409
  continue;
309
410
  }
310
411
  if (summary.confidence.source === "declared") {
@@ -327,10 +428,10 @@ function indexCodeByBoundary(code) {
327
428
  }
328
429
  return byKey;
329
430
  }
330
- function compareIntentToImpl(intent, impl, boundary, deployment) {
431
+ function compareIntentToImpl(intent, impl, boundary, deployment, wrappers) {
331
432
  const findings = [];
332
433
  const ref = codeRef(impl);
333
- const calls = (0, import_behavioral_ir.boundaryCalls)(impl);
434
+ const calls = (0, import_behavioral_ir2.boundaryCalls)(impl);
334
435
  const codeOutcomes = impl.transitions.map((t) => toCodeOutcome(t, calls, deployment)).filter((o) => o !== null);
335
436
  const everyEffect = impl.transitions.flatMap(
336
437
  (t) => codeEffectsOf(t, deployment)
@@ -478,6 +579,9 @@ function compareIntentToImpl(intent, impl, boundary, deployment) {
478
579
  findings.push(finding);
479
580
  undeclared.push({ finding, boundary: made.label, does: made.does });
480
581
  }
582
+ findings.push(
583
+ ...checkReceivesBlock({ intent, impl, wrappers, boundary, code: ref })
584
+ );
481
585
  return foldRenamedBoundaries(
482
586
  intent,
483
587
  impl,
@@ -657,9 +761,9 @@ function conditionMet(declared, turnsOn) {
657
761
  function toCodeOutcome(t, calls, deployment) {
658
762
  const output = t.output;
659
763
  const effects = codeEffectsOf(t, deployment);
660
- const turnsOn = (0, import_behavioral_ir.boundaryGuardsOf)(t, calls).map((guard) => ({
764
+ const turnsOn = (0, import_behavioral_ir2.boundaryGuardsOf)(t, calls).map((guard) => ({
661
765
  ...guard,
662
- binding: (0, import_behavioral_ir.groundBinding)(guard.binding, deployment)
766
+ binding: (0, import_behavioral_ir2.groundBinding)(guard.binding, deployment)
663
767
  }));
664
768
  if (output.type === "response") {
665
769
  const status = output.statusCode !== null && output.statusCode.type === "literal" ? Number(output.statusCode.value) : null;
@@ -700,13 +804,13 @@ function codeEffectsOf(t, deployment) {
700
804
  if (effect.type !== "interaction") {
701
805
  continue;
702
806
  }
703
- if ((0, import_behavioral_ir.goesThroughRelation)(effect.interaction)) {
807
+ if ((0, import_behavioral_ir2.goesThroughRelation)(effect.interaction)) {
704
808
  continue;
705
809
  }
706
- const binding = (0, import_behavioral_ir.groundBinding)(effect.binding, deployment);
810
+ const binding = (0, import_behavioral_ir2.groundBinding)(effect.binding, deployment);
707
811
  const label = (0, import_ir_core.displayLabel)(binding);
708
812
  const touched = accessDetail(effect.interaction);
709
- for (const does of (0, import_behavioral_ir.relationsOf)(effect.interaction)) {
813
+ for (const does of (0, import_behavioral_ir2.relationsOf)(effect.interaction)) {
710
814
  reached.push({ does, binding, label, ...touched });
711
815
  }
712
816
  }
package/dist/index.js CHANGED
@@ -8,7 +8,8 @@ import {
8
8
  groundBinding,
9
9
  relationsOf,
10
10
  summaryRef,
11
- withDeclaredDelivery
11
+ withDeclaredDelivery,
12
+ wrappersAround
12
13
  } from "@suss/behavioral-ir";
13
14
  import {
14
15
  applySuppressionsToFindings,
@@ -22,6 +23,99 @@ import {
22
23
  ruleBoundaryMatchesKey,
23
24
  semanticsAgree
24
25
  } from "@suss/ir-core";
26
+
27
+ // src/receivedInput.ts
28
+ import { boundaryInputReads, formatPath } from "@suss/behavioral-ir";
29
+ function overlaps(a, b) {
30
+ const left = comparable(a);
31
+ const right = comparable(b);
32
+ const shared = Math.min(left.length, right.length);
33
+ for (let i = 0; i < shared; i += 1) {
34
+ if (left[i] !== right[i]) {
35
+ return false;
36
+ }
37
+ }
38
+ return true;
39
+ }
40
+ function comparable(path) {
41
+ if (path[0] !== "headers") {
42
+ return [...path];
43
+ }
44
+ return path.map(
45
+ (segment, index) => index === 1 ? segment.toLowerCase() : segment
46
+ );
47
+ }
48
+ function checkReceivesBlock(pairing) {
49
+ const declared = pairing.intent.receives;
50
+ if (declared.length === 0) {
51
+ return [];
52
+ }
53
+ const result = boundaryInputReads(
54
+ pairing.impl,
55
+ pairing.intent.boundary,
56
+ pairing.wrappers
57
+ );
58
+ if (!result.read) {
59
+ return [];
60
+ }
61
+ const read = result.reads.paths;
62
+ const unread = declared.filter(
63
+ (field) => !read.some((path) => overlaps(path, field.path))
64
+ );
65
+ const alreadyDescribed = DESCRIBED_WITHOUT_LISTING[pairing.intent.boundary.semantics.name];
66
+ const undeclared = read.filter(
67
+ (path) => !declared.some((field) => overlaps(path, field.path)) && !alreadyDescribed(declared, path)
68
+ );
69
+ return [
70
+ ...unread.map((field) => unreadFinding(pairing, field)),
71
+ ...undeclaredFindings(pairing, undeclared)
72
+ ];
73
+ }
74
+ var LISTS_EVERYTHING = () => false;
75
+ var DESCRIBED_WITHOUT_LISTING = {
76
+ rest: (declared, path) => path[0] === "body" && declared.some((field) => field.path[0] === "body"),
77
+ "function-call": LISTS_EVERYTHING,
78
+ "message-bus": LISTS_EVERYTHING,
79
+ storage: LISTS_EVERYTHING,
80
+ "unit-invocation": LISTS_EVERYTHING,
81
+ "graphql-resolver": LISTS_EVERYTHING,
82
+ "graphql-operation": LISTS_EVERYTHING,
83
+ "runtime-config": LISTS_EVERYTHING,
84
+ metric: LISTS_EVERYTHING
85
+ };
86
+ function unreadFinding(pairing, field) {
87
+ const needsIt = field.required ? " and needs it" : "";
88
+ return {
89
+ kind: "unreadInputField",
90
+ severity: field.required ? "warning" : "info",
91
+ boundary: pairing.boundary,
92
+ intent: { name: pairing.intent.name },
93
+ code: pairing.code,
94
+ message: `Intent "${pairing.intent.name}" says ${pairing.boundary} receives ${formatPath(field.path)}${needsIt}; ${pairing.impl.identity.name} never reads it.`
95
+ };
96
+ }
97
+ function undeclaredFindings(pairing, undeclared) {
98
+ const said = /* @__PURE__ */ new Set();
99
+ const findings = [];
100
+ for (const path of undeclared) {
101
+ const spelled = formatPath(path);
102
+ if (said.has(spelled)) {
103
+ continue;
104
+ }
105
+ said.add(spelled);
106
+ findings.push({
107
+ kind: "undeclaredInputRead",
108
+ severity: "info",
109
+ boundary: pairing.boundary,
110
+ intent: { name: pairing.intent.name },
111
+ code: pairing.code,
112
+ message: `${pairing.impl.identity.name} reads ${spelled} off what it was handed at ${pairing.boundary}; intent "${pairing.intent.name}" does not declare it under receives.`
113
+ });
114
+ }
115
+ return findings;
116
+ }
117
+
118
+ // src/index.ts
25
119
  function checkIntentAgreement(intents, code) {
26
120
  const findings = [];
27
121
  const checked = [];
@@ -29,6 +123,7 @@ function checkIntentAgreement(intents, code) {
29
123
  const codeByBoundary = indexCodeByBoundary(code);
30
124
  const boundaryByName = indexBoundaryIntentsByName(intents);
31
125
  const deploymentOfUnit = deploymentOf(code);
126
+ const wrappersOfUnit = wrappersAround(code);
32
127
  for (const intent of intents) {
33
128
  if (intent.kind === "prd") {
34
129
  const result2 = checkPrdCoverage(intent, boundaryByName);
@@ -39,7 +134,8 @@ function checkIntentAgreement(intents, code) {
39
134
  const result = checkBoundaryIntent(
40
135
  intent,
41
136
  codeByBoundary,
42
- deploymentOfUnit
137
+ deploymentOfUnit,
138
+ wrappersOfUnit
43
139
  );
44
140
  findings.push(...withProvenance(result.findings, intent.source));
45
141
  checked.push(...result.checked);
@@ -83,7 +179,7 @@ function checkOutcomesDescribed(intents) {
83
179
  }
84
180
  return findings;
85
181
  }
86
- function checkBoundaryIntent(intent, codeByBoundary, deploymentOfUnit) {
182
+ function checkBoundaryIntent(intent, codeByBoundary, deploymentOfUnit, wrappersOfUnit) {
87
183
  const key = pairingKey(intent.boundary);
88
184
  if (key === null) {
89
185
  return {
@@ -137,7 +233,13 @@ function checkBoundaryIntent(intent, codeByBoundary, deploymentOfUnit) {
137
233
  const findings = [];
138
234
  for (const impl of impls) {
139
235
  findings.push(
140
- ...compareIntentToImpl(intent, impl, label, deploymentOfUnit(impl))
236
+ ...compareIntentToImpl(
237
+ intent,
238
+ impl,
239
+ label,
240
+ deploymentOfUnit(impl),
241
+ wrappersOfUnit(impl)
242
+ )
141
243
  );
142
244
  }
143
245
  return {
@@ -322,7 +424,7 @@ function indexCodeByBoundary(code) {
322
424
  }
323
425
  return byKey;
324
426
  }
325
- function compareIntentToImpl(intent, impl, boundary, deployment) {
427
+ function compareIntentToImpl(intent, impl, boundary, deployment, wrappers) {
326
428
  const findings = [];
327
429
  const ref = codeRef(impl);
328
430
  const calls = boundaryCalls(impl);
@@ -473,6 +575,9 @@ function compareIntentToImpl(intent, impl, boundary, deployment) {
473
575
  findings.push(finding);
474
576
  undeclared.push({ finding, boundary: made.label, does: made.does });
475
577
  }
578
+ findings.push(
579
+ ...checkReceivesBlock({ intent, impl, wrappers, boundary, code: ref })
580
+ );
476
581
  return foldRenamedBoundaries(
477
582
  intent,
478
583
  impl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/checker-intent",
3
- "version": "0.32.0",
3
+ "version": "0.33.1",
4
4
  "type": "module",
5
5
  "description": "Compare the intent your team wrote down against what suss read out of the code, and report where the two have drifted apart.",
6
6
  "keywords": [
@@ -49,9 +49,9 @@
49
49
  "zod": "^4.0.0"
50
50
  },
51
51
  "dependencies": {
52
- "@suss/behavioral-ir": "0.32.0",
53
- "@suss/intent-ir": "0.32.0",
54
- "@suss/ir-core": "0.32.0"
52
+ "@suss/behavioral-ir": "0.33.1",
53
+ "@suss/intent-ir": "0.33.1",
54
+ "@suss/ir-core": "0.33.1"
55
55
  },
56
56
  "devDependencies": {
57
57
  "tsup": "*",