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/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
- Violations
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
- -> format(atom(ListStr), '~w', [Value]),
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.