@gakim-digital/dexter-bridge 0.11.4 → 0.11.6
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 +20 -11
- package/package.json +3 -2
- package/src/agent.js +47 -16
- package/src/cli.js +37 -6
- package/src/config.js +6 -2
- package/src/framerAgentTools.js +353 -72
- package/src/framerBehaviorContract.js +516 -103
- package/src/protocol.js +34 -12
- package/src/providers/claudeAgentSdk.js +169 -0
- package/src/providers/codexAppServer.js +68 -12
- package/src/runtimeProfiles.js +27 -2
|
@@ -24,6 +24,7 @@ export const BEHAVIOR_LAYOUT_EFFECTS = [
|
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
export const BEHAVIOR_MECHANISMS = [
|
|
27
|
+
'native_link',
|
|
27
28
|
'native_event',
|
|
28
29
|
'native_effect',
|
|
29
30
|
'native_overlay',
|
|
@@ -33,6 +34,14 @@ export const BEHAVIOR_MECHANISMS = [
|
|
|
33
34
|
];
|
|
34
35
|
|
|
35
36
|
const MECHANISM_CAPABILITIES = {
|
|
37
|
+
native_link: {
|
|
38
|
+
activations: new Set(['tap', 'key']),
|
|
39
|
+
presentations: new Set(['navigation']),
|
|
40
|
+
layoutEffects: new Set(['navigation']),
|
|
41
|
+
stateChange: false,
|
|
42
|
+
reversible: false,
|
|
43
|
+
repeatable: true,
|
|
44
|
+
},
|
|
36
45
|
native_event: {
|
|
37
46
|
activations: new Set(['tap', 'hover', 'appear', 'key']),
|
|
38
47
|
presentations: new Set(['none', 'navigation']),
|
|
@@ -147,6 +156,7 @@ export function behaviorSignature(behavior) {
|
|
|
147
156
|
expectedTargets: behavior.expectedTargets,
|
|
148
157
|
initialState: behavior.initialState,
|
|
149
158
|
activatedState: behavior.activatedState,
|
|
159
|
+
expectedDestinations: behavior.expectedDestinations,
|
|
150
160
|
}))
|
|
151
161
|
.digest('hex');
|
|
152
162
|
}
|
|
@@ -193,9 +203,20 @@ function normalizeBehavior(rawBehavior, index) {
|
|
|
193
203
|
repeatable: rawBehavior.repeatable === true,
|
|
194
204
|
expectedTargets: integer(rawBehavior.expectedTargets),
|
|
195
205
|
expectedContent: uniqueStrings(rawBehavior.expectedContent, 20),
|
|
206
|
+
expectedDestinations: uniqueStrings(rawBehavior.expectedDestinations, 20),
|
|
196
207
|
initialState,
|
|
197
208
|
activatedState,
|
|
198
209
|
};
|
|
210
|
+
if (
|
|
211
|
+
(behavior.presentation === 'navigation'
|
|
212
|
+
|| behavior.layoutEffect === 'navigation')
|
|
213
|
+
&& behavior.expectedDestinations.length === 0
|
|
214
|
+
) {
|
|
215
|
+
throw contractError(
|
|
216
|
+
'FRAMER_BEHAVIOR_CONTRACT_INVALID',
|
|
217
|
+
`Behavior ${index + 1} must declare expectedDestinations for navigation.`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
199
220
|
return {
|
|
200
221
|
...behavior,
|
|
201
222
|
signature: behaviorSignature(behavior),
|
|
@@ -227,6 +248,11 @@ function normalizeMechanism(rawMechanism, index) {
|
|
|
227
248
|
mechanism,
|
|
228
249
|
rationale,
|
|
229
250
|
representativeFirst: rawMechanism.representativeFirst !== false,
|
|
251
|
+
targetNodeIds: uniqueStrings(rawMechanism.targetNodeIds, 50),
|
|
252
|
+
representativeTargetNodeId: text(
|
|
253
|
+
rawMechanism.representativeTargetNodeId,
|
|
254
|
+
200,
|
|
255
|
+
),
|
|
230
256
|
};
|
|
231
257
|
}
|
|
232
258
|
|
|
@@ -333,6 +359,21 @@ export function normalizeBehaviorPlan(rawArguments, inheritedRejected = []) {
|
|
|
333
359
|
`Behavior "${decision.behaviorId}" has more than one selected mechanism.`,
|
|
334
360
|
);
|
|
335
361
|
}
|
|
362
|
+
const behavior = behaviors.find((item) => item.id === decision.behaviorId);
|
|
363
|
+
if (
|
|
364
|
+
behavior?.expectedTargets > 1
|
|
365
|
+
&& decision.representativeFirst
|
|
366
|
+
&& (
|
|
367
|
+
decision.targetNodeIds.length !== behavior.expectedTargets
|
|
368
|
+
|| !decision.representativeTargetNodeId
|
|
369
|
+
|| !decision.targetNodeIds.includes(decision.representativeTargetNodeId)
|
|
370
|
+
)
|
|
371
|
+
) {
|
|
372
|
+
throw contractError(
|
|
373
|
+
'FRAMER_BEHAVIOR_REPRESENTATIVE_TARGETS_REQUIRED',
|
|
374
|
+
`Repeated behavior "${decision.behaviorId}" must declare all targetNodeIds and one representativeTargetNodeId before mutation.`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
336
377
|
mechanismByBehavior.set(decision.behaviorId, decision);
|
|
337
378
|
}
|
|
338
379
|
const compatibility = behaviors.map((behavior) => {
|
|
@@ -520,9 +561,19 @@ function referencesComponent(snapshot, componentNodeId) {
|
|
|
520
561
|
function nodeVisible(snapshot) {
|
|
521
562
|
const node = nodeFromSnapshot(snapshot);
|
|
522
563
|
if (!node) return null;
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
564
|
+
const visible = Object.prototype.hasOwnProperty.call(node, 'visible')
|
|
565
|
+
? node.visible
|
|
566
|
+
: isRecord(node.attributes)
|
|
567
|
+
? node.attributes.visible
|
|
568
|
+
: undefined;
|
|
569
|
+
const hidden = Object.prototype.hasOwnProperty.call(node, 'hidden')
|
|
570
|
+
? node.hidden
|
|
571
|
+
: isRecord(node.attributes)
|
|
572
|
+
? node.attributes.hidden
|
|
573
|
+
: undefined;
|
|
574
|
+
if (visible === false || hidden === true) return false;
|
|
575
|
+
if (visible === true || hidden === false) return true;
|
|
576
|
+
return null;
|
|
526
577
|
}
|
|
527
578
|
|
|
528
579
|
function eventText(snapshot, event) {
|
|
@@ -570,11 +621,24 @@ function normalizeEvidence(raw, behaviorById) {
|
|
|
570
621
|
`Interaction evidence references unknown behavior "${behaviorId}".`,
|
|
571
622
|
);
|
|
572
623
|
}
|
|
624
|
+
const expectedDestination = text(raw.expectedDestination, 500);
|
|
625
|
+
if (
|
|
626
|
+
expectedDestination
|
|
627
|
+
&& !behavior.expectedDestinations.some((planned) =>
|
|
628
|
+
destinationCandidates(planned).some((candidate) =>
|
|
629
|
+
destinationCandidates(expectedDestination).includes(candidate)))
|
|
630
|
+
) {
|
|
631
|
+
throw contractError(
|
|
632
|
+
'FRAMER_BEHAVIOR_EVIDENCE_INVALID',
|
|
633
|
+
`Interaction evidence for "${behaviorId}" must use a destination declared in the behavior plan.`,
|
|
634
|
+
);
|
|
635
|
+
}
|
|
573
636
|
return {
|
|
574
637
|
behaviorId,
|
|
575
638
|
description: text(raw.description, 300),
|
|
576
639
|
triggerNodeId: text(raw.triggerNodeId, 200),
|
|
577
640
|
event: text(raw.event, 80),
|
|
641
|
+
expectedDestination,
|
|
578
642
|
targetNodeId: text(raw.targetNodeId, 200),
|
|
579
643
|
componentNodeId: text(raw.componentNodeId, 200),
|
|
580
644
|
instanceNodeIds: uniqueStrings(raw.instanceNodeIds, 50),
|
|
@@ -582,6 +646,19 @@ function normalizeEvidence(raw, behaviorById) {
|
|
|
582
646
|
closedVariantNodeId: text(raw.closedVariantNodeId, 200),
|
|
583
647
|
openAnswerNodeId: text(raw.openAnswerNodeId, 200),
|
|
584
648
|
closedAnswerNodeId: text(raw.closedAnswerNodeId, 200),
|
|
649
|
+
semanticAssessment: isRecord(raw.semanticAssessment)
|
|
650
|
+
&& ['verified', 'contradicted', 'unknown']
|
|
651
|
+
.includes(raw.semanticAssessment.verdict)
|
|
652
|
+
&& text(raw.semanticAssessment.rationale, 1200).length >= 10
|
|
653
|
+
? {
|
|
654
|
+
verdict: raw.semanticAssessment.verdict,
|
|
655
|
+
rationale: text(raw.semanticAssessment.rationale, 1200),
|
|
656
|
+
evidenceNodeIds: uniqueStrings(
|
|
657
|
+
raw.semanticAssessment.evidenceNodeIds,
|
|
658
|
+
50,
|
|
659
|
+
),
|
|
660
|
+
}
|
|
661
|
+
: null,
|
|
585
662
|
};
|
|
586
663
|
}
|
|
587
664
|
|
|
@@ -612,94 +689,154 @@ export function evidenceNodeIds(checks) {
|
|
|
612
689
|
]), 200);
|
|
613
690
|
}
|
|
614
691
|
|
|
615
|
-
function
|
|
692
|
+
function evidenceState() {
|
|
693
|
+
return {
|
|
694
|
+
contradictions: [],
|
|
695
|
+
unknowns: [],
|
|
696
|
+
observations: [],
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function contradict(state, message) {
|
|
701
|
+
state.contradictions.push(message);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function unknown(state, message) {
|
|
705
|
+
state.unknowns.push(message);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function observe(state, kind, detail = {}) {
|
|
709
|
+
state.observations.push({ kind, ...detail });
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function verifyTrigger(check, behavior, trigger, state) {
|
|
616
713
|
if (!check.triggerNodeId || !trigger) {
|
|
617
|
-
|
|
714
|
+
unknown(state, 'trigger node was not found');
|
|
618
715
|
return '';
|
|
619
716
|
}
|
|
717
|
+
observe(state, 'node_found', {
|
|
718
|
+
nodeId: check.triggerNodeId,
|
|
719
|
+
nodeType: nodeType(trigger),
|
|
720
|
+
});
|
|
620
721
|
const allowedEvents = expectedEvents(behavior.activation);
|
|
621
722
|
if (
|
|
622
723
|
allowedEvents.length > 0
|
|
623
724
|
&& !allowedEvents.includes(check.event)
|
|
624
725
|
) {
|
|
625
|
-
|
|
726
|
+
contradict(
|
|
727
|
+
state,
|
|
626
728
|
`${check.event || 'the supplied event'} does not match ${behavior.activation} activation`,
|
|
627
729
|
);
|
|
628
730
|
}
|
|
629
731
|
if (!check.event) {
|
|
630
|
-
|
|
732
|
+
unknown(state, 'the trigger event was not supplied');
|
|
631
733
|
return '';
|
|
632
734
|
}
|
|
633
735
|
if (!FRAME_EVENT_NODE_TYPES.has(nodeType(trigger))) {
|
|
634
|
-
|
|
736
|
+
contradict(
|
|
737
|
+
state,
|
|
738
|
+
`${nodeType(trigger) || 'the trigger node'} does not support verified frame events`,
|
|
739
|
+
);
|
|
635
740
|
}
|
|
636
741
|
const evidence = eventText(trigger, check.event);
|
|
637
|
-
if (!evidence)
|
|
742
|
+
if (!evidence) {
|
|
743
|
+
unknown(state, `${check.event} was not visible in the serialized trigger`);
|
|
744
|
+
} else {
|
|
745
|
+
observe(state, 'event_binding', {
|
|
746
|
+
nodeId: check.triggerNodeId,
|
|
747
|
+
event: check.event,
|
|
748
|
+
value: evidence.slice(0, 1000),
|
|
749
|
+
});
|
|
750
|
+
}
|
|
638
751
|
return evidence;
|
|
639
752
|
}
|
|
640
753
|
|
|
641
|
-
function verifyExpectedContent(behavior, snapshots, nodeIds,
|
|
754
|
+
function verifyExpectedContent(behavior, snapshots, nodeIds, state) {
|
|
642
755
|
const combined = normalizedEvidenceText(
|
|
643
756
|
nodeIds.map((id) => nodeFromSnapshot(snapshotFor(snapshots, id))),
|
|
644
757
|
);
|
|
645
758
|
for (const expected of behavior.expectedContent) {
|
|
646
759
|
if (!combined.includes(expected.toLowerCase())) {
|
|
647
|
-
|
|
760
|
+
unknown(state, `expected content was not visible in the evidence: ${expected}`);
|
|
761
|
+
} else {
|
|
762
|
+
observe(state, 'expected_content', { value: expected });
|
|
648
763
|
}
|
|
649
764
|
}
|
|
650
765
|
}
|
|
651
766
|
|
|
652
|
-
function verifyOverlay(check, behavior, snapshots,
|
|
767
|
+
function verifyOverlay(check, behavior, snapshots, state) {
|
|
653
768
|
const trigger = snapshotFor(snapshots, check.triggerNodeId);
|
|
654
769
|
const target = snapshotFor(snapshots, check.targetNodeId);
|
|
655
|
-
const triggerEvent = verifyTrigger(check, behavior, trigger,
|
|
770
|
+
const triggerEvent = verifyTrigger(check, behavior, trigger, state);
|
|
656
771
|
if (!check.targetNodeId || !target) {
|
|
657
|
-
|
|
772
|
+
unknown(state, 'overlay target node was not found');
|
|
658
773
|
return;
|
|
659
774
|
}
|
|
660
|
-
|
|
661
|
-
|
|
775
|
+
observe(state, 'node_found', {
|
|
776
|
+
nodeId: check.targetNodeId,
|
|
777
|
+
nodeType: nodeType(target),
|
|
778
|
+
});
|
|
779
|
+
if (triggerEvent && !triggerEvent.includes('show_overlay')) {
|
|
780
|
+
contradict(state, `${check.event} does not contain SHOW_OVERLAY`);
|
|
662
781
|
}
|
|
663
|
-
if (
|
|
664
|
-
|
|
782
|
+
if (
|
|
783
|
+
triggerEvent
|
|
784
|
+
&& !triggerEvent.includes(check.targetNodeId.toLowerCase())
|
|
785
|
+
) {
|
|
786
|
+
contradict(state, `${check.event} does not reference target ${check.targetNodeId}`);
|
|
665
787
|
}
|
|
666
788
|
const expectedType =
|
|
667
789
|
behavior.presentation === 'modal'
|
|
668
790
|
? 'FixedOverlayNode'
|
|
669
791
|
: 'RelativeOverlayNode';
|
|
670
792
|
if (nodeType(target) !== expectedType) {
|
|
671
|
-
|
|
793
|
+
contradict(state, `the behavior requires a ${expectedType}`);
|
|
672
794
|
}
|
|
673
|
-
|
|
674
|
-
|
|
795
|
+
const actualParentId = parentId(target);
|
|
796
|
+
if (!actualParentId) {
|
|
797
|
+
unknown(state, 'the overlay parent was not visible in the evidence');
|
|
798
|
+
} else if (actualParentId !== check.triggerNodeId) {
|
|
799
|
+
contradict(state, 'the overlay is not parented to its actual trigger');
|
|
675
800
|
}
|
|
676
|
-
verifyExpectedContent(behavior, snapshots, [check.targetNodeId],
|
|
801
|
+
verifyExpectedContent(behavior, snapshots, [check.targetNodeId], state);
|
|
677
802
|
}
|
|
678
803
|
|
|
679
804
|
function verifyComponentInstances(
|
|
680
805
|
check,
|
|
681
806
|
behavior,
|
|
682
807
|
snapshots,
|
|
683
|
-
|
|
808
|
+
state,
|
|
684
809
|
context,
|
|
685
810
|
{ requireDefinition = true } = {},
|
|
686
811
|
) {
|
|
687
812
|
const component = snapshotFor(snapshots, check.componentNodeId);
|
|
688
|
-
if (
|
|
813
|
+
if (requireDefinition && !component) {
|
|
814
|
+
unknown(state, 'component definition was not found');
|
|
815
|
+
} else if (
|
|
689
816
|
requireDefinition
|
|
690
|
-
&&
|
|
817
|
+
&& nodeType(component) !== 'ComponentNode'
|
|
691
818
|
) {
|
|
692
|
-
|
|
819
|
+
contradict(state, 'the declared component definition is not a ComponentNode');
|
|
693
820
|
}
|
|
694
821
|
const instances = check.instanceNodeIds.map((id) => ({
|
|
695
822
|
id,
|
|
696
823
|
snapshot: snapshotFor(snapshots, id),
|
|
697
824
|
}));
|
|
698
825
|
for (const instance of instances) {
|
|
699
|
-
if (!instance.snapshot
|
|
700
|
-
|
|
826
|
+
if (!instance.snapshot) {
|
|
827
|
+
unknown(state, `component instance ${instance.id} was not found`);
|
|
828
|
+
} else if (nodeType(instance.snapshot) !== 'ComponentInstanceNode') {
|
|
829
|
+
contradict(state, `${instance.id} is not a ComponentInstanceNode`);
|
|
701
830
|
} else if (!referencesComponent(instance.snapshot, check.componentNodeId)) {
|
|
702
|
-
|
|
831
|
+
contradict(
|
|
832
|
+
state,
|
|
833
|
+
`component instance ${instance.id} does not reference ${check.componentNodeId}`,
|
|
834
|
+
);
|
|
835
|
+
} else {
|
|
836
|
+
observe(state, 'component_instance', {
|
|
837
|
+
nodeId: instance.id,
|
|
838
|
+
componentNodeId: check.componentNodeId,
|
|
839
|
+
});
|
|
703
840
|
}
|
|
704
841
|
const authorizedContainerIds = uniqueStrings(
|
|
705
842
|
context?.authorizedInstanceContainerIds,
|
|
@@ -714,7 +851,8 @@ function verifyComponentInstances(
|
|
|
714
851
|
: []),
|
|
715
852
|
].filter(Boolean));
|
|
716
853
|
if (!authorizedContainerIds.some((id) => ancestry.has(id))) {
|
|
717
|
-
|
|
854
|
+
contradict(
|
|
855
|
+
state,
|
|
718
856
|
`component instance ${instance.id} is outside the authorized write scope`,
|
|
719
857
|
);
|
|
720
858
|
}
|
|
@@ -726,7 +864,7 @@ function verifyComponentVariants(
|
|
|
726
864
|
check,
|
|
727
865
|
behavior,
|
|
728
866
|
snapshots,
|
|
729
|
-
|
|
867
|
+
state,
|
|
730
868
|
context,
|
|
731
869
|
) {
|
|
732
870
|
const trigger = snapshotFor(snapshots, check.triggerNodeId);
|
|
@@ -735,27 +873,28 @@ function verifyComponentVariants(
|
|
|
735
873
|
const closedVariant = snapshotFor(snapshots, check.closedVariantNodeId);
|
|
736
874
|
const openAnswer = snapshotFor(snapshots, check.openAnswerNodeId);
|
|
737
875
|
const closedAnswer = snapshotFor(snapshots, check.closedAnswerNodeId);
|
|
738
|
-
const triggerEvent = verifyTrigger(check, behavior, trigger,
|
|
739
|
-
verifyComponentInstances(check, behavior, snapshots,
|
|
876
|
+
const triggerEvent = verifyTrigger(check, behavior, trigger, state);
|
|
877
|
+
verifyComponentInstances(check, behavior, snapshots, state, context);
|
|
740
878
|
if (component && trigger && !belongsTo(trigger, check.componentNodeId)) {
|
|
741
|
-
|
|
879
|
+
contradict(state, 'the trigger is not inside the component definition');
|
|
742
880
|
}
|
|
743
881
|
if (
|
|
744
882
|
!openVariant
|
|
745
883
|
|| !closedVariant
|
|
746
|
-
|| check.openVariantNodeId === check.closedVariantNodeId
|
|
747
884
|
) {
|
|
748
|
-
|
|
885
|
+
unknown(state, 'open and closed variants were not both found');
|
|
886
|
+
} else if (check.openVariantNodeId === check.closedVariantNodeId) {
|
|
887
|
+
contradict(state, 'open and closed variants must be distinct');
|
|
749
888
|
} else {
|
|
750
889
|
if (!belongsTo(openVariant, check.componentNodeId)) {
|
|
751
|
-
|
|
890
|
+
contradict(state, 'the open variant does not belong to the component');
|
|
752
891
|
}
|
|
753
892
|
if (!belongsTo(closedVariant, check.componentNodeId)) {
|
|
754
|
-
|
|
893
|
+
contradict(state, 'the closed variant does not belong to the component');
|
|
755
894
|
}
|
|
756
895
|
}
|
|
757
|
-
if (!triggerEvent.includes('set_variant')) {
|
|
758
|
-
|
|
896
|
+
if (triggerEvent && !triggerEvent.includes('set_variant')) {
|
|
897
|
+
contradict(state, `${check.event} does not contain SET_VARIANT`);
|
|
759
898
|
}
|
|
760
899
|
if (
|
|
761
900
|
triggerEvent
|
|
@@ -763,44 +902,167 @@ function verifyComponentVariants(
|
|
|
763
902
|
&& !triggerEvent.includes(check.openVariantNodeId.toLowerCase())
|
|
764
903
|
&& !triggerEvent.includes(check.closedVariantNodeId.toLowerCase())
|
|
765
904
|
) {
|
|
766
|
-
|
|
905
|
+
contradict(state, 'SET_VARIANT does not reference cycle or a declared variant');
|
|
767
906
|
}
|
|
768
907
|
if (!openAnswer || !closedAnswer) {
|
|
769
|
-
|
|
908
|
+
unknown(state, 'open and closed answer-state nodes were not found');
|
|
770
909
|
} else {
|
|
771
910
|
if (!belongsTo(openAnswer, check.openVariantNodeId)) {
|
|
772
|
-
|
|
911
|
+
contradict(state, 'the visible answer does not belong to the open variant');
|
|
773
912
|
}
|
|
774
913
|
if (!belongsTo(closedAnswer, check.closedVariantNodeId)) {
|
|
775
|
-
|
|
914
|
+
contradict(state, 'the hidden answer does not belong to the closed variant');
|
|
776
915
|
}
|
|
777
|
-
|
|
778
|
-
|
|
916
|
+
const openVisibility = nodeVisible(openAnswer);
|
|
917
|
+
const closedVisibility = nodeVisible(closedAnswer);
|
|
918
|
+
observe(state, 'visibility', {
|
|
919
|
+
nodeId: check.openAnswerNodeId,
|
|
920
|
+
value:
|
|
921
|
+
openVisibility === null
|
|
922
|
+
? 'unknown'
|
|
923
|
+
: openVisibility
|
|
924
|
+
? 'visible'
|
|
925
|
+
: 'hidden',
|
|
926
|
+
});
|
|
927
|
+
observe(state, 'visibility', {
|
|
928
|
+
nodeId: check.closedAnswerNodeId,
|
|
929
|
+
value:
|
|
930
|
+
closedVisibility === null
|
|
931
|
+
? 'unknown'
|
|
932
|
+
: closedVisibility
|
|
933
|
+
? 'visible'
|
|
934
|
+
: 'hidden',
|
|
935
|
+
});
|
|
936
|
+
if (openVisibility === false) {
|
|
937
|
+
contradict(state, 'the answer is hidden in the open variant');
|
|
938
|
+
} else if (openVisibility === null) {
|
|
939
|
+
unknown(state, 'the open answer visibility was not exposed');
|
|
779
940
|
}
|
|
780
|
-
if (
|
|
781
|
-
|
|
941
|
+
if (closedVisibility === true) {
|
|
942
|
+
contradict(state, 'the answer is not hidden in the closed variant');
|
|
943
|
+
} else if (closedVisibility === null) {
|
|
944
|
+
unknown(state, 'the closed answer visibility was not exposed');
|
|
782
945
|
}
|
|
783
946
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function destinationCandidates(value) {
|
|
950
|
+
const candidate = text(value, 2000);
|
|
951
|
+
if (!candidate) return [];
|
|
952
|
+
const normalized = candidate.toLowerCase().replace(/\/+$/, '') || '/';
|
|
953
|
+
const candidates = new Set([normalized]);
|
|
954
|
+
try {
|
|
955
|
+
const parsed = new URL(candidate, 'https://dexter.local');
|
|
956
|
+
candidates.add(
|
|
957
|
+
`${parsed.pathname}${parsed.search}${parsed.hash}`
|
|
958
|
+
.toLowerCase()
|
|
959
|
+
.replace(/\/+$/, '') || '/',
|
|
960
|
+
);
|
|
961
|
+
} catch {
|
|
962
|
+
// Non-URL control values are ignored unless the raw value matches.
|
|
963
|
+
}
|
|
964
|
+
return [...candidates];
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function linkFacts(value) {
|
|
968
|
+
const facts = [];
|
|
969
|
+
const visit = (current, path = []) => {
|
|
970
|
+
if (Array.isArray(current)) {
|
|
971
|
+
current.forEach((nested, index) => visit(nested, [...path, String(index)]));
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (!isRecord(current)) return;
|
|
975
|
+
for (const [key, nested] of Object.entries(current)) {
|
|
976
|
+
const nextPath = [...path, key];
|
|
977
|
+
if (
|
|
978
|
+
typeof nested === 'string'
|
|
979
|
+
&& /(?:href|url|link|destination|route|path)$/i.test(key)
|
|
980
|
+
) {
|
|
981
|
+
facts.push({
|
|
982
|
+
path: nextPath.join('.'),
|
|
983
|
+
value: nested,
|
|
984
|
+
candidates: destinationCandidates(nested),
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
visit(nested, nextPath);
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
visit(value);
|
|
991
|
+
return facts.filter((fact) => fact.candidates.length > 0);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function verifyNativeLink(check, behavior, snapshots, state, context) {
|
|
995
|
+
const targetIds = check.instanceNodeIds.length > 0
|
|
996
|
+
? check.instanceNodeIds
|
|
997
|
+
: [check.triggerNodeId].filter(Boolean);
|
|
998
|
+
const expectedValues = check.expectedDestination
|
|
999
|
+
? [check.expectedDestination]
|
|
1000
|
+
: behavior.expectedDestinations;
|
|
1001
|
+
const expected = new Set(expectedValues.flatMap(destinationCandidates));
|
|
1002
|
+
if (targetIds.length === 0) {
|
|
1003
|
+
unknown(state, 'no navigation target node was supplied');
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
for (const nodeId of targetIds) {
|
|
1007
|
+
const snapshot = snapshotFor(snapshots, nodeId);
|
|
1008
|
+
if (!snapshot) {
|
|
1009
|
+
unknown(state, `navigation node ${nodeId} was not found`);
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
const facts = linkFacts([
|
|
1013
|
+
nodeFromSnapshot(snapshot),
|
|
1014
|
+
snapshot?.controls,
|
|
1015
|
+
]);
|
|
1016
|
+
if (facts.length === 0) {
|
|
1017
|
+
const controlFacts = linkFacts(context?.componentControls);
|
|
1018
|
+
if (controlFacts.length > 0) {
|
|
1019
|
+
observe(state, 'component_link_control_available', {
|
|
1020
|
+
nodeId,
|
|
1021
|
+
sources: controlFacts.map((fact) => fact.path).slice(0, 20),
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
unknown(
|
|
1025
|
+
state,
|
|
1026
|
+
`no native link or component-control destination was visible for ${nodeId}`,
|
|
1027
|
+
);
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
const matchingFact = facts.find((fact) =>
|
|
1031
|
+
fact.candidates.some((candidate) => expected.has(candidate)));
|
|
1032
|
+
if (matchingFact) {
|
|
1033
|
+
observe(state, 'navigation_destination', {
|
|
1034
|
+
nodeId,
|
|
1035
|
+
source: matchingFact.path,
|
|
1036
|
+
value: matchingFact.value,
|
|
1037
|
+
});
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
contradict(
|
|
1041
|
+
state,
|
|
1042
|
+
`${nodeId} points to ${facts.map((fact) => fact.value).join(', ')} instead of ${expectedValues.join(' or ')}`,
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
790
1045
|
}
|
|
791
1046
|
|
|
792
1047
|
function verifyExistingComponent(
|
|
793
1048
|
check,
|
|
794
1049
|
behavior,
|
|
795
1050
|
snapshots,
|
|
796
|
-
|
|
1051
|
+
state,
|
|
797
1052
|
context,
|
|
798
1053
|
) {
|
|
799
1054
|
const trigger = snapshotFor(snapshots, check.triggerNodeId);
|
|
800
|
-
|
|
801
|
-
|
|
1055
|
+
verifyComponentInstances(check, behavior, snapshots, state, context);
|
|
1056
|
+
if (
|
|
1057
|
+
behavior.presentation === 'navigation'
|
|
1058
|
+
|| behavior.layoutEffect === 'navigation'
|
|
1059
|
+
) {
|
|
1060
|
+
verifyNativeLink(check, behavior, snapshots, state, context);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
const triggerEvent = verifyTrigger(check, behavior, trigger, state);
|
|
802
1064
|
if (trigger && !belongsTo(trigger, check.componentNodeId)) {
|
|
803
|
-
|
|
1065
|
+
contradict(state, 'the trigger is not inside the existing component definition');
|
|
804
1066
|
}
|
|
805
1067
|
const permittedActions =
|
|
806
1068
|
behavior.presentation === 'floating' || behavior.presentation === 'modal'
|
|
@@ -810,15 +1072,19 @@ function verifyExistingComponent(
|
|
|
810
1072
|
: [];
|
|
811
1073
|
if (
|
|
812
1074
|
permittedActions.length > 0
|
|
1075
|
+
&& triggerEvent
|
|
813
1076
|
&& !permittedActions.some((action) => triggerEvent.includes(action))
|
|
814
1077
|
) {
|
|
815
|
-
|
|
1078
|
+
contradict(
|
|
1079
|
+
state,
|
|
1080
|
+
'the component trigger does not expose an action capable of the declared behavior',
|
|
1081
|
+
);
|
|
816
1082
|
}
|
|
817
1083
|
verifyExpectedContent(
|
|
818
1084
|
behavior,
|
|
819
1085
|
snapshots,
|
|
820
1086
|
[check.componentNodeId, ...check.instanceNodeIds],
|
|
821
|
-
|
|
1087
|
+
state,
|
|
822
1088
|
);
|
|
823
1089
|
}
|
|
824
1090
|
|
|
@@ -834,7 +1100,7 @@ function hasCodeComponentEvidence(snapshot) {
|
|
|
834
1100
|
);
|
|
835
1101
|
}
|
|
836
1102
|
|
|
837
|
-
function verifyCodeComponent(check, behavior, snapshots,
|
|
1103
|
+
function verifyCodeComponent(check, behavior, snapshots, state, context) {
|
|
838
1104
|
// Code files are not native canvas ComponentNodes. Their durable structural
|
|
839
1105
|
// proof is an installed ComponentInstanceNode whose component reference
|
|
840
1106
|
// resolves to the authored code-file ID.
|
|
@@ -842,7 +1108,7 @@ function verifyCodeComponent(check, behavior, snapshots, failures, context) {
|
|
|
842
1108
|
check,
|
|
843
1109
|
behavior,
|
|
844
1110
|
snapshots,
|
|
845
|
-
|
|
1111
|
+
state,
|
|
846
1112
|
context,
|
|
847
1113
|
{ requireDefinition: false },
|
|
848
1114
|
);
|
|
@@ -851,7 +1117,10 @@ function verifyCodeComponent(check, behavior, snapshots, failures, context) {
|
|
|
851
1117
|
!nodes.some(hasCodeComponentEvidence)
|
|
852
1118
|
&& Number(context?.lowLevelWriteCalls || 0) < 1
|
|
853
1119
|
) {
|
|
854
|
-
|
|
1120
|
+
unknown(
|
|
1121
|
+
state,
|
|
1122
|
+
'no code-component source or installed code-component evidence was found',
|
|
1123
|
+
);
|
|
855
1124
|
}
|
|
856
1125
|
// Content is covered by the mandatory structural and visual checks. Behavior
|
|
857
1126
|
// plans may describe content semantically rather than quote literal canvas
|
|
@@ -859,49 +1128,61 @@ function verifyCodeComponent(check, behavior, snapshots, failures, context) {
|
|
|
859
1128
|
// false failures for otherwise valid code components.
|
|
860
1129
|
}
|
|
861
1130
|
|
|
862
|
-
function verifyNativeEvent(check, behavior, snapshots,
|
|
1131
|
+
function verifyNativeEvent(check, behavior, snapshots, state) {
|
|
863
1132
|
verifyTrigger(
|
|
864
1133
|
check,
|
|
865
1134
|
behavior,
|
|
866
1135
|
snapshotFor(snapshots, check.triggerNodeId),
|
|
867
|
-
|
|
1136
|
+
state,
|
|
868
1137
|
);
|
|
869
1138
|
}
|
|
870
1139
|
|
|
871
|
-
function verifyNativeEffect(check, behavior, snapshots,
|
|
1140
|
+
function verifyNativeEffect(check, behavior, snapshots, state) {
|
|
872
1141
|
const trigger = snapshotFor(snapshots, check.triggerNodeId);
|
|
873
1142
|
if (!check.triggerNodeId || !trigger) {
|
|
874
|
-
|
|
1143
|
+
unknown(state, 'effect target node was not found');
|
|
875
1144
|
return null;
|
|
876
1145
|
}
|
|
1146
|
+
observe(state, 'node_found', {
|
|
1147
|
+
nodeId: check.triggerNodeId,
|
|
1148
|
+
nodeType: nodeType(trigger),
|
|
1149
|
+
});
|
|
877
1150
|
if (!FRAME_EFFECT_NODE_TYPES.has(nodeType(trigger))) {
|
|
878
|
-
|
|
1151
|
+
contradict(
|
|
1152
|
+
state,
|
|
879
1153
|
`${nodeType(trigger) || 'the target node'} does not support verified Framer effects`,
|
|
880
1154
|
);
|
|
881
1155
|
}
|
|
882
1156
|
const effectName = NATIVE_EFFECT_BY_ACTIVATION[behavior.activation];
|
|
883
1157
|
if (!effectName) {
|
|
884
|
-
|
|
1158
|
+
contradict(
|
|
1159
|
+
state,
|
|
885
1160
|
`native_effect does not support ${behavior.activation} activation`,
|
|
886
1161
|
);
|
|
887
1162
|
return null;
|
|
888
1163
|
}
|
|
889
1164
|
if (check.event !== effectName) {
|
|
890
|
-
|
|
1165
|
+
contradict(
|
|
1166
|
+
state,
|
|
891
1167
|
`${check.event || 'the supplied effect'} does not match ${effectName}`,
|
|
892
1168
|
);
|
|
893
1169
|
}
|
|
894
1170
|
const effect = directNodeProperty(trigger, effectName);
|
|
895
1171
|
if (!isRecord(effect) || Object.keys(effect).length === 0) {
|
|
896
|
-
|
|
1172
|
+
unknown(state, `${effectName} is not visible on the serialized target`);
|
|
897
1173
|
return null;
|
|
898
1174
|
}
|
|
1175
|
+
observe(state, 'native_effect', {
|
|
1176
|
+
nodeId: check.triggerNodeId,
|
|
1177
|
+
effectName,
|
|
1178
|
+
value: effect,
|
|
1179
|
+
});
|
|
899
1180
|
if (
|
|
900
1181
|
effectName === 'appearEffect'
|
|
901
1182
|
&& !['onInView', 'onMount', 'onScrollTarget', 'onScrollDirection']
|
|
902
1183
|
.includes(effect.trigger)
|
|
903
1184
|
) {
|
|
904
|
-
|
|
1185
|
+
contradict(state, 'appearEffect does not declare a supported trigger');
|
|
905
1186
|
}
|
|
906
1187
|
return {
|
|
907
1188
|
effectName,
|
|
@@ -909,6 +1190,66 @@ function verifyNativeEffect(check, behavior, snapshots, failures) {
|
|
|
909
1190
|
};
|
|
910
1191
|
}
|
|
911
1192
|
|
|
1193
|
+
function verdictFor(state) {
|
|
1194
|
+
if (state.contradictions.length > 0) return 'contradicted';
|
|
1195
|
+
if (state.unknowns.length > 0) return 'unknown';
|
|
1196
|
+
return 'verified';
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function applySemanticAssessment(
|
|
1200
|
+
check,
|
|
1201
|
+
result,
|
|
1202
|
+
mode,
|
|
1203
|
+
liveEvidenceNodeIds = new Set(),
|
|
1204
|
+
) {
|
|
1205
|
+
const assessment = check.semanticAssessment;
|
|
1206
|
+
const base = {
|
|
1207
|
+
...result,
|
|
1208
|
+
deterministicVerdict: result.verdict,
|
|
1209
|
+
semanticAssessment: assessment
|
|
1210
|
+
? {
|
|
1211
|
+
...assessment,
|
|
1212
|
+
mode,
|
|
1213
|
+
accepted: false,
|
|
1214
|
+
}
|
|
1215
|
+
: null,
|
|
1216
|
+
};
|
|
1217
|
+
if (
|
|
1218
|
+
mode !== 'enforce'
|
|
1219
|
+
|| result.verdict !== 'unknown'
|
|
1220
|
+
|| !assessment
|
|
1221
|
+
|| assessment.verdict === 'unknown'
|
|
1222
|
+
) return base;
|
|
1223
|
+
if (
|
|
1224
|
+
assessment.evidenceNodeIds.length === 0
|
|
1225
|
+
|| !assessment.evidenceNodeIds.every((nodeId) =>
|
|
1226
|
+
liveEvidenceNodeIds.has(nodeId))
|
|
1227
|
+
) {
|
|
1228
|
+
return {
|
|
1229
|
+
...base,
|
|
1230
|
+
unknowns: uniqueStrings([
|
|
1231
|
+
...base.unknowns,
|
|
1232
|
+
'semantic verification was not accepted because its evidence IDs were not backed by live observations',
|
|
1233
|
+
]),
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
return {
|
|
1237
|
+
...base,
|
|
1238
|
+
verdict: assessment.verdict,
|
|
1239
|
+
contradictions:
|
|
1240
|
+
assessment.verdict === 'contradicted'
|
|
1241
|
+
? uniqueStrings([
|
|
1242
|
+
...base.contradictions,
|
|
1243
|
+
`Semantic assessment contradicted the behavior: ${assessment.rationale}`,
|
|
1244
|
+
], 30)
|
|
1245
|
+
: base.contradictions,
|
|
1246
|
+
semanticAssessment: {
|
|
1247
|
+
...base.semanticAssessment,
|
|
1248
|
+
accepted: true,
|
|
1249
|
+
},
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
|
|
912
1253
|
export function verifyBehaviorEvidence({
|
|
913
1254
|
plan,
|
|
914
1255
|
checks,
|
|
@@ -924,82 +1265,154 @@ export function verifyBehaviorEvidence({
|
|
|
924
1265
|
decision,
|
|
925
1266
|
]),
|
|
926
1267
|
);
|
|
1268
|
+
const liveEvidenceNodeIds = new Set(
|
|
1269
|
+
snapshots instanceof Map
|
|
1270
|
+
? [...snapshots.entries()]
|
|
1271
|
+
.filter(([, snapshot]) => Boolean(snapshot))
|
|
1272
|
+
.map(([nodeId]) => nodeId)
|
|
1273
|
+
: Object.entries(snapshots || {})
|
|
1274
|
+
.filter(([, snapshot]) => Boolean(snapshot))
|
|
1275
|
+
.map(([nodeId]) => nodeId),
|
|
1276
|
+
);
|
|
927
1277
|
const verifiedChecks = checks.map((check) => {
|
|
928
1278
|
const behavior = behaviorById.get(check.behaviorId);
|
|
929
1279
|
const decision = mechanismByBehavior.get(check.behaviorId);
|
|
930
|
-
const
|
|
1280
|
+
const state = evidenceState();
|
|
931
1281
|
let nativeEffectEvidence = null;
|
|
932
1282
|
if (!behavior || !decision) {
|
|
933
|
-
|
|
1283
|
+
contradict(state, 'the planned behavior or mechanism was not found');
|
|
1284
|
+
} else if (decision.mechanism === 'native_link') {
|
|
1285
|
+
verifyNativeLink(check, behavior, snapshots, state, context);
|
|
934
1286
|
} else if (decision.mechanism === 'native_overlay') {
|
|
935
|
-
verifyOverlay(check, behavior, snapshots,
|
|
1287
|
+
verifyOverlay(check, behavior, snapshots, state);
|
|
936
1288
|
} else if (decision.mechanism === 'native_component_variants') {
|
|
937
|
-
verifyComponentVariants(check, behavior, snapshots,
|
|
1289
|
+
verifyComponentVariants(check, behavior, snapshots, state, context);
|
|
938
1290
|
} else if (decision.mechanism === 'existing_component_control') {
|
|
939
|
-
verifyExistingComponent(check, behavior, snapshots,
|
|
1291
|
+
verifyExistingComponent(check, behavior, snapshots, state, context);
|
|
940
1292
|
} else if (decision.mechanism === 'code_component') {
|
|
941
|
-
verifyCodeComponent(check, behavior, snapshots,
|
|
1293
|
+
verifyCodeComponent(check, behavior, snapshots, state, context);
|
|
942
1294
|
} else if (decision.mechanism === 'native_effect') {
|
|
943
1295
|
nativeEffectEvidence = verifyNativeEffect(
|
|
944
1296
|
check,
|
|
945
1297
|
behavior,
|
|
946
1298
|
snapshots,
|
|
947
|
-
|
|
1299
|
+
state,
|
|
948
1300
|
);
|
|
949
1301
|
} else {
|
|
950
|
-
verifyNativeEvent(check, behavior, snapshots,
|
|
1302
|
+
verifyNativeEvent(check, behavior, snapshots, state);
|
|
951
1303
|
}
|
|
952
|
-
|
|
1304
|
+
const result = {
|
|
953
1305
|
...check,
|
|
954
1306
|
mechanism: decision?.mechanism,
|
|
955
1307
|
...(nativeEffectEvidence || {}),
|
|
956
|
-
|
|
957
|
-
|
|
1308
|
+
verdict: verdictFor(state),
|
|
1309
|
+
contradictions: uniqueStrings(state.contradictions, 30),
|
|
1310
|
+
unknowns: uniqueStrings(state.unknowns, 30),
|
|
1311
|
+
observations: state.observations.slice(0, 50),
|
|
1312
|
+
repairAllowed: state.contradictions.length > 0,
|
|
1313
|
+
nextSafeActions:
|
|
1314
|
+
state.contradictions.length > 0
|
|
1315
|
+
? ['Repair only the observed contradictory properties, then verify once.']
|
|
1316
|
+
: state.unknowns.length > 0
|
|
1317
|
+
? ['Collect a different live evidence source or provide a grounded semantic assessment; do not mutate based only on unknown evidence.']
|
|
1318
|
+
: [],
|
|
958
1319
|
};
|
|
1320
|
+
return applySemanticAssessment(
|
|
1321
|
+
check,
|
|
1322
|
+
result,
|
|
1323
|
+
['off', 'shadow', 'enforce'].includes(context?.semanticVerifierMode)
|
|
1324
|
+
? context.semanticVerifierMode
|
|
1325
|
+
: 'shadow',
|
|
1326
|
+
liveEvidenceNodeIds,
|
|
1327
|
+
);
|
|
959
1328
|
});
|
|
960
1329
|
|
|
961
1330
|
const behaviorResults = (plan?.behaviors || []).map((behavior) => {
|
|
962
1331
|
const decision = mechanismByBehavior.get(behavior.id);
|
|
963
1332
|
const matching = verifiedChecks.filter((check) =>
|
|
964
1333
|
check.behaviorId === behavior.id);
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1334
|
+
const targetIdsFor = (check) =>
|
|
1335
|
+
decision?.mechanism === 'native_component_variants'
|
|
1336
|
+
|| decision?.mechanism === 'existing_component_control'
|
|
1337
|
+
|| decision?.mechanism === 'code_component'
|
|
1338
|
+
|| (
|
|
1339
|
+
decision?.mechanism === 'native_link'
|
|
1340
|
+
&& check.instanceNodeIds.length > 0
|
|
1341
|
+
)
|
|
1342
|
+
? check.instanceNodeIds
|
|
1343
|
+
: [check.triggerNodeId].filter(Boolean);
|
|
1344
|
+
const observedTargets = new Set(matching.flatMap(targetIdsFor));
|
|
1345
|
+
const verifiedTargets = new Set(
|
|
1346
|
+
matching
|
|
1347
|
+
.filter((check) => check.verdict === 'verified')
|
|
1348
|
+
.flatMap(targetIdsFor),
|
|
1349
|
+
);
|
|
1350
|
+
const contradictedTargets = new Set(
|
|
1351
|
+
matching
|
|
1352
|
+
.filter((check) => check.verdict === 'contradicted')
|
|
1353
|
+
.flatMap(targetIdsFor),
|
|
972
1354
|
);
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1355
|
+
const contradictions = matching
|
|
1356
|
+
.filter((check) => check.verdict === 'contradicted')
|
|
1357
|
+
.flatMap((check) => check.contradictions);
|
|
1358
|
+
const unknowns = matching
|
|
1359
|
+
.filter((check) => check.verdict === 'unknown')
|
|
1360
|
+
.flatMap((check) => check.unknowns);
|
|
1361
|
+
if (matching.length === 0) unknowns.push('no evidence was supplied');
|
|
1362
|
+
if (verifiedTargets.size < behavior.expectedTargets) {
|
|
1363
|
+
unknowns.push(
|
|
1364
|
+
`verified ${verifiedTargets.size} of ${behavior.expectedTargets} expected targets`,
|
|
978
1365
|
);
|
|
979
1366
|
}
|
|
1367
|
+
const verdict =
|
|
1368
|
+
matching.some((check) => check.verdict === 'contradicted')
|
|
1369
|
+
? 'contradicted'
|
|
1370
|
+
: matching.some((check) => check.verdict === 'unknown')
|
|
1371
|
+
|| unknowns.length > 0
|
|
1372
|
+
? 'unknown'
|
|
1373
|
+
: 'verified';
|
|
980
1374
|
return {
|
|
981
1375
|
behaviorId: behavior.id,
|
|
982
1376
|
behaviorSignature: behavior.signature,
|
|
983
1377
|
mechanism: decision?.mechanism,
|
|
984
1378
|
expectedTargets: behavior.expectedTargets,
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1379
|
+
observedTargets: observedTargets.size,
|
|
1380
|
+
verifiedTargets: verifiedTargets.size,
|
|
1381
|
+
contradictedTargets: contradictedTargets.size,
|
|
1382
|
+
unknownTargets: Math.max(
|
|
1383
|
+
0,
|
|
1384
|
+
behavior.expectedTargets
|
|
1385
|
+
- verifiedTargets.size
|
|
1386
|
+
- contradictedTargets.size,
|
|
1387
|
+
),
|
|
1388
|
+
verdict,
|
|
1389
|
+
contradictions: uniqueStrings(contradictions, 50),
|
|
1390
|
+
unknowns: uniqueStrings(unknowns, 50),
|
|
988
1391
|
};
|
|
989
1392
|
});
|
|
990
1393
|
const representativeBehaviorIds = behaviorResults
|
|
991
1394
|
.filter((result) =>
|
|
992
1395
|
verifiedChecks.some((check) =>
|
|
993
|
-
check.behaviorId === result.behaviorId
|
|
1396
|
+
check.behaviorId === result.behaviorId
|
|
1397
|
+
&& check.verdict === 'verified'))
|
|
994
1398
|
.map((result) => result.behaviorId);
|
|
995
1399
|
const completed =
|
|
996
1400
|
behaviorResults.length > 0
|
|
997
|
-
&& behaviorResults.every((result) => result.
|
|
1401
|
+
&& behaviorResults.every((result) => result.verdict === 'verified');
|
|
1402
|
+
const hasContradictions = behaviorResults.some((result) =>
|
|
1403
|
+
result.verdict === 'contradicted');
|
|
1404
|
+
const usedSemanticAssessment = verifiedChecks.some((check) =>
|
|
1405
|
+
check.semanticAssessment?.accepted === true);
|
|
998
1406
|
return {
|
|
999
1407
|
completed,
|
|
1000
|
-
verificationLevel:
|
|
1001
|
-
|
|
1002
|
-
|
|
1408
|
+
verificationLevel:
|
|
1409
|
+
completed && usedSemanticAssessment
|
|
1410
|
+
? 'semantically_verified'
|
|
1411
|
+
: completed
|
|
1412
|
+
? 'structurally_verified'
|
|
1413
|
+
: hasContradictions
|
|
1414
|
+
? 'contradicted'
|
|
1415
|
+
: 'unknown',
|
|
1003
1416
|
checks: verifiedChecks,
|
|
1004
1417
|
behaviors: behaviorResults,
|
|
1005
1418
|
representativeBehaviorIds,
|