@suss/checker-intent 0.21.0 → 0.21.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
@@ -33,6 +33,7 @@ function checkIntentAgreement(intents, code) {
33
33
  const unchecked = [];
34
34
  const codeByBoundary = indexCodeByBoundary(code);
35
35
  const boundaryByName = indexBoundaryIntentsByName(intents);
36
+ const deploymentOfUnit = (0, import_behavioral_ir.deploymentOf)(code);
36
37
  for (const intent of intents) {
37
38
  if (intent.kind === "prd") {
38
39
  const result2 = checkPrdCoverage(intent, boundaryByName);
@@ -40,7 +41,11 @@ function checkIntentAgreement(intents, code) {
40
41
  checked.push(result2.checked);
41
42
  continue;
42
43
  }
43
- const result = checkBoundaryIntent(intent, codeByBoundary);
44
+ const result = checkBoundaryIntent(
45
+ intent,
46
+ codeByBoundary,
47
+ deploymentOfUnit
48
+ );
44
49
  findings.push(...withProvenance(result.findings, intent.source));
45
50
  checked.push(...result.checked);
46
51
  unchecked.push(...result.unchecked);
@@ -83,7 +88,7 @@ function checkOutcomesDescribed(intents) {
83
88
  }
84
89
  return findings;
85
90
  }
86
- function checkBoundaryIntent(intent, codeByBoundary) {
91
+ function checkBoundaryIntent(intent, codeByBoundary, deploymentOfUnit) {
87
92
  const key = (0, import_ir_core.pairingKey)(intent.boundary);
88
93
  if (key === null) {
89
94
  return {
@@ -136,7 +141,9 @@ function checkBoundaryIntent(intent, codeByBoundary) {
136
141
  }
137
142
  const findings = [];
138
143
  for (const impl of impls) {
139
- findings.push(...compareIntentToImpl(intent, impl, label));
144
+ findings.push(
145
+ ...compareIntentToImpl(intent, impl, label, deploymentOfUnit(impl))
146
+ );
140
147
  }
141
148
  return {
142
149
  findings,
@@ -320,12 +327,14 @@ function indexCodeByBoundary(code) {
320
327
  }
321
328
  return byKey;
322
329
  }
323
- function compareIntentToImpl(intent, impl, boundary) {
330
+ function compareIntentToImpl(intent, impl, boundary, deployment) {
324
331
  const findings = [];
325
332
  const ref = codeRef(impl);
326
333
  const calls = (0, import_behavioral_ir.boundaryCalls)(impl);
327
- const codeOutcomes = impl.transitions.map((t) => toCodeOutcome(t, calls)).filter((o) => o !== null);
328
- const everyEffect = impl.transitions.flatMap(codeEffectsOf);
334
+ const codeOutcomes = impl.transitions.map((t) => toCodeOutcome(t, calls, deployment)).filter((o) => o !== null);
335
+ const everyEffect = impl.transitions.flatMap(
336
+ (t) => codeEffectsOf(t, deployment)
337
+ );
329
338
  for (const outcome of intent.outcomes) {
330
339
  const reached = outcome.kind === "effect" ? everyEffect : codeOutcomes.filter((co) => outcomeMatches(outcome, co)).flatMap((co) => co.effects);
331
340
  for (const effect of outcome.effects) {
@@ -338,7 +347,7 @@ function compareIntentToImpl(intent, impl, boundary) {
338
347
  boundary,
339
348
  intent: { name: intent.name, outcomeId: outcome.id },
340
349
  code: ref,
341
- message: `Intent "${intent.name}" declares that ${outcome.id} results in ${describeEffect(effect)} at ${boundary}; no transition of ${impl.identity.name} does that.`
350
+ message: `Intent "${intent.name}" declares that ${outcome.id} results in ${describeEffect(effect)} at ${boundary}; no transition of ${impl.identity.name} does that.${unsettledNote(reached, effect, deployment)}`
342
351
  });
343
352
  }
344
353
  if (outcome.kind === "effect") {
@@ -438,6 +447,21 @@ function compareIntentToImpl(intent, impl, boundary) {
438
447
  }
439
448
  return findings;
440
449
  }
450
+ function unsettledNote(reached, declared, deployment) {
451
+ const variables = /* @__PURE__ */ new Set();
452
+ for (const made of reached) {
453
+ const reference = (0, import_ir_core.nameReference)(made.binding);
454
+ const variable = reference === null ? null : deployment.variableFor(reference);
455
+ if (made.does === declared.does && variable !== null) {
456
+ variables.add(variable);
457
+ }
458
+ }
459
+ if (variables.size === 0) {
460
+ return "";
461
+ }
462
+ const named = [...variables].sort().join(", ");
463
+ return ` The code says which one through ${named}, and nothing in this run says what that is set to. Read the deployment that sets it in and check again.`;
464
+ }
441
465
  function effectMatches(declared, made) {
442
466
  return declared.does === made.does && (0, import_ir_core.namesBoundary)(declared.names, made.binding) && statesAll(declared.fields, made.fields) && statesAll(declared.by, made.by);
443
467
  }
@@ -452,10 +476,13 @@ function conditionMet(declared, turnsOn) {
452
476
  (guard) => guard.does === declared.at.does && (0, import_ir_core.namesBoundary)(declared.at.names, guard.binding) && (declared.finds === null || declared.finds === guard.polarity)
453
477
  );
454
478
  }
455
- function toCodeOutcome(t, calls) {
479
+ function toCodeOutcome(t, calls, deployment) {
456
480
  const output = t.output;
457
- const effects = codeEffectsOf(t);
458
- const turnsOn = (0, import_behavioral_ir.boundaryGuardsOf)(t, calls);
481
+ const effects = codeEffectsOf(t, deployment);
482
+ const turnsOn = (0, import_behavioral_ir.boundaryGuardsOf)(t, calls).map((guard) => ({
483
+ ...guard,
484
+ binding: (0, import_behavioral_ir.groundBinding)(guard.binding, deployment)
485
+ }));
459
486
  if (output.type === "response") {
460
487
  const status = output.statusCode !== null && output.statusCode.type === "literal" ? Number(output.statusCode.value) : null;
461
488
  return {
@@ -489,7 +516,7 @@ function toCodeOutcome(t, calls) {
489
516
  }
490
517
  return null;
491
518
  }
492
- function codeEffectsOf(t) {
519
+ function codeEffectsOf(t, deployment) {
493
520
  const reached = [];
494
521
  for (const effect of t.effects) {
495
522
  if (effect.type !== "interaction") {
@@ -498,10 +525,11 @@ function codeEffectsOf(t) {
498
525
  if ((0, import_behavioral_ir.goesThroughRelation)(effect.interaction)) {
499
526
  continue;
500
527
  }
501
- const label = (0, import_ir_core.displayLabel)(effect.binding);
528
+ const binding = (0, import_behavioral_ir.groundBinding)(effect.binding, deployment);
529
+ const label = (0, import_ir_core.displayLabel)(binding);
502
530
  const touched = accessDetail(effect.interaction);
503
531
  for (const does of (0, import_behavioral_ir.relationsOf)(effect.interaction)) {
504
- reached.push({ does, binding: effect.binding, label, ...touched });
532
+ reached.push({ does, binding, label, ...touched });
505
533
  }
506
534
  }
507
535
  return reached;
package/dist/index.js CHANGED
@@ -3,7 +3,9 @@ import {
3
3
  BOUNDARY_ROLE,
4
4
  boundaryCalls,
5
5
  boundaryGuardsOf,
6
+ deploymentOf,
6
7
  goesThroughRelation,
8
+ groundBinding,
7
9
  relationsOf,
8
10
  summaryRef,
9
11
  withDeclaredDelivery
@@ -14,6 +16,7 @@ import {
14
16
  boundaryKey,
15
17
  displayLabel,
16
18
  EVERY_FIELD,
19
+ nameReference,
17
20
  namesBoundary,
18
21
  pairingKey,
19
22
  ruleBoundaryMatchesKey,
@@ -25,6 +28,7 @@ function checkIntentAgreement(intents, code) {
25
28
  const unchecked = [];
26
29
  const codeByBoundary = indexCodeByBoundary(code);
27
30
  const boundaryByName = indexBoundaryIntentsByName(intents);
31
+ const deploymentOfUnit = deploymentOf(code);
28
32
  for (const intent of intents) {
29
33
  if (intent.kind === "prd") {
30
34
  const result2 = checkPrdCoverage(intent, boundaryByName);
@@ -32,7 +36,11 @@ function checkIntentAgreement(intents, code) {
32
36
  checked.push(result2.checked);
33
37
  continue;
34
38
  }
35
- const result = checkBoundaryIntent(intent, codeByBoundary);
39
+ const result = checkBoundaryIntent(
40
+ intent,
41
+ codeByBoundary,
42
+ deploymentOfUnit
43
+ );
36
44
  findings.push(...withProvenance(result.findings, intent.source));
37
45
  checked.push(...result.checked);
38
46
  unchecked.push(...result.unchecked);
@@ -75,7 +83,7 @@ function checkOutcomesDescribed(intents) {
75
83
  }
76
84
  return findings;
77
85
  }
78
- function checkBoundaryIntent(intent, codeByBoundary) {
86
+ function checkBoundaryIntent(intent, codeByBoundary, deploymentOfUnit) {
79
87
  const key = pairingKey(intent.boundary);
80
88
  if (key === null) {
81
89
  return {
@@ -128,7 +136,9 @@ function checkBoundaryIntent(intent, codeByBoundary) {
128
136
  }
129
137
  const findings = [];
130
138
  for (const impl of impls) {
131
- findings.push(...compareIntentToImpl(intent, impl, label));
139
+ findings.push(
140
+ ...compareIntentToImpl(intent, impl, label, deploymentOfUnit(impl))
141
+ );
132
142
  }
133
143
  return {
134
144
  findings,
@@ -312,12 +322,14 @@ function indexCodeByBoundary(code) {
312
322
  }
313
323
  return byKey;
314
324
  }
315
- function compareIntentToImpl(intent, impl, boundary) {
325
+ function compareIntentToImpl(intent, impl, boundary, deployment) {
316
326
  const findings = [];
317
327
  const ref = codeRef(impl);
318
328
  const calls = boundaryCalls(impl);
319
- const codeOutcomes = impl.transitions.map((t) => toCodeOutcome(t, calls)).filter((o) => o !== null);
320
- const everyEffect = impl.transitions.flatMap(codeEffectsOf);
329
+ const codeOutcomes = impl.transitions.map((t) => toCodeOutcome(t, calls, deployment)).filter((o) => o !== null);
330
+ const everyEffect = impl.transitions.flatMap(
331
+ (t) => codeEffectsOf(t, deployment)
332
+ );
321
333
  for (const outcome of intent.outcomes) {
322
334
  const reached = outcome.kind === "effect" ? everyEffect : codeOutcomes.filter((co) => outcomeMatches(outcome, co)).flatMap((co) => co.effects);
323
335
  for (const effect of outcome.effects) {
@@ -330,7 +342,7 @@ function compareIntentToImpl(intent, impl, boundary) {
330
342
  boundary,
331
343
  intent: { name: intent.name, outcomeId: outcome.id },
332
344
  code: ref,
333
- message: `Intent "${intent.name}" declares that ${outcome.id} results in ${describeEffect(effect)} at ${boundary}; no transition of ${impl.identity.name} does that.`
345
+ message: `Intent "${intent.name}" declares that ${outcome.id} results in ${describeEffect(effect)} at ${boundary}; no transition of ${impl.identity.name} does that.${unsettledNote(reached, effect, deployment)}`
334
346
  });
335
347
  }
336
348
  if (outcome.kind === "effect") {
@@ -430,6 +442,21 @@ function compareIntentToImpl(intent, impl, boundary) {
430
442
  }
431
443
  return findings;
432
444
  }
445
+ function unsettledNote(reached, declared, deployment) {
446
+ const variables = /* @__PURE__ */ new Set();
447
+ for (const made of reached) {
448
+ const reference = nameReference(made.binding);
449
+ const variable = reference === null ? null : deployment.variableFor(reference);
450
+ if (made.does === declared.does && variable !== null) {
451
+ variables.add(variable);
452
+ }
453
+ }
454
+ if (variables.size === 0) {
455
+ return "";
456
+ }
457
+ const named = [...variables].sort().join(", ");
458
+ return ` The code says which one through ${named}, and nothing in this run says what that is set to. Read the deployment that sets it in and check again.`;
459
+ }
433
460
  function effectMatches(declared, made) {
434
461
  return declared.does === made.does && namesBoundary(declared.names, made.binding) && statesAll(declared.fields, made.fields) && statesAll(declared.by, made.by);
435
462
  }
@@ -444,10 +471,13 @@ function conditionMet(declared, turnsOn) {
444
471
  (guard) => guard.does === declared.at.does && namesBoundary(declared.at.names, guard.binding) && (declared.finds === null || declared.finds === guard.polarity)
445
472
  );
446
473
  }
447
- function toCodeOutcome(t, calls) {
474
+ function toCodeOutcome(t, calls, deployment) {
448
475
  const output = t.output;
449
- const effects = codeEffectsOf(t);
450
- const turnsOn = boundaryGuardsOf(t, calls);
476
+ const effects = codeEffectsOf(t, deployment);
477
+ const turnsOn = boundaryGuardsOf(t, calls).map((guard) => ({
478
+ ...guard,
479
+ binding: groundBinding(guard.binding, deployment)
480
+ }));
451
481
  if (output.type === "response") {
452
482
  const status = output.statusCode !== null && output.statusCode.type === "literal" ? Number(output.statusCode.value) : null;
453
483
  return {
@@ -481,7 +511,7 @@ function toCodeOutcome(t, calls) {
481
511
  }
482
512
  return null;
483
513
  }
484
- function codeEffectsOf(t) {
514
+ function codeEffectsOf(t, deployment) {
485
515
  const reached = [];
486
516
  for (const effect of t.effects) {
487
517
  if (effect.type !== "interaction") {
@@ -490,10 +520,11 @@ function codeEffectsOf(t) {
490
520
  if (goesThroughRelation(effect.interaction)) {
491
521
  continue;
492
522
  }
493
- const label = displayLabel(effect.binding);
523
+ const binding = groundBinding(effect.binding, deployment);
524
+ const label = displayLabel(binding);
494
525
  const touched = accessDetail(effect.interaction);
495
526
  for (const does of relationsOf(effect.interaction)) {
496
- reached.push({ does, binding: effect.binding, label, ...touched });
527
+ reached.push({ does, binding, label, ...touched });
497
528
  }
498
529
  }
499
530
  return reached;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/checker-intent",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
4
4
  "type": "module",
5
5
  "description": "Pair team-authored intent (IntentSummary) against derived code (BehavioralSummary) and report where they diverge.",
6
6
  "keywords": [
@@ -47,9 +47,9 @@
47
47
  "zod": "^4.0.0"
48
48
  },
49
49
  "dependencies": {
50
- "@suss/behavioral-ir": "0.21.0",
51
- "@suss/intent-ir": "0.21.0",
52
- "@suss/ir-core": "0.21.0"
50
+ "@suss/behavioral-ir": "0.21.1",
51
+ "@suss/intent-ir": "0.21.1",
52
+ "@suss/ir-core": "0.21.1"
53
53
  },
54
54
  "devDependencies": {
55
55
  "tsup": "*",