kibi-core 0.18.2 → 0.20.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/package.json +1 -1
- package/src/discovery.pl +449 -1
- package/src/kb.pl +150 -0
package/package.json
CHANGED
package/src/discovery.pl
CHANGED
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
requirement_proof_json/7,
|
|
13
13
|
symbol_proof_json/7,
|
|
14
14
|
graph_expand_json/8,
|
|
15
|
-
search_answer_verdicts_json/2
|
|
15
|
+
search_answer_verdicts_json/2,
|
|
16
|
+
ask_predicate_json/9,
|
|
17
|
+
ask_property_json/6,
|
|
18
|
+
ask_schemas_json/5,
|
|
19
|
+
ask_fact_kind_json/4
|
|
16
20
|
]).
|
|
17
21
|
|
|
18
22
|
% Source contains non-ASCII text; do not depend on the host locale.
|
|
@@ -471,6 +475,450 @@ graph_expand_json(SeedIds, Relationships, Direction, Depth, EntityTypes, MaxNode
|
|
|
471
475
|
Response = _{nodes: Nodes, edges: Edges, truncated: Truncated, meta: Meta},
|
|
472
476
|
dict_json_string(Response, JsonString).
|
|
473
477
|
|
|
478
|
+
%% ask_predicate_json(+Namespace, +Name, +ArgSpec, +PolarityFilter, +Scope, +DistinctArg, +Limit, +Offset, -JsonString)
|
|
479
|
+
% Typed query over ontology predicate facts (fact_kind: predicate). Namespace
|
|
480
|
+
% `none` matches any namespace. ArgSpec is either a positional list of
|
|
481
|
+
% constants where the atom '$any' is a wildcard ([] matches any arguments),
|
|
482
|
+
% or where(Pairs) with Name=Value entries resolved through the predicate's
|
|
483
|
+
% declared argument names (schema or built-in signature). PolarityFilter is
|
|
484
|
+
% `none`, assert or deny; Scope `current_requirements` keeps only facts that a
|
|
485
|
+
% current requirement requires; DistinctArg names a declared argument whose
|
|
486
|
+
% distinct values are counted. Rows carry the effective allow/deny decision
|
|
487
|
+
% when the predicate is a decision predicate. An empty answer lists, per
|
|
488
|
+
% bound argument, the values that do exist (suggestions). Read-only.
|
|
489
|
+
% implements REQ-kibi-query-fact-filters
|
|
490
|
+
ask_predicate_json(Namespace, Name, ArgSpec, PolarityFilter, Scope, DistinctArg, Limit, Offset, JsonString) :-
|
|
491
|
+
ask_namespace(Namespace, NamespaceMatch),
|
|
492
|
+
% The schema lookup may bind a namespace; an unnamed namespace must keep
|
|
493
|
+
% matching facts in every namespace, so the schema sees a copy.
|
|
494
|
+
copy_term(NamespaceMatch, SchemaNamespace),
|
|
495
|
+
ask_schema_dict(SchemaNamespace, Name, SchemaDict, ArgumentNames),
|
|
496
|
+
ask_arg_pattern(ArgSpec, ArgumentNames, ArgPattern, PatternWarnings),
|
|
497
|
+
( PatternWarnings == []
|
|
498
|
+
-> findall(Row,
|
|
499
|
+
ask_predicate_row(NamespaceMatch, Name, ArgPattern, PolarityFilter, Scope, ArgumentNames, Row),
|
|
500
|
+
Rows0)
|
|
501
|
+
; Rows0 = []
|
|
502
|
+
),
|
|
503
|
+
sort_dict_rows(Rows0, SortedRows),
|
|
504
|
+
length(SortedRows, Count),
|
|
505
|
+
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
506
|
+
ask_distinct(DistinctArg, ArgumentNames, SortedRows, Distinct, DistinctWarnings),
|
|
507
|
+
append(PatternWarnings, DistinctWarnings, Warnings),
|
|
508
|
+
ask_suggestions(Count, NamespaceMatch, Name, ArgSpec, ArgumentNames, Suggestions),
|
|
509
|
+
ask_spec_json(ArgSpec, ArgPattern, SpecJson),
|
|
510
|
+
ask_optional_atom(Namespace, NamespaceJson),
|
|
511
|
+
status_meta_dict(Meta),
|
|
512
|
+
Response = _{
|
|
513
|
+
mode: predicate,
|
|
514
|
+
predicate: _{namespace: NamespaceJson, name: Name, args: SpecJson},
|
|
515
|
+
schema: SchemaDict,
|
|
516
|
+
count: Count,
|
|
517
|
+
rows: Rows,
|
|
518
|
+
distinct: Distinct,
|
|
519
|
+
suggestions: Suggestions,
|
|
520
|
+
warnings: Warnings,
|
|
521
|
+
meta: Meta
|
|
522
|
+
},
|
|
523
|
+
dict_json_string(Response, JsonString).
|
|
524
|
+
|
|
525
|
+
%% ask_arg_pattern(+ArgSpec, +ArgumentNames, -ArgPattern, -Warnings)
|
|
526
|
+
% A positional spec is used as is. A where(Pairs) spec becomes a positional
|
|
527
|
+
% pattern over the declared argument names; an undeclared name yields a
|
|
528
|
+
% warning that lists the declared names and no rows.
|
|
529
|
+
ask_arg_pattern(where(Pairs), ArgumentNames, ArgPattern, Warnings) :-
|
|
530
|
+
!,
|
|
531
|
+
( is_list(ArgumentNames)
|
|
532
|
+
-> findall(Warning,
|
|
533
|
+
( member(Key=_, Pairs),
|
|
534
|
+
\+ memberchk(Key, ArgumentNames),
|
|
535
|
+
atomic_list_concat(ArgumentNames, ', ', Declared),
|
|
536
|
+
format(atom(Warning), "'~w' is not an argument of this predicate; declared arguments: ~w", [Key, Declared])
|
|
537
|
+
),
|
|
538
|
+
Warnings),
|
|
539
|
+
findall(Entry,
|
|
540
|
+
( member(ArgName, ArgumentNames),
|
|
541
|
+
( memberchk(ArgName=Value, Pairs)
|
|
542
|
+
-> Entry = Value
|
|
543
|
+
; Entry = '$any'
|
|
544
|
+
)
|
|
545
|
+
),
|
|
546
|
+
ArgPattern)
|
|
547
|
+
; Pairs == []
|
|
548
|
+
-> ArgPattern = [],
|
|
549
|
+
Warnings = []
|
|
550
|
+
; ArgPattern = [],
|
|
551
|
+
Warnings = ["named arguments need a predicate schema; none declares this predicate, so pass positional args instead"]
|
|
552
|
+
).
|
|
553
|
+
ask_arg_pattern(ArgPattern, ArgumentNames, ArgPattern, Warnings) :-
|
|
554
|
+
( is_list(ArgumentNames),
|
|
555
|
+
ArgPattern \== [],
|
|
556
|
+
length(ArgPattern, Given),
|
|
557
|
+
length(ArgumentNames, Arity),
|
|
558
|
+
Given =\= Arity
|
|
559
|
+
-> format(atom(Warning), "positional args need one entry per argument (~w declared, ~w given); use null for any value or name them with where", [Arity, Given]),
|
|
560
|
+
Warnings = [Warning]
|
|
561
|
+
; Warnings = []
|
|
562
|
+
).
|
|
563
|
+
|
|
564
|
+
ask_spec_json(where(Pairs), _ArgPattern, Json) :-
|
|
565
|
+
!,
|
|
566
|
+
findall(Key-Value, member(Key=Value, Pairs), KeyValues),
|
|
567
|
+
dict_pairs(Json, _, KeyValues).
|
|
568
|
+
ask_spec_json(_ArgSpec, ArgPattern, Json) :-
|
|
569
|
+
ask_pattern_json(ArgPattern, Json).
|
|
570
|
+
|
|
571
|
+
%% ask_suggestions(+Count, +Namespace, +Name, +ArgSpec, +ArgumentNames, -Suggestions)
|
|
572
|
+
% When nothing matched a where(...) spec, list for each bound argument the
|
|
573
|
+
% values that do exist for this predicate so the caller can correct the
|
|
574
|
+
% question instead of reading "no results".
|
|
575
|
+
ask_suggestions(0, Namespace, Name, where(Pairs), ArgumentNames, Suggestions) :-
|
|
576
|
+
is_list(ArgumentNames),
|
|
577
|
+
Pairs \== [],
|
|
578
|
+
findall(Key-Values,
|
|
579
|
+
( member(Key=_, Pairs),
|
|
580
|
+
nth1(Position, ArgumentNames, Key),
|
|
581
|
+
findall(Value,
|
|
582
|
+
( kb:predicate_fact(_, Namespace, Name, Args, _),
|
|
583
|
+
nth1(Position, Args, Value)
|
|
584
|
+
),
|
|
585
|
+
Values0),
|
|
586
|
+
sort(Values0, Values)
|
|
587
|
+
),
|
|
588
|
+
Entries),
|
|
589
|
+
Entries \== [],
|
|
590
|
+
!,
|
|
591
|
+
dict_pairs(Suggestions, _, Entries).
|
|
592
|
+
ask_suggestions(_Count, _Namespace, _Name, _ArgSpec, _ArgumentNames, null).
|
|
593
|
+
|
|
594
|
+
ask_namespace(none, _) :- !.
|
|
595
|
+
ask_namespace(Namespace, Namespace).
|
|
596
|
+
|
|
597
|
+
ask_optional_atom(none, null) :- !.
|
|
598
|
+
ask_optional_atom(Atom, Atom).
|
|
599
|
+
|
|
600
|
+
ask_pattern_json([], []) :- !.
|
|
601
|
+
ask_pattern_json(Pattern, Json) :-
|
|
602
|
+
maplist(ask_pattern_entry_json, Pattern, Json).
|
|
603
|
+
|
|
604
|
+
ask_pattern_entry_json('$any', null) :- !.
|
|
605
|
+
ask_pattern_entry_json(Value, Value).
|
|
606
|
+
|
|
607
|
+
% Arguments of a stored fact unify with the requested pattern; '$any' entries
|
|
608
|
+
% become fresh variables and an empty pattern accepts any arity.
|
|
609
|
+
ask_pattern_args([], _Args) :- !.
|
|
610
|
+
ask_pattern_args(Pattern, Args) :-
|
|
611
|
+
maplist(ask_pattern_arg, Pattern, Args).
|
|
612
|
+
|
|
613
|
+
ask_pattern_arg('$any', _) :- !.
|
|
614
|
+
ask_pattern_arg(Value, Arg) :-
|
|
615
|
+
kb:normalize_term_atom(Value, Arg).
|
|
616
|
+
|
|
617
|
+
ask_polarity_match(none, _) :- !.
|
|
618
|
+
ask_polarity_match(Polarity, Polarity).
|
|
619
|
+
|
|
620
|
+
% implements REQ-kibi-query-fact-filters
|
|
621
|
+
ask_predicate_row(Namespace, Name, ArgPattern, PolarityFilter, Scope, ArgumentNames, Row) :-
|
|
622
|
+
kb:predicate_fact(FactId, Namespace, Name, Args, Polarity),
|
|
623
|
+
ask_pattern_args(ArgPattern, Args),
|
|
624
|
+
ask_polarity_match(PolarityFilter, Polarity),
|
|
625
|
+
ask_fact_requirements(FactId, requires_predicate, Requirements, HasCurrent),
|
|
626
|
+
ask_scope_match(Scope, HasCurrent),
|
|
627
|
+
ask_fact_text(FactId, Title, Status, ClaimText, CanonicalKey),
|
|
628
|
+
ask_named_arguments(ArgumentNames, Args, NamedArguments),
|
|
629
|
+
ask_effect(Namespace, Name, Args, Polarity, Effect),
|
|
630
|
+
Row = _{
|
|
631
|
+
id: FactId,
|
|
632
|
+
type: fact,
|
|
633
|
+
title: Title,
|
|
634
|
+
status: Status,
|
|
635
|
+
namespace: Namespace,
|
|
636
|
+
name: Name,
|
|
637
|
+
args: Args,
|
|
638
|
+
arguments: NamedArguments,
|
|
639
|
+
polarity: Polarity,
|
|
640
|
+
effect: Effect,
|
|
641
|
+
canonicalKey: CanonicalKey,
|
|
642
|
+
claimText: ClaimText,
|
|
643
|
+
requirements: Requirements
|
|
644
|
+
}.
|
|
645
|
+
|
|
646
|
+
ask_scope_match(current_requirements, true) :- !.
|
|
647
|
+
ask_scope_match(current_requirements, _) :- !, fail.
|
|
648
|
+
ask_scope_match(_Scope, _HasCurrent).
|
|
649
|
+
|
|
650
|
+
%% ask_fact_requirements(+FactId, +Relationship, -Requirements, -HasCurrent)
|
|
651
|
+
% Requirements linked to the fact through Relationship, each with whether it
|
|
652
|
+
% is current (kb:current_req/1); HasCurrent is true when at least one is.
|
|
653
|
+
ask_fact_requirements(FactId, Relationship, Requirements, HasCurrent) :-
|
|
654
|
+
findall(ReqId, kb_relationship(Relationship, ReqId, FactId), ReqIds0),
|
|
655
|
+
sort(ReqIds0, ReqIds),
|
|
656
|
+
maplist(ask_requirement_dict, ReqIds, Requirements),
|
|
657
|
+
( member(Dict, Requirements), Dict.current == true
|
|
658
|
+
-> HasCurrent = true
|
|
659
|
+
; HasCurrent = false
|
|
660
|
+
).
|
|
661
|
+
|
|
662
|
+
ask_requirement_dict(ReqId, _{id: ReqId, status: Status, current: Current}) :-
|
|
663
|
+
( kb_entity(ReqId, req, Props), memberchk(status=StatusRaw, Props)
|
|
664
|
+
-> source_value_atom(StatusRaw, Status)
|
|
665
|
+
; Status = ''
|
|
666
|
+
),
|
|
667
|
+
( kb:current_req(ReqId) -> Current = true ; Current = false ).
|
|
668
|
+
|
|
669
|
+
ask_fact_text(FactId, Title, Status, ClaimText, CanonicalKey) :-
|
|
670
|
+
kb_entity(FactId, fact, Props),
|
|
671
|
+
( memberchk(title=TitleRaw, Props) -> source_value_atom(TitleRaw, Title) ; Title = '' ),
|
|
672
|
+
( memberchk(status=StatusRaw, Props) -> source_value_atom(StatusRaw, Status) ; Status = '' ),
|
|
673
|
+
( memberchk(claim_text=ClaimRaw, Props) -> source_value_atom(ClaimRaw, ClaimText) ; ClaimText = '' ),
|
|
674
|
+
( memberchk(canonical_key=KeyRaw, Props) -> source_value_atom(KeyRaw, CanonicalKey) ; CanonicalKey = '' ).
|
|
675
|
+
|
|
676
|
+
% Named arguments follow the schema when it exists and has the same arity;
|
|
677
|
+
% otherwise the row carries only the positional list.
|
|
678
|
+
ask_named_arguments(ArgumentNames, Args, Named) :-
|
|
679
|
+
is_list(ArgumentNames),
|
|
680
|
+
same_length(ArgumentNames, Args),
|
|
681
|
+
!,
|
|
682
|
+
pairs_keys_values(Pairs, ArgumentNames, Args),
|
|
683
|
+
dict_pairs(Named, _, Pairs).
|
|
684
|
+
ask_named_arguments(_ArgumentNames, _Args, null).
|
|
685
|
+
|
|
686
|
+
% The effective allow/deny decision of a decision predicate (a schema with
|
|
687
|
+
% key_arguments and a decision argument, or the built-in permission_rule).
|
|
688
|
+
ask_effect(Namespace, Name, Args, Polarity, Effect) :-
|
|
689
|
+
length(Args, Arity),
|
|
690
|
+
kb:decision_predicate(Namespace, Name, Arity, _KeyPositions, DecisionPosition),
|
|
691
|
+
nth1(DecisionPosition, Args, Decision),
|
|
692
|
+
kb:effective_decision(Decision, Polarity, Effect0),
|
|
693
|
+
!,
|
|
694
|
+
Effect = Effect0.
|
|
695
|
+
ask_effect(_Namespace, _Name, _Args, _Polarity, null).
|
|
696
|
+
|
|
697
|
+
%% ask_schema_dict(?Namespace, +Name, -SchemaDict, -ArgumentNames)
|
|
698
|
+
% The project predicate_schema for the predicate, the built-in decision
|
|
699
|
+
% signature, or null when neither exists.
|
|
700
|
+
ask_schema_dict(Namespace, Name, SchemaDict, ArgumentNames) :-
|
|
701
|
+
kb:predicate_schema(SchemaId, Namespace, Name, Arity, ArgumentNames, ArgumentTypes),
|
|
702
|
+
!,
|
|
703
|
+
ask_schema_row(SchemaId, Namespace, Name, Arity, ArgumentNames, ArgumentTypes, SchemaDict).
|
|
704
|
+
ask_schema_dict(Namespace, Name, SchemaDict, ArgumentNames) :-
|
|
705
|
+
kb:builtin_decision_predicate(Namespace, Name, Arity, KeyPositions, DecisionPosition),
|
|
706
|
+
!,
|
|
707
|
+
ask_builtin_schema_row(Namespace, Name, Arity, KeyPositions, DecisionPosition, SchemaDict),
|
|
708
|
+
ArgumentNames = SchemaDict.argumentNames.
|
|
709
|
+
ask_schema_dict(_Namespace, _Name, null, none).
|
|
710
|
+
|
|
711
|
+
ask_builtin_schema_row(Namespace, Name, Arity, KeyPositions, DecisionPosition, Row) :-
|
|
712
|
+
builtin_argument_names(Name, Names),
|
|
713
|
+
findall(Key, (member(P, KeyPositions), nth1(P, Names, Key)), KeyArguments),
|
|
714
|
+
nth1(DecisionPosition, Names, DecisionArgument),
|
|
715
|
+
aggregate_all(count, kb:predicate_fact(_, Namespace, Name, _, _), FactCount),
|
|
716
|
+
Row = _{
|
|
717
|
+
id: null,
|
|
718
|
+
builtin: true,
|
|
719
|
+
namespace: Namespace,
|
|
720
|
+
name: Name,
|
|
721
|
+
arity: Arity,
|
|
722
|
+
argumentNames: Names,
|
|
723
|
+
argumentTypes: Names,
|
|
724
|
+
keyArguments: KeyArguments,
|
|
725
|
+
decisionArgument: DecisionArgument,
|
|
726
|
+
argumentConstants: null,
|
|
727
|
+
examples: [],
|
|
728
|
+
factCount: FactCount
|
|
729
|
+
}.
|
|
730
|
+
|
|
731
|
+
% Keep in step with builtin_decision_predicate/5 in kb.pl.
|
|
732
|
+
builtin_argument_names(permission_rule, [actor, action, resource, decision]).
|
|
733
|
+
|
|
734
|
+
ask_schema_row(SchemaId, Namespace, Name, Arity, ArgumentNames, ArgumentTypes, Row) :-
|
|
735
|
+
kb_entity(SchemaId, fact, Props),
|
|
736
|
+
( memberchk(key_arguments=KeysRaw, Props)
|
|
737
|
+
-> kb:normalize_term_atom_list(KeysRaw, KeyArguments)
|
|
738
|
+
; KeyArguments = []
|
|
739
|
+
),
|
|
740
|
+
( kb:decision_predicate(Namespace, Name, Arity, _, DecisionPosition),
|
|
741
|
+
nth1(DecisionPosition, ArgumentNames, DecisionArgument0)
|
|
742
|
+
-> DecisionArgument = DecisionArgument0
|
|
743
|
+
; DecisionArgument = null
|
|
744
|
+
),
|
|
745
|
+
( memberchk(argument_constants=ConstantsRaw, Props),
|
|
746
|
+
ask_json_value(ConstantsRaw, Constants0)
|
|
747
|
+
-> Constants = Constants0
|
|
748
|
+
; Constants = null
|
|
749
|
+
),
|
|
750
|
+
( memberchk(examples=ExamplesRaw, Props), is_list(ExamplesRaw)
|
|
751
|
+
-> maplist(source_value_atom, ExamplesRaw, Examples)
|
|
752
|
+
; Examples = []
|
|
753
|
+
),
|
|
754
|
+
aggregate_all(count, kb:predicate_fact(_, Namespace, Name, _, _), FactCount),
|
|
755
|
+
Row = _{
|
|
756
|
+
id: SchemaId,
|
|
757
|
+
builtin: false,
|
|
758
|
+
namespace: Namespace,
|
|
759
|
+
name: Name,
|
|
760
|
+
arity: Arity,
|
|
761
|
+
argumentNames: ArgumentNames,
|
|
762
|
+
argumentTypes: ArgumentTypes,
|
|
763
|
+
keyArguments: KeyArguments,
|
|
764
|
+
decisionArgument: DecisionArgument,
|
|
765
|
+
argumentConstants: Constants,
|
|
766
|
+
examples: Examples,
|
|
767
|
+
factCount: FactCount
|
|
768
|
+
}.
|
|
769
|
+
|
|
770
|
+
ask_json_value(^^(Value, _Type), Parsed) :- !, ask_json_value(Value, Parsed).
|
|
771
|
+
ask_json_value(literal(Value), Parsed) :- !, ask_json_value(Value, Parsed).
|
|
772
|
+
ask_json_value(Dict, Dict) :- is_dict(Dict), !.
|
|
773
|
+
ask_json_value(Raw, Parsed) :-
|
|
774
|
+
(atom(Raw) ; string(Raw)),
|
|
775
|
+
catch(atom_json_dict(Raw, Parsed, [value_string_as(string)]), _, fail).
|
|
776
|
+
|
|
777
|
+
%% ask_distinct(+DistinctArg, +ArgumentNames, +Rows, -Distinct, -Warnings)
|
|
778
|
+
ask_distinct(none, _ArgumentNames, _Rows, null, []) :- !.
|
|
779
|
+
ask_distinct(DistinctArg, ArgumentNames, Rows, Distinct, []) :-
|
|
780
|
+
is_list(ArgumentNames),
|
|
781
|
+
nth1(Position, ArgumentNames, DistinctArg),
|
|
782
|
+
!,
|
|
783
|
+
findall(Value, (member(Row, Rows), nth1(Position, Row.args, Value)), Values0),
|
|
784
|
+
msort(Values0, Values),
|
|
785
|
+
clumped(Values, Clumps),
|
|
786
|
+
findall(_{value: Value, count: Count}, member(Value-Count, Clumps), Entries),
|
|
787
|
+
Distinct = _{argument: DistinctArg, values: Entries}.
|
|
788
|
+
ask_distinct(DistinctArg, _ArgumentNames, _Rows, null, [Warning]) :-
|
|
789
|
+
format(atom(Warning), "distinct argument '~w' is not a declared argument of this predicate; no schema names it", [DistinctArg]).
|
|
790
|
+
|
|
791
|
+
%% ask_property_json(+SubjectKey, +PropertyKey, +Scope, +Limit, +Offset, -JsonString)
|
|
792
|
+
% Typed query over strict property_value facts of one subject key.
|
|
793
|
+
% PropertyKey `none` matches every property of the subject.
|
|
794
|
+
% implements REQ-kibi-query-fact-filters
|
|
795
|
+
ask_property_json(SubjectKey, PropertyKey, Scope, Limit, Offset, JsonString) :-
|
|
796
|
+
kb:normalize_term_atom(SubjectKey, Subject),
|
|
797
|
+
ask_namespace(PropertyKey, PropertyMatch),
|
|
798
|
+
findall(Row, ask_property_row(Subject, PropertyMatch, Scope, Row), Rows0),
|
|
799
|
+
sort_dict_rows(Rows0, SortedRows),
|
|
800
|
+
length(SortedRows, Count),
|
|
801
|
+
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
802
|
+
findall(SubjectId, kb:fact_subject_key(SubjectId, Subject), SubjectIds0),
|
|
803
|
+
sort(SubjectIds0, SubjectIds),
|
|
804
|
+
ask_optional_atom(PropertyKey, PropertyJson),
|
|
805
|
+
status_meta_dict(Meta),
|
|
806
|
+
Response = _{
|
|
807
|
+
mode: property,
|
|
808
|
+
property: _{subjectKey: Subject, propertyKey: PropertyJson},
|
|
809
|
+
subjectFacts: SubjectIds,
|
|
810
|
+
count: Count,
|
|
811
|
+
rows: Rows,
|
|
812
|
+
warnings: [],
|
|
813
|
+
meta: Meta
|
|
814
|
+
},
|
|
815
|
+
dict_json_string(Response, JsonString).
|
|
816
|
+
|
|
817
|
+
ask_property_row(Subject, PropertyMatch, Scope, Row) :-
|
|
818
|
+
kb:fact_property_tuple(FactId, Subject, Property, Op, ValType, Value, Unit, PropScope, Polarity),
|
|
819
|
+
( PropertyMatch == Property -> true ; var(PropertyMatch) ),
|
|
820
|
+
ask_fact_requirements(FactId, requires_property, Requirements, HasCurrent),
|
|
821
|
+
ask_scope_match(Scope, HasCurrent),
|
|
822
|
+
ask_fact_text(FactId, Title, Status, ClaimText, CanonicalKey),
|
|
823
|
+
ask_property_value_json(Value, ValueJson),
|
|
824
|
+
Row = _{
|
|
825
|
+
id: FactId,
|
|
826
|
+
type: fact,
|
|
827
|
+
title: Title,
|
|
828
|
+
status: Status,
|
|
829
|
+
subjectKey: Subject,
|
|
830
|
+
propertyKey: Property,
|
|
831
|
+
operator: Op,
|
|
832
|
+
valueType: ValType,
|
|
833
|
+
value: ValueJson,
|
|
834
|
+
unit: Unit,
|
|
835
|
+
scope: PropScope,
|
|
836
|
+
polarity: Polarity,
|
|
837
|
+
canonicalKey: CanonicalKey,
|
|
838
|
+
claimText: ClaimText,
|
|
839
|
+
requirements: Requirements
|
|
840
|
+
}.
|
|
841
|
+
|
|
842
|
+
ask_property_value_json(Value, Value) :- number(Value), !.
|
|
843
|
+
ask_property_value_json(Value, Json) :- source_value_atom(Value, Json).
|
|
844
|
+
|
|
845
|
+
%% ask_schemas_json(+Namespace, +Name, +Limit, +Offset, -JsonString)
|
|
846
|
+
% Catalog of the predicates an agent can ask about: every project
|
|
847
|
+
% predicate_schema fact plus built-in decision predicates without a project
|
|
848
|
+
% schema, with argument names, constants, key arguments and fact counts.
|
|
849
|
+
% implements REQ-kibi-query-fact-filters
|
|
850
|
+
ask_schemas_json(Namespace, Name, Limit, Offset, JsonString) :-
|
|
851
|
+
ask_namespace(Namespace, NamespaceMatch),
|
|
852
|
+
ask_namespace(Name, NameMatch),
|
|
853
|
+
findall(Row,
|
|
854
|
+
( kb:predicate_schema(SchemaId, NamespaceMatch, NameMatch, Arity, ArgumentNames, ArgumentTypes),
|
|
855
|
+
ask_schema_row(SchemaId, NamespaceMatch, NameMatch, Arity, ArgumentNames, ArgumentTypes, Row)
|
|
856
|
+
),
|
|
857
|
+
SchemaRows),
|
|
858
|
+
findall(Row,
|
|
859
|
+
( kb:builtin_decision_predicate(NamespaceMatch, NameMatch, Arity, KeyPositions, DecisionPosition),
|
|
860
|
+
\+ kb:predicate_schema(_, NamespaceMatch, NameMatch, Arity, _, _),
|
|
861
|
+
ask_builtin_schema_row(NamespaceMatch, NameMatch, Arity, KeyPositions, DecisionPosition, Row)
|
|
862
|
+
),
|
|
863
|
+
BuiltinRows),
|
|
864
|
+
append(SchemaRows, BuiltinRows, Rows0),
|
|
865
|
+
map_list_to_pairs(ask_schema_sort_key, Rows0, Pairs),
|
|
866
|
+
keysort(Pairs, SortedPairs),
|
|
867
|
+
pairs_values(SortedPairs, SortedRows),
|
|
868
|
+
length(SortedRows, Count),
|
|
869
|
+
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
870
|
+
status_meta_dict(Meta),
|
|
871
|
+
Response = _{mode: schemas, count: Count, rows: Rows, warnings: [], meta: Meta},
|
|
872
|
+
dict_json_string(Response, JsonString).
|
|
873
|
+
|
|
874
|
+
ask_schema_sort_key(Row, Key) :-
|
|
875
|
+
format(atom(Key), '~w:~w/~w', [Row.namespace, Row.name, Row.arity]).
|
|
876
|
+
|
|
877
|
+
%% ask_fact_kind_json(+Kind, +Limit, +Offset, -JsonString)
|
|
878
|
+
% Facts of one fact_kind (subject, property_value, predicate, observation,
|
|
879
|
+
% meta, rule, rule_schema) with their identifying typed fields only, so a
|
|
880
|
+
% caller can see what is modeled without paging full entities.
|
|
881
|
+
% implements REQ-kibi-query-fact-filters
|
|
882
|
+
ask_fact_kind_json(Kind, Limit, Offset, JsonString) :-
|
|
883
|
+
findall(Row, ask_fact_kind_row(Kind, Row), Rows0),
|
|
884
|
+
sort_dict_rows(Rows0, SortedRows),
|
|
885
|
+
length(SortedRows, Count),
|
|
886
|
+
paginate_rows(SortedRows, Offset, Limit, Rows),
|
|
887
|
+
status_meta_dict(Meta),
|
|
888
|
+
Response = _{mode: fact_kind, factKind: Kind, count: Count, rows: Rows, warnings: [], meta: Meta},
|
|
889
|
+
dict_json_string(Response, JsonString).
|
|
890
|
+
|
|
891
|
+
ask_fact_kind_row(Kind, Row) :-
|
|
892
|
+
kb_entity(FactId, fact, Props),
|
|
893
|
+
memberchk(fact_kind=KindRaw, Props),
|
|
894
|
+
kb:normalize_term_atom(KindRaw, Kind),
|
|
895
|
+
ask_fact_text(FactId, Title, Status, ClaimText, CanonicalKey),
|
|
896
|
+
findall(Key-Value,
|
|
897
|
+
( member(Key, [subject_key, property_key, operator, value_type, unit, predicate_namespace, predicate_name, predicate_args, polarity]),
|
|
898
|
+
memberchk(Key=Raw, Props),
|
|
899
|
+
ask_typed_value(Raw, Value)
|
|
900
|
+
),
|
|
901
|
+
Typed),
|
|
902
|
+
( Kind == property_value
|
|
903
|
+
-> ( memberchk(value_type=ValTypeRaw, Props)
|
|
904
|
+
-> kb:normalize_term_atom(ValTypeRaw, ValType)
|
|
905
|
+
; ValType = string
|
|
906
|
+
),
|
|
907
|
+
kb:value_from_props(Props, ValType, Value0),
|
|
908
|
+
ask_property_value_json(Value0, PropertyValue),
|
|
909
|
+
ValuePairs = [value-PropertyValue]
|
|
910
|
+
; ValuePairs = []
|
|
911
|
+
),
|
|
912
|
+
append(Typed, ValuePairs, Fields),
|
|
913
|
+
dict_pairs(Row, _, [id-FactId, type-fact, title-Title, status-Status, factKind-Kind, canonicalKey-CanonicalKey, claimText-ClaimText | Fields]).
|
|
914
|
+
|
|
915
|
+
ask_typed_value(Raw, Value) :-
|
|
916
|
+
is_list(Raw),
|
|
917
|
+
!,
|
|
918
|
+
kb:normalize_term_atom_list(Raw, Value).
|
|
919
|
+
ask_typed_value(Raw, Value) :-
|
|
920
|
+
source_value_atom(Raw, Value).
|
|
921
|
+
|
|
474
922
|
matching_gap_row(TypeFilter, MissingRelationships, PresentRelationships, Tags, SourceFilter, Row) :-
|
|
475
923
|
matching_gap_candidate(TypeFilter, MissingRelationships, PresentRelationships, Tags, SourceFilter, Row, Stub),
|
|
476
924
|
gap_row_visible(Tags, Stub).
|
package/src/kb.pl
CHANGED
|
@@ -135,6 +135,7 @@ kb_uri('urn-kibi:').
|
|
|
135
135
|
% the memo never serves a property list the store no longer holds.
|
|
136
136
|
:- dynamic kb_entity_props_memo/2.
|
|
137
137
|
:- dynamic kb_entity_props_memo_key/1.
|
|
138
|
+
:- dynamic kb_decision_signature_memo/2.
|
|
138
139
|
:- dynamic kb_index_verified_key/1.
|
|
139
140
|
:- dynamic entity/4. % Support legacy .pl file format (Type, Id, Title, Props)
|
|
140
141
|
|
|
@@ -361,6 +362,7 @@ kb_detach :-
|
|
|
361
362
|
retractall(kb_index_verified_key(_)),
|
|
362
363
|
retractall(kb_entity_props_memo(_, _)),
|
|
363
364
|
retractall(kb_entity_props_memo_key(_)),
|
|
365
|
+
retractall(kb_decision_signature_memo(_, _)),
|
|
364
366
|
retractall(kb_index_entity(_, _)),
|
|
365
367
|
retractall(kb_index_type(_, _)),
|
|
366
368
|
retractall(kb_index_tag(_, _)),
|
|
@@ -2662,6 +2664,154 @@ req_conflict_witness(ReqA, ReqB, Witness) :-
|
|
|
2662
2664
|
right: Right
|
|
2663
2665
|
}.
|
|
2664
2666
|
|
|
2667
|
+
% implements REQ-kibi-truthful-consistency
|
|
2668
|
+
% Opposite decisions on the same case: two asserted facts of a predicate
|
|
2669
|
+
% whose key arguments determine a closed allow/deny decision argument (the
|
|
2670
|
+
% built-in permission_rule, or a project schema that declares key_arguments
|
|
2671
|
+
% and a `decision` argument) agree on every key argument but decide
|
|
2672
|
+
% differently, e.g. permission_rule(coach, read, x, allow) against
|
|
2673
|
+
% permission_rule(coach, read, x, deny). kb_model writes a denial as
|
|
2674
|
+
% decision deny with polarity deny, so a deny decision counts as a denial
|
|
2675
|
+
% whatever its polarity; an allow decision counts only when asserted.
|
|
2676
|
+
%
|
|
2677
|
+
% Cost: the search answer layer and coverage ask for the witnesses of each
|
|
2678
|
+
% requirement with the other side unbound. The pairing therefore starts
|
|
2679
|
+
% from whichever requirement is bound, filters its facts against the
|
|
2680
|
+
% decision signatures (memoized per KB generation, so the project schemas
|
|
2681
|
+
% are read once rather than once per fact), and pre-binds the partner's key
|
|
2682
|
+
% arguments before enumerating partner requirements.
|
|
2683
|
+
req_conflict_witness(ReqA, ReqB, Witness) :-
|
|
2684
|
+
decision_signatures(Signatures),
|
|
2685
|
+
Signatures \== [],
|
|
2686
|
+
decision_conflict_pair(Signatures, ReqA, ReqB, FactA, FactB, Namespace, Name,
|
|
2687
|
+
ArgsA, ArgsB, PolarityA, PolarityB, KeyPositions, DecisionPosition),
|
|
2688
|
+
length(ArgsA, Arity),
|
|
2689
|
+
nth1(DecisionPosition, ArgsA, DecisionA),
|
|
2690
|
+
nth1(DecisionPosition, ArgsB, DecisionB),
|
|
2691
|
+
DecisionA \== DecisionB,
|
|
2692
|
+
effective_decision(DecisionA, PolarityA, EffectA),
|
|
2693
|
+
effective_decision(DecisionB, PolarityB, EffectB),
|
|
2694
|
+
EffectA \== EffectB,
|
|
2695
|
+
findall(KeyValue, (member(Position, KeyPositions), nth1(Position, ArgsA, KeyValue)), KeyValues),
|
|
2696
|
+
atomic_list_concat(KeyValues, ',', KeyText),
|
|
2697
|
+
format(
|
|
2698
|
+
string(Reason),
|
|
2699
|
+
"Decision conflict on ~w:~w(~w): ~w decides ~w while ~w decides ~w",
|
|
2700
|
+
[Namespace, Name, KeyText, ReqA, EffectA, ReqB, EffectB]
|
|
2701
|
+
),
|
|
2702
|
+
predicate_conflict_side(ReqA, FactA, Namespace, Name, ArgsA, PolarityA, Left),
|
|
2703
|
+
predicate_conflict_side(ReqB, FactB, Namespace, Name, ArgsB, PolarityB, Right),
|
|
2704
|
+
findall(SchemaId, predicate_schema(SchemaId, Namespace, Name, Arity, _Names, _Types), SchemaIds0),
|
|
2705
|
+
sort(SchemaIds0, SchemaIds),
|
|
2706
|
+
Witness = _{
|
|
2707
|
+
kind: predicate,
|
|
2708
|
+
status: contradiction,
|
|
2709
|
+
requirements: [ReqA, ReqB],
|
|
2710
|
+
reason: Reason,
|
|
2711
|
+
predicateNamespace: Namespace,
|
|
2712
|
+
predicateName: Name,
|
|
2713
|
+
predicateArgs: ArgsA,
|
|
2714
|
+
keyArguments: KeyValues,
|
|
2715
|
+
schemaIds: SchemaIds,
|
|
2716
|
+
left: Left,
|
|
2717
|
+
right: Right
|
|
2718
|
+
}.
|
|
2719
|
+
|
|
2720
|
+
%% decision_conflict_pair(+Signatures, ?ReqA, ?ReqB, -FactA, -FactB, -Namespace, -Name, -ArgsA, -ArgsB, -PolarityA, -PolarityB, -KeyPositions, -DecisionPosition)
|
|
2721
|
+
% Two decision-predicate facts of current requirements ReqA @< ReqB that
|
|
2722
|
+
% agree on every key argument. Starts from the bound requirement.
|
|
2723
|
+
decision_conflict_pair(Signatures, ReqA, ReqB, FactA, FactB, Namespace, Name,
|
|
2724
|
+
ArgsA, ArgsB, PolarityA, PolarityB, KeyPositions, DecisionPosition) :-
|
|
2725
|
+
( var(ReqA), nonvar(ReqB)
|
|
2726
|
+
-> decision_req_fact(Signatures, ReqB, FactB, Namespace, Name, ArgsB, PolarityB, KeyPositions, DecisionPosition),
|
|
2727
|
+
length(ArgsB, Arity),
|
|
2728
|
+
length(ArgsA, Arity),
|
|
2729
|
+
bind_key_arguments(KeyPositions, ArgsB, ArgsA),
|
|
2730
|
+
current_req(ReqA),
|
|
2731
|
+
ReqA @< ReqB,
|
|
2732
|
+
effective_req_predicate(ReqA, FactA, Namespace, Name, ArgsA, PolarityA)
|
|
2733
|
+
; decision_req_fact(Signatures, ReqA, FactA, Namespace, Name, ArgsA, PolarityA, KeyPositions, DecisionPosition),
|
|
2734
|
+
length(ArgsA, Arity),
|
|
2735
|
+
length(ArgsB, Arity),
|
|
2736
|
+
bind_key_arguments(KeyPositions, ArgsA, ArgsB),
|
|
2737
|
+
current_req(ReqB),
|
|
2738
|
+
ReqA @< ReqB,
|
|
2739
|
+
effective_req_predicate(ReqB, FactB, Namespace, Name, ArgsB, PolarityB)
|
|
2740
|
+
),
|
|
2741
|
+
FactA \= FactB.
|
|
2742
|
+
|
|
2743
|
+
decision_req_fact(Signatures, ReqId, FactId, Namespace, Name, Args, Polarity, KeyPositions, DecisionPosition) :-
|
|
2744
|
+
current_req(ReqId),
|
|
2745
|
+
effective_req_predicate(ReqId, FactId, Namespace, Name, Args, Polarity),
|
|
2746
|
+
length(Args, Arity),
|
|
2747
|
+
memberchk(decision_signature(Namespace, Name, Arity, KeyPositions, DecisionPosition), Signatures).
|
|
2748
|
+
|
|
2749
|
+
%% decision_predicate(?Namespace, ?Name, ?Arity, -KeyPositions, -DecisionPosition)
|
|
2750
|
+
% A predicate whose key arguments (1-based KeyPositions) determine one
|
|
2751
|
+
% allow/deny decision argument. A project-local predicate_schema decides
|
|
2752
|
+
% this for its own signature: it must declare key_arguments and name a
|
|
2753
|
+
% non-key argument `decision`. Without a project schema the built-in
|
|
2754
|
+
% catalog entry applies.
|
|
2755
|
+
decision_predicate(Namespace, Name, Arity, KeyPositions, DecisionPosition) :-
|
|
2756
|
+
decision_signatures(Signatures),
|
|
2757
|
+
member(decision_signature(Namespace, Name, Arity, KeyPositions, DecisionPosition), Signatures).
|
|
2758
|
+
|
|
2759
|
+
%% decision_signatures(-Signatures)
|
|
2760
|
+
% All decision predicates of the current KB, memoized per graph, RDF
|
|
2761
|
+
% generation and legacy fact count like the entity property memo.
|
|
2762
|
+
decision_signatures(Signatures) :-
|
|
2763
|
+
index_verification_key(decision_signatures, Key),
|
|
2764
|
+
( kb_decision_signature_memo(Key, Cached)
|
|
2765
|
+
-> Signatures = Cached
|
|
2766
|
+
; compute_decision_signatures(Computed),
|
|
2767
|
+
retractall(kb_decision_signature_memo(_, _)),
|
|
2768
|
+
assertz(kb_decision_signature_memo(Key, Computed)),
|
|
2769
|
+
Signatures = Computed
|
|
2770
|
+
).
|
|
2771
|
+
|
|
2772
|
+
compute_decision_signatures(Signatures) :-
|
|
2773
|
+
findall(Namespace-Name-Arity,
|
|
2774
|
+
predicate_schema(_SchemaId, Namespace, Name, Arity, _Names, _Types),
|
|
2775
|
+
SchemaSignatures0),
|
|
2776
|
+
sort(SchemaSignatures0, SchemaSignatures),
|
|
2777
|
+
findall(decision_signature(Namespace, Name, Arity, KeyPositions, DecisionPosition),
|
|
2778
|
+
( member(Namespace-Name-Arity, SchemaSignatures),
|
|
2779
|
+
schema_decision_positions(Namespace, Name, Arity, KeyPositions, DecisionPosition)
|
|
2780
|
+
),
|
|
2781
|
+
FromSchemas),
|
|
2782
|
+
findall(decision_signature(Namespace, Name, Arity, KeyPositions, DecisionPosition),
|
|
2783
|
+
( builtin_decision_predicate(Namespace, Name, Arity, KeyPositions, DecisionPosition),
|
|
2784
|
+
\+ memberchk(Namespace-Name-Arity, SchemaSignatures)
|
|
2785
|
+
),
|
|
2786
|
+
Builtins),
|
|
2787
|
+
append(FromSchemas, Builtins, Signatures).
|
|
2788
|
+
|
|
2789
|
+
schema_decision_positions(Namespace, Name, Arity, KeyPositions, DecisionPosition) :-
|
|
2790
|
+
predicate_schema_keys(Namespace, Name, Arity, KeyPositions),
|
|
2791
|
+
predicate_schema(_SchemaId, Namespace, Name, Arity, ArgumentNames, _ArgumentTypes),
|
|
2792
|
+
nth1(DecisionPosition, ArgumentNames, decision),
|
|
2793
|
+
\+ memberchk(DecisionPosition, KeyPositions),
|
|
2794
|
+
!.
|
|
2795
|
+
|
|
2796
|
+
%% bind_key_arguments(+KeyPositions, +ArgsA, ?ArgsB)
|
|
2797
|
+
% Unify ArgsB with ArgsA on every key position.
|
|
2798
|
+
bind_key_arguments([], _ArgsA, _ArgsB).
|
|
2799
|
+
bind_key_arguments([Position|Positions], ArgsA, ArgsB) :-
|
|
2800
|
+
nth1(Position, ArgsA, Value),
|
|
2801
|
+
nth1(Position, ArgsB, Value),
|
|
2802
|
+
bind_key_arguments(Positions, ArgsA, ArgsB).
|
|
2803
|
+
|
|
2804
|
+
%% builtin_decision_predicate(?Namespace, ?Name, ?Arity, ?KeyPositions, ?DecisionPosition)
|
|
2805
|
+
% Built-in catalog predicates with a closed allow/deny decision. Keep in
|
|
2806
|
+
% step with key_arguments in packages/cli/src/operations/modeling/
|
|
2807
|
+
% predicate-catalog-4.ts (permission_rule: actor, action, resource).
|
|
2808
|
+
builtin_decision_predicate(default, permission_rule, 4, [1, 2, 3], 4).
|
|
2809
|
+
|
|
2810
|
+
%% effective_decision(+Decision, +Polarity, -Effect)
|
|
2811
|
+
effective_decision(allow, assert, allow).
|
|
2812
|
+
effective_decision(allow, deny, deny).
|
|
2813
|
+
effective_decision(deny, _Polarity, deny).
|
|
2814
|
+
|
|
2665
2815
|
property_conflict_side(ReqId, FactId, SubjectKey, PropertyKey, Operator, ValueType, Value, Unit, Scope, Polarity, ValidFrom, ValidTo, Side) :-
|
|
2666
2816
|
entity_evidence_source(ReqId, req, RequirementSource),
|
|
2667
2817
|
fact_evidence_fields(FactId, FactSource, ClaimKey, ClaimText, _CanonicalKey),
|