@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 +41 -13
- package/dist/index.js +44 -13
- package/package.json +4 -4
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
51
|
-
"@suss/intent-ir": "0.21.
|
|
52
|
-
"@suss/ir-core": "0.21.
|
|
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": "*",
|