kibi-core 0.7.0 → 0.8.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/package.json +1 -1
- package/schema/entities.pl +16 -0
- package/schema/relationships.pl +2 -0
- package/schema/validation.pl +59 -31
- package/src/checks.pl +419 -6
- package/src/kb.pl +22 -1
- package/src/logic_ir.pl +629 -0
- package/src/sparql_client.pl +23 -1
- package/src/status.pl +9 -0
package/src/checks.pl
CHANGED
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
check_strict_req_fact_pairing/1,% Returns list of malformed strict req/fact pairing violations
|
|
19
19
|
check_strict_readiness/1, % Returns list of strict readiness audit violations
|
|
20
20
|
check_predicate_verifiability/1,% Returns list of unverifiable predicate-lane requirement links
|
|
21
|
+
check_logic_coverage/1, % Returns list of incomplete declared logical claim manifests
|
|
22
|
+
check_rule_safety/1,
|
|
23
|
+
check_rule_verifiability/1,
|
|
24
|
+
check_semantic_completeness/1,
|
|
21
25
|
run_checks_json/0, % Entry point for JSON output
|
|
22
26
|
violation_id_text/2 % Extract text from entity ID term (exported for testing)
|
|
23
27
|
]).
|
|
@@ -27,6 +31,7 @@
|
|
|
27
31
|
:- use_module('kb.pl').
|
|
28
32
|
:- use_module('../schema/entities.pl', [entity_type/1, required_property/2]).
|
|
29
33
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
34
|
+
:- use_module('logic_ir.pl', [logic_rule_safety/2, logic_rule_from_props/2, logic_rule_conflict/3, logic_rules_stratified/1]).
|
|
30
35
|
|
|
31
36
|
% Required fields for all entities
|
|
32
37
|
required_fields([id, title, status, created_at, updated_at, source]).
|
|
@@ -34,7 +39,7 @@ required_fields([id, title, status, created_at, updated_at, source]).
|
|
|
34
39
|
% Relationship types to check for dangling references
|
|
35
40
|
all_relationship_types([
|
|
36
41
|
depends_on, verified_by, validates, specified_by,
|
|
37
|
-
constrains, requires_property, requires_predicate, supersedes, relates_to
|
|
42
|
+
constrains, requires_property, requires_predicate, requires_rule, supersedes, relates_to
|
|
38
43
|
]).
|
|
39
44
|
|
|
40
45
|
%% check_all(-ViolationsDict)
|
|
@@ -54,6 +59,10 @@ check_all(ViolationsDict) :-
|
|
|
54
59
|
check_strict_req_fact_pairing(StrictReqFactPairing),
|
|
55
60
|
check_strict_readiness(StrictReadiness),
|
|
56
61
|
check_predicate_verifiability(PredicateVerifiability),
|
|
62
|
+
check_logic_coverage(LogicCoverage),
|
|
63
|
+
check_rule_safety(RuleSafety),
|
|
64
|
+
check_rule_verifiability(RuleVerifiability),
|
|
65
|
+
check_semantic_completeness(SemanticCompleteness),
|
|
57
66
|
ViolationsDict = _{
|
|
58
67
|
must_priority_coverage: MustPriority,
|
|
59
68
|
symbol_coverage: SymbolCoverage,
|
|
@@ -66,7 +75,11 @@ check_all(ViolationsDict) :-
|
|
|
66
75
|
strict_fact_shape: StrictFactShape,
|
|
67
76
|
strict_req_fact_pairing: StrictReqFactPairing,
|
|
68
77
|
strict_readiness: StrictReadiness,
|
|
69
|
-
predicate_verifiability: PredicateVerifiability
|
|
78
|
+
predicate_verifiability: PredicateVerifiability,
|
|
79
|
+
logic_coverage: LogicCoverage,
|
|
80
|
+
rule_safety: RuleSafety,
|
|
81
|
+
rule_verifiability: RuleVerifiability,
|
|
82
|
+
semantic_completeness: SemanticCompleteness
|
|
70
83
|
}.
|
|
71
84
|
|
|
72
85
|
%% check_must_priority_coverage(-Violations)
|
|
@@ -405,9 +418,25 @@ strict_fact_shape_violation(violation(
|
|
|
405
418
|
Description = First,
|
|
406
419
|
Suggestion = "Ensure predicate facts have predicate_name, non-empty predicate_args, canonical_key, and assert/deny polarity when present"
|
|
407
420
|
)
|
|
421
|
+
; Kind = rule_schema
|
|
422
|
+
-> findall(Msg, rule_schema_shape_error(Props, Msg), Errors),
|
|
423
|
+
( Errors = []
|
|
424
|
+
-> fail
|
|
425
|
+
; Errors = [First|_],
|
|
426
|
+
Description = First,
|
|
427
|
+
Suggestion = "Ensure rule_schema facts declare rule_name plus same-length argument_names and argument_types"
|
|
428
|
+
)
|
|
429
|
+
; Kind = rule
|
|
430
|
+
-> findall(Msg, rule_shape_error(Props, Msg), Errors),
|
|
431
|
+
( Errors = []
|
|
432
|
+
-> fail
|
|
433
|
+
; Errors = [First|_],
|
|
434
|
+
Description = First,
|
|
435
|
+
Suggestion = "Ensure rule facts contain a safe kibi.logic.v1 rule_ir, full rule_hash, rule_schema_id, rule_name, and semantic_key"
|
|
436
|
+
)
|
|
408
437
|
; % Unknown fact_kind - report as malformed
|
|
409
438
|
format(string(Description), "Unknown fact_kind: ~w", [Kind]),
|
|
410
|
-
Suggestion = "Use one of: subject, property_value, observation, meta, predicate_schema, predicate"
|
|
439
|
+
Suggestion = "Use one of: subject, property_value, observation, meta, predicate_schema, predicate, rule_schema, rule"
|
|
411
440
|
),
|
|
412
441
|
|
|
413
442
|
( memberchk(source=Source0, Props)
|
|
@@ -500,6 +529,42 @@ predicate_shape_error(Props, "Predicate fact polarity must be assert or deny") :
|
|
|
500
529
|
normalize_term_atom(RawPolarity, Polarity),
|
|
501
530
|
\+ memberchk(Polarity, [assert, deny]).
|
|
502
531
|
|
|
532
|
+
rule_schema_shape_error(Props, "Rule schema fact missing required field: rule_name") :-
|
|
533
|
+
\+ memberchk(rule_name=_, Props).
|
|
534
|
+
rule_schema_shape_error(Props, "Rule schema fact missing required field: argument_names") :-
|
|
535
|
+
memberchk(rule_name=_, Props),
|
|
536
|
+
\+ memberchk(argument_names=_, Props).
|
|
537
|
+
rule_schema_shape_error(Props, "Rule schema fact missing required field: argument_types") :-
|
|
538
|
+
memberchk(argument_names=_, Props),
|
|
539
|
+
\+ memberchk(argument_types=_, Props).
|
|
540
|
+
rule_schema_shape_error(Props, "Rule schema fact argument_names and argument_types must have equal lengths") :-
|
|
541
|
+
memberchk(argument_names=RawNames, Props),
|
|
542
|
+
memberchk(argument_types=RawTypes, Props),
|
|
543
|
+
checks_normalize_atom_list(RawNames, Names),
|
|
544
|
+
checks_normalize_atom_list(RawTypes, Types),
|
|
545
|
+
length(Names, NameCount),
|
|
546
|
+
length(Types, TypeCount),
|
|
547
|
+
NameCount =\= TypeCount.
|
|
548
|
+
|
|
549
|
+
rule_shape_error(Props, "Rule fact missing required field: rule_ir") :-
|
|
550
|
+
\+ memberchk(rule_ir=_, Props).
|
|
551
|
+
rule_shape_error(Props, "Rule fact missing required field: rule_hash") :-
|
|
552
|
+
memberchk(rule_ir=_, Props),
|
|
553
|
+
\+ memberchk(rule_hash=_, Props).
|
|
554
|
+
rule_shape_error(Props, "Rule fact missing required field: rule_schema_id") :-
|
|
555
|
+
memberchk(rule_hash=_, Props),
|
|
556
|
+
\+ memberchk(rule_schema_id=_, Props).
|
|
557
|
+
rule_shape_error(Props, "Rule fact missing required field: rule_name") :-
|
|
558
|
+
memberchk(rule_schema_id=_, Props),
|
|
559
|
+
\+ memberchk(rule_name=_, Props).
|
|
560
|
+
rule_shape_error(Props, "Rule fact missing required field: semantic_key") :-
|
|
561
|
+
memberchk(rule_name=_, Props),
|
|
562
|
+
\+ memberchk(semantic_key=_, Props).
|
|
563
|
+
rule_shape_error(Props, "Rule fact rule_ir failed kibi.logic.v1 safety validation") :-
|
|
564
|
+
memberchk(rule_ir=_, Props),
|
|
565
|
+
logic_rule_safety(Props, Errors),
|
|
566
|
+
Errors \= [].
|
|
567
|
+
|
|
503
568
|
checks_normalize_integer(Raw, Integer) :-
|
|
504
569
|
( Raw = ^^(Value, _Type)
|
|
505
570
|
-> checks_normalize_integer(Value, Integer)
|
|
@@ -534,8 +599,41 @@ check_domain_contradictions(Violations) :-
|
|
|
534
599
|
format(string(EntityId), "~w/~w", [ReqA, ReqB]),
|
|
535
600
|
format(string(Description), "~w [strict-readiness: contradiction-ready]", [Reason])
|
|
536
601
|
),
|
|
537
|
-
|
|
538
|
-
)
|
|
602
|
+
StrictViolations
|
|
603
|
+
),
|
|
604
|
+
findall(
|
|
605
|
+
violation(
|
|
606
|
+
'domain-contradictions',
|
|
607
|
+
EntityId,
|
|
608
|
+
Description,
|
|
609
|
+
"Align the opposing rule heads, separate their scopes, or add an explicit supersedes relationship",
|
|
610
|
+
""
|
|
611
|
+
),
|
|
612
|
+
( opposing_rule_requirements(ReqA, ReqB, RuleA, RuleB),
|
|
613
|
+
logic_rule_conflict(RuleA, RuleB, Conflict),
|
|
614
|
+
Conflict \= disjoint,
|
|
615
|
+
format(string(EntityId), "~w/~w", [ReqA, ReqB]),
|
|
616
|
+
format(string(Description), "Rule conflict (~w) between ~w and ~w", [Conflict, ReqA, ReqB])
|
|
617
|
+
),
|
|
618
|
+
RuleViolations
|
|
619
|
+
),
|
|
620
|
+
append(StrictViolations, RuleViolations, Violations).
|
|
621
|
+
|
|
622
|
+
opposing_rule_requirements(ReqA, ReqB, RuleA, RuleB) :-
|
|
623
|
+
kb:current_req(ReqA),
|
|
624
|
+
kb:current_req(ReqB),
|
|
625
|
+
ReqA @< ReqB,
|
|
626
|
+
kb_relationship(requires_rule, ReqA, FactA),
|
|
627
|
+
kb_relationship(requires_rule, ReqB, FactB),
|
|
628
|
+
FactA @< FactB,
|
|
629
|
+
kb_entity(FactA, fact, PropsA),
|
|
630
|
+
kb_entity(FactB, fact, PropsB),
|
|
631
|
+
memberchk(fact_kind=KindA, PropsA),
|
|
632
|
+
memberchk(fact_kind=KindB, PropsB),
|
|
633
|
+
normalize_term_atom(KindA, rule),
|
|
634
|
+
normalize_term_atom(KindB, rule),
|
|
635
|
+
logic_rule_from_props(PropsA, RuleA),
|
|
636
|
+
logic_rule_from_props(PropsB, RuleB).
|
|
539
637
|
|
|
540
638
|
%% check_strict_req_fact_pairing(-Violations)
|
|
541
639
|
% Finds current requirements attempting strict-lane modeling with incomplete
|
|
@@ -800,6 +898,313 @@ predicate_verifiability_kind_label(legacy, "a legacy fact without fact_kind").
|
|
|
800
898
|
predicate_verifiability_kind_label(Kind, Label) :-
|
|
801
899
|
format(string(Label), "fact_kind=~w", [Kind]).
|
|
802
900
|
|
|
901
|
+
%% check_logic_coverage(-Violations)
|
|
902
|
+
% Validates explicitly declared atomic requirement claim manifests. Legacy
|
|
903
|
+
% requirements without logic_claims remain eligible for gradual backfill; once
|
|
904
|
+
% a manifest exists, every key must be grounded by a linked strict-property or
|
|
905
|
+
% predicate fact carrying the same claim_key.
|
|
906
|
+
check_logic_coverage(Violations) :-
|
|
907
|
+
findall(Violation, logic_coverage_violation(Violation), Violations0),
|
|
908
|
+
sort(Violations0, Violations).
|
|
909
|
+
|
|
910
|
+
logic_coverage_violation(violation(
|
|
911
|
+
'logic-coverage',
|
|
912
|
+
ReqId,
|
|
913
|
+
Description,
|
|
914
|
+
"Ground every declared claim with a property_value, predicate, or rule fact via its typed relationship, preserving the same claim_key",
|
|
915
|
+
Source
|
|
916
|
+
)) :-
|
|
917
|
+
kb:current_req(ReqId),
|
|
918
|
+
requirement_logic_claims(ReqId, ClaimKeys),
|
|
919
|
+
member(ClaimKey, ClaimKeys),
|
|
920
|
+
\+ grounded_requirement_claim(ReqId, ClaimKey),
|
|
921
|
+
format(
|
|
922
|
+
string(Description),
|
|
923
|
+
"Requirement declares logical claim ~w but has no matching ground fact",
|
|
924
|
+
[ClaimKey]
|
|
925
|
+
),
|
|
926
|
+
violation_source(ReqId, req, Source).
|
|
927
|
+
|
|
928
|
+
logic_coverage_violation(violation(
|
|
929
|
+
'logic-coverage',
|
|
930
|
+
ReqId,
|
|
931
|
+
Description,
|
|
932
|
+
"Keep a one-to-one mapping between each atomic claim key and its linked ground fact; split compound clauses before grounding",
|
|
933
|
+
Source
|
|
934
|
+
)) :-
|
|
935
|
+
kb:current_req(ReqId),
|
|
936
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactA),
|
|
937
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactB),
|
|
938
|
+
FactA @< FactB,
|
|
939
|
+
format(
|
|
940
|
+
string(Description),
|
|
941
|
+
"Requirement grounds logical claim ~w more than once through ~w and ~w",
|
|
942
|
+
[ClaimKey, FactA, FactB]
|
|
943
|
+
),
|
|
944
|
+
violation_source(ReqId, req, Source).
|
|
945
|
+
|
|
946
|
+
logic_coverage_violation(violation(
|
|
947
|
+
'logic-coverage',
|
|
948
|
+
ReqId,
|
|
949
|
+
Description,
|
|
950
|
+
"Remove punctuation or wording variants from the atomic inventory and keep one claim key for each distinct logical term",
|
|
951
|
+
Source
|
|
952
|
+
)) :-
|
|
953
|
+
kb:current_req(ReqId),
|
|
954
|
+
grounded_requirement_claim_fact(ReqId, ClaimA, FactA),
|
|
955
|
+
grounded_requirement_claim_fact(ReqId, ClaimB, FactB),
|
|
956
|
+
ClaimA \= ClaimB,
|
|
957
|
+
FactA @< FactB,
|
|
958
|
+
logical_ground_signature(FactA, Signature),
|
|
959
|
+
logical_ground_signature(FactB, Signature),
|
|
960
|
+
format(
|
|
961
|
+
string(Description),
|
|
962
|
+
"Requirement declares duplicate logical ground term ~w through claim keys ~w and ~w",
|
|
963
|
+
[Signature, ClaimA, ClaimB]
|
|
964
|
+
),
|
|
965
|
+
violation_source(ReqId, req, Source).
|
|
966
|
+
|
|
967
|
+
logic_coverage_violation(violation(
|
|
968
|
+
'logic-coverage',
|
|
969
|
+
ReqId,
|
|
970
|
+
Description,
|
|
971
|
+
"Append the linked fact claim_key to the requirement logic_claims manifest, or remove the stale logical link",
|
|
972
|
+
Source
|
|
973
|
+
)) :-
|
|
974
|
+
kb:current_req(ReqId),
|
|
975
|
+
requirement_logic_claims(ReqId, ClaimKeys),
|
|
976
|
+
grounded_requirement_claim(ReqId, ClaimKey),
|
|
977
|
+
\+ memberchk(ClaimKey, ClaimKeys),
|
|
978
|
+
format(
|
|
979
|
+
string(Description),
|
|
980
|
+
"Requirement links ground logical claim ~w but omits it from logic_claims",
|
|
981
|
+
[ClaimKey]
|
|
982
|
+
),
|
|
983
|
+
violation_source(ReqId, req, Source).
|
|
984
|
+
|
|
985
|
+
requirement_logic_claims(ReqId, ClaimKeys) :-
|
|
986
|
+
kb_entity(ReqId, req, Props),
|
|
987
|
+
memberchk(logic_claims=RawClaimKeys, Props),
|
|
988
|
+
kb:normalize_term_atom_list(RawClaimKeys, ClaimKeys).
|
|
989
|
+
|
|
990
|
+
grounded_requirement_claim(ReqId, ClaimKey) :-
|
|
991
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, _FactId).
|
|
992
|
+
|
|
993
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactId) :-
|
|
994
|
+
kb_relationship(requires_property, ReqId, FactId),
|
|
995
|
+
ground_fact_claim_key(FactId, property_value, ClaimKey).
|
|
996
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactId) :-
|
|
997
|
+
kb_relationship(requires_predicate, ReqId, FactId),
|
|
998
|
+
ground_fact_claim_key(FactId, predicate, ClaimKey).
|
|
999
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactId) :-
|
|
1000
|
+
kb_relationship(requires_rule, ReqId, FactId),
|
|
1001
|
+
ground_fact_claim_key(FactId, rule, ClaimKey).
|
|
1002
|
+
|
|
1003
|
+
logical_ground_signature(FactId, predicate(Namespace, Name, Args, Polarity)) :-
|
|
1004
|
+
kb:predicate_fact(FactId, Namespace, Name, Args, Polarity).
|
|
1005
|
+
logical_ground_signature(
|
|
1006
|
+
FactId,
|
|
1007
|
+
property(Subject, Property, Operator, ValueType, Value, Unit, Scope, Polarity)
|
|
1008
|
+
) :-
|
|
1009
|
+
kb:fact_property_tuple(
|
|
1010
|
+
FactId,
|
|
1011
|
+
Subject,
|
|
1012
|
+
Property,
|
|
1013
|
+
Operator,
|
|
1014
|
+
ValueType,
|
|
1015
|
+
Value,
|
|
1016
|
+
Unit,
|
|
1017
|
+
Scope,
|
|
1018
|
+
Polarity
|
|
1019
|
+
).
|
|
1020
|
+
logical_ground_signature(FactId, rule(SemanticKey)) :-
|
|
1021
|
+
kb_entity(FactId, fact, Props),
|
|
1022
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1023
|
+
normalize_term_atom(RawKind, rule),
|
|
1024
|
+
( memberchk(semantic_key=RawKey, Props)
|
|
1025
|
+
-> normalize_term_atom(RawKey, SemanticKey)
|
|
1026
|
+
; memberchk(rule_hash=RawKey, Props), normalize_term_atom(RawKey, SemanticKey)
|
|
1027
|
+
).
|
|
1028
|
+
|
|
1029
|
+
ground_fact_claim_key(FactId, ExpectedKind, ClaimKey) :-
|
|
1030
|
+
kb_entity(FactId, fact, Props),
|
|
1031
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1032
|
+
normalize_term_atom(RawKind, ExpectedKind),
|
|
1033
|
+
memberchk(claim_key=RawClaimKey, Props),
|
|
1034
|
+
normalize_term_atom(RawClaimKey, ClaimKey),
|
|
1035
|
+
memberchk(claim_text=RawClaimText, Props),
|
|
1036
|
+
normalize_term_atom(RawClaimText, ClaimText),
|
|
1037
|
+
ClaimText \= ''.
|
|
1038
|
+
|
|
1039
|
+
%% check_rule_safety(-Violations)
|
|
1040
|
+
% Every stored rule must decode through the closed Logic IR vocabulary and
|
|
1041
|
+
% pass the independent Prolog-side safety checks.
|
|
1042
|
+
check_rule_safety(Violations) :-
|
|
1043
|
+
findall(Violation, rule_safety_violation(Violation), Raw),
|
|
1044
|
+
sort(Raw, Violations).
|
|
1045
|
+
|
|
1046
|
+
rule_safety_violation(violation(
|
|
1047
|
+
'rule-safety',
|
|
1048
|
+
FactId,
|
|
1049
|
+
Description,
|
|
1050
|
+
"Use kibi.logic.v1 typed IR; remove raw Prolog, unsafe variables, or unstratified negation",
|
|
1051
|
+
Source
|
|
1052
|
+
)) :-
|
|
1053
|
+
kb_entity(FactId, fact, Props),
|
|
1054
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1055
|
+
normalize_term_atom(RawKind, rule),
|
|
1056
|
+
logic_rule_safety(Props, Errors),
|
|
1057
|
+
Errors \= [],
|
|
1058
|
+
format(string(Description), "Rule fact ~w failed safety validation: ~w", [FactId, Errors]),
|
|
1059
|
+
violation_source(FactId, fact, Source).
|
|
1060
|
+
|
|
1061
|
+
% Local rule checks cannot detect a negation cycle spanning multiple facts.
|
|
1062
|
+
% Run the finite dependency-graph check over the complete stored rule set and
|
|
1063
|
+
% report each participating negated rule as infrastructure-grade unsafe IR.
|
|
1064
|
+
rule_safety_violation(violation(
|
|
1065
|
+
'rule-safety',
|
|
1066
|
+
FactId,
|
|
1067
|
+
"Stored rules contain an unstratified negation dependency cycle",
|
|
1068
|
+
"Separate the negated dependency into strata or replace it with an explicit closed-world fact",
|
|
1069
|
+
Source
|
|
1070
|
+
)) :-
|
|
1071
|
+
findall(Rule, stored_rule_term(Rule), Rules),
|
|
1072
|
+
Rules \= [],
|
|
1073
|
+
\+ logic_rules_stratified(Rules),
|
|
1074
|
+
stored_rule_fact(FactId, _Props, Rule),
|
|
1075
|
+
rule_contains_negation(Rule),
|
|
1076
|
+
violation_source(FactId, fact, Source).
|
|
1077
|
+
|
|
1078
|
+
stored_rule_term(Rule) :-
|
|
1079
|
+
kb_entity(_FactId, fact, Props),
|
|
1080
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1081
|
+
normalize_term_atom(RawKind, rule),
|
|
1082
|
+
logic_rule_from_props(Props, Rule).
|
|
1083
|
+
|
|
1084
|
+
stored_rule_fact(FactId, Props, Rule) :-
|
|
1085
|
+
kb_entity(FactId, fact, Props),
|
|
1086
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1087
|
+
normalize_term_atom(RawKind, rule),
|
|
1088
|
+
logic_rule_from_props(Props, Rule).
|
|
1089
|
+
|
|
1090
|
+
rule_contains_negation(rule(_, _, _Head, Body, Exceptions, _Scope, _From, _To, _Schema, _Variables)) :-
|
|
1091
|
+
( expression_contains_negation(Body)
|
|
1092
|
+
; member(Exception, Exceptions), expression_contains_negation(Exception)
|
|
1093
|
+
),
|
|
1094
|
+
!.
|
|
1095
|
+
|
|
1096
|
+
expression_contains_negation(not(_)).
|
|
1097
|
+
expression_contains_negation(all(Items)) :- member(Item, Items), expression_contains_negation(Item).
|
|
1098
|
+
expression_contains_negation(any(Items)) :- member(Item, Items), expression_contains_negation(Item).
|
|
1099
|
+
expression_contains_negation(count(Atom, _Operator, _Value)) :- expression_contains_negation(Atom).
|
|
1100
|
+
|
|
1101
|
+
%% check_rule_verifiability(-Violations)
|
|
1102
|
+
% A requires_rule edge is meaningful only when it reaches a rule fact whose
|
|
1103
|
+
% schema endpoint exists and whose rule IR is safe.
|
|
1104
|
+
check_rule_verifiability(Violations) :-
|
|
1105
|
+
findall(Violation, rule_verifiability_violation(Violation), Raw),
|
|
1106
|
+
sort(Raw, Violations).
|
|
1107
|
+
|
|
1108
|
+
rule_verifiability_violation(violation(
|
|
1109
|
+
'rule-verifiability',
|
|
1110
|
+
ReqId,
|
|
1111
|
+
Description,
|
|
1112
|
+
"Link requires_rule to fact_kind=rule with a valid rule_schema_id and safe kibi.logic.v1 IR",
|
|
1113
|
+
Source
|
|
1114
|
+
)) :-
|
|
1115
|
+
kb:current_req(ReqId),
|
|
1116
|
+
kb_relationship(requires_rule, ReqId, FactId),
|
|
1117
|
+
kb_entity(FactId, fact, Props),
|
|
1118
|
+
rule_verifiability_description(Props, FactId, Description),
|
|
1119
|
+
violation_source(ReqId, req, Source).
|
|
1120
|
+
|
|
1121
|
+
rule_verifiability_description(Props, FactId, Description) :-
|
|
1122
|
+
( memberchk(fact_kind=RawKind, Props)
|
|
1123
|
+
-> normalize_term_atom(RawKind, Kind)
|
|
1124
|
+
; Kind = legacy
|
|
1125
|
+
),
|
|
1126
|
+
Kind \= rule,
|
|
1127
|
+
format(string(Description), "Requirement uses requires_rule ~w but target is fact_kind=~w", [FactId, Kind]).
|
|
1128
|
+
rule_verifiability_description(Props, FactId, Description) :-
|
|
1129
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1130
|
+
normalize_term_atom(RawKind, rule),
|
|
1131
|
+
\+ memberchk(rule_schema_id=_, Props),
|
|
1132
|
+
format(string(Description), "Rule fact ~w has no rule_schema_id", [FactId]).
|
|
1133
|
+
rule_verifiability_description(Props, FactId, Description) :-
|
|
1134
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1135
|
+
normalize_term_atom(RawKind, rule),
|
|
1136
|
+
memberchk(rule_schema_id=SchemaId, Props),
|
|
1137
|
+
\+ kb_entity(SchemaId, fact, _),
|
|
1138
|
+
format(string(Description), "Rule fact ~w references missing rule schema ~w", [FactId, SchemaId]).
|
|
1139
|
+
rule_verifiability_description(Props, FactId, Description) :-
|
|
1140
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1141
|
+
normalize_term_atom(RawKind, rule),
|
|
1142
|
+
memberchk(rule_schema_id=SchemaId, Props),
|
|
1143
|
+
kb_entity(SchemaId, fact, SchemaProps),
|
|
1144
|
+
\+ valid_rule_schema_props(SchemaProps),
|
|
1145
|
+
format(string(Description), "Rule fact ~w references ~w, which is not a rule_schema fact", [FactId, SchemaId]).
|
|
1146
|
+
rule_verifiability_description(Props, FactId, Description) :-
|
|
1147
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1148
|
+
normalize_term_atom(RawKind, rule),
|
|
1149
|
+
logic_rule_safety(Props, Errors),
|
|
1150
|
+
Errors \= [],
|
|
1151
|
+
format(string(Description), "Rule fact ~w is not safe: ~w", [FactId, Errors]).
|
|
1152
|
+
|
|
1153
|
+
valid_rule_schema_props(Props) :-
|
|
1154
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1155
|
+
normalize_term_atom(RawKind, rule_schema),
|
|
1156
|
+
memberchk(rule_name=Name, Props),
|
|
1157
|
+
nonempty_normalized(Name),
|
|
1158
|
+
memberchk(argument_names=RawNames, Props),
|
|
1159
|
+
memberchk(argument_types=RawTypes, Props),
|
|
1160
|
+
checks_normalize_atom_list(RawNames, Names),
|
|
1161
|
+
checks_normalize_atom_list(RawTypes, Types),
|
|
1162
|
+
same_length(Names, Types).
|
|
1163
|
+
|
|
1164
|
+
nonempty_normalized(Value) :-
|
|
1165
|
+
normalize_term_atom(Value, Atom),
|
|
1166
|
+
Atom \= ''.
|
|
1167
|
+
|
|
1168
|
+
%% check_semantic_completeness(-Violations)
|
|
1169
|
+
% The proposition ledger is additive and optional during migration. When it is
|
|
1170
|
+
% present, every assertive proposition must either be modeled or explicitly
|
|
1171
|
+
% recorded as an unresolved review state; silently missing entries are errors.
|
|
1172
|
+
check_semantic_completeness(Violations) :-
|
|
1173
|
+
findall(Violation, semantic_completeness_violation(Violation), Raw),
|
|
1174
|
+
sort(Raw, Violations).
|
|
1175
|
+
|
|
1176
|
+
semantic_completeness_violation(violation(
|
|
1177
|
+
'semantic-completeness',
|
|
1178
|
+
ReqId,
|
|
1179
|
+
Description,
|
|
1180
|
+
"Run kb_semantic_advisor again, preserve every proposition span, and model or explicitly classify each assertive proposition",
|
|
1181
|
+
Source
|
|
1182
|
+
)) :-
|
|
1183
|
+
kb:current_req(ReqId),
|
|
1184
|
+
kb_entity(ReqId, req, Props),
|
|
1185
|
+
memberchk(semantic_inventory=RawInventory, Props),
|
|
1186
|
+
inventory_entries(RawInventory, Entries),
|
|
1187
|
+
member(Entry, Entries),
|
|
1188
|
+
inventory_entry_status(Entry, Status),
|
|
1189
|
+
memberchk(Status, [missing]),
|
|
1190
|
+
format(string(Description), "Requirement proposition ledger contains an unclassified assertive span (~w)", [Entry]),
|
|
1191
|
+
violation_source(ReqId, req, Source).
|
|
1192
|
+
|
|
1193
|
+
inventory_entries(Raw, Entries) :-
|
|
1194
|
+
( is_list(Raw) -> Entries = Raw
|
|
1195
|
+
; Raw = ^^(Value, _) -> inventory_entries(Value, Entries)
|
|
1196
|
+
; (atom(Raw) ; string(Raw)),
|
|
1197
|
+
catch(atom_json_term(Raw, JsonEntries, [value_string_as(string)]), _, fail),
|
|
1198
|
+
is_list(JsonEntries)
|
|
1199
|
+
-> Entries = JsonEntries
|
|
1200
|
+
; Entries = []
|
|
1201
|
+
).
|
|
1202
|
+
|
|
1203
|
+
inventory_entry_status(Entry, Status) :-
|
|
1204
|
+
is_dict(Entry), get_dict(status, Entry, Raw), normalize_term_atom(Raw, Status).
|
|
1205
|
+
inventory_entry_status(Entry, Status) :-
|
|
1206
|
+
is_list(Entry), memberchk(status=Raw, Entry), normalize_term_atom(Raw, Status).
|
|
1207
|
+
|
|
803
1208
|
strict_readiness_has_fact_link(ReqId) :-
|
|
804
1209
|
kb_relationship(constrains, ReqId, FactId),
|
|
805
1210
|
kb_entity(FactId, fact, _),
|
|
@@ -873,6 +1278,10 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
873
1278
|
check_strict_req_fact_pairing(StrictReqFactPairing),
|
|
874
1279
|
check_strict_readiness(StrictReadiness),
|
|
875
1280
|
check_predicate_verifiability(PredicateVerifiability),
|
|
1281
|
+
check_logic_coverage(LogicCoverage),
|
|
1282
|
+
check_rule_safety(RuleSafety),
|
|
1283
|
+
check_rule_verifiability(RuleVerifiability),
|
|
1284
|
+
check_semantic_completeness(SemanticCompleteness),
|
|
876
1285
|
ViolationsDict = _{
|
|
877
1286
|
must_priority_coverage: MustPriority,
|
|
878
1287
|
symbol_coverage: SymbolCoverage,
|
|
@@ -885,7 +1294,11 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
885
1294
|
strict_fact_shape: StrictFactShape,
|
|
886
1295
|
strict_req_fact_pairing: StrictReqFactPairing,
|
|
887
1296
|
strict_readiness: StrictReadiness,
|
|
888
|
-
predicate_verifiability: PredicateVerifiability
|
|
1297
|
+
predicate_verifiability: PredicateVerifiability,
|
|
1298
|
+
logic_coverage: LogicCoverage,
|
|
1299
|
+
rule_safety: RuleSafety,
|
|
1300
|
+
rule_verifiability: RuleVerifiability,
|
|
1301
|
+
semantic_completeness: SemanticCompleteness
|
|
889
1302
|
}.
|
|
890
1303
|
|
|
891
1304
|
%% violations_dict_to_json(+ViolationsDict, -JsonDict)
|
package/src/kb.pl
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
contradicting_reqs/3,
|
|
46
46
|
check_req_contradiction/1,
|
|
47
47
|
normalize_term_atom/2,
|
|
48
|
+
normalize_term_atom_list/2,
|
|
48
49
|
changeset/4, % Export for testing
|
|
49
50
|
kb_uri/1
|
|
50
51
|
]).
|
|
@@ -636,7 +637,7 @@ value_to_literal(Key, Value, Literal) :-
|
|
|
636
637
|
; string(Value)
|
|
637
638
|
-> Literal = Value^^'http://www.w3.org/2001/XMLSchema#string'
|
|
638
639
|
; is_list(Value)
|
|
639
|
-
->
|
|
640
|
+
-> with_output_to(atom(ListStr), write_term(Value, [quoted(true)])),
|
|
640
641
|
Literal = ListStr^^'http://www.w3.org/2001/XMLSchema#string'
|
|
641
642
|
; format(atom(Str), '~w', [Value]),
|
|
642
643
|
Literal = Str^^'http://www.w3.org/2001/XMLSchema#string'
|
|
@@ -1005,6 +1006,26 @@ req_conflict(ReqA, ReqB, Reason) :-
|
|
|
1005
1006
|
; property_conflict(SubjectKey, PropertyKey, OpA, ValTypeA, ValA, UnitA, PolarityA,
|
|
1006
1007
|
OpB, ValTypeB, ValB, UnitB, PolarityB, Reason)
|
|
1007
1008
|
).
|
|
1009
|
+
req_conflict(ReqA, ReqB, Reason) :-
|
|
1010
|
+
effective_req_predicate(ReqA, FactA, Namespace, Name, Args, PolarityA),
|
|
1011
|
+
effective_req_predicate(ReqB, FactB, Namespace, Name, Args, PolarityB),
|
|
1012
|
+
FactA \= FactB,
|
|
1013
|
+
opposite_predicate_polarity(PolarityA, PolarityB),
|
|
1014
|
+
atomic_list_concat(Args, ',', ArgsText),
|
|
1015
|
+
format(
|
|
1016
|
+
string(Reason),
|
|
1017
|
+
"Predicate conflict on ~w:~w(~w): ~w asserts ~w while ~w asserts ~w",
|
|
1018
|
+
[Namespace, Name, ArgsText, ReqA, PolarityA, ReqB, PolarityB]
|
|
1019
|
+
).
|
|
1020
|
+
|
|
1021
|
+
%% effective_req_predicate(+ReqId, -FactId, -Namespace, -Name, -Args, -Polarity)
|
|
1022
|
+
% Ground predicate claim required by a requirement.
|
|
1023
|
+
effective_req_predicate(ReqId, FactId, Namespace, Name, Args, Polarity) :-
|
|
1024
|
+
kb_relationship(requires_predicate, ReqId, FactId),
|
|
1025
|
+
predicate_fact(FactId, Namespace, Name, Args, Polarity).
|
|
1026
|
+
|
|
1027
|
+
opposite_predicate_polarity(assert, deny).
|
|
1028
|
+
opposite_predicate_polarity(deny, assert).
|
|
1008
1029
|
|
|
1009
1030
|
%% fact_subject_key(+FactId, -SubjectKey)
|
|
1010
1031
|
% Extract the normalized subject key for strict subject facts.
|