kibi-core 0.7.1 → 0.9.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 +28 -0
- package/schema/relationships.pl +2 -0
- package/schema/validation.pl +41 -32
- package/src/checks.pl +410 -19
- package/src/discovery.pl +88 -18
- package/src/kb.pl +256 -19
- package/src/logic_ir.pl +629 -0
- package/src/requirement_proof.pl +837 -0
package/src/checks.pl
CHANGED
|
@@ -14,11 +14,15 @@
|
|
|
14
14
|
check_required_fields/1, % Returns list of missing required field violations
|
|
15
15
|
check_deprecated_adrs/1, % Returns list of deprecated ADR violations
|
|
16
16
|
check_domain_contradictions/1, % Returns list of contradiction violations
|
|
17
|
+
check_domain_contradiction_witnesses/1,
|
|
17
18
|
check_strict_fact_shape/1, % Returns list of malformed strict fact violations
|
|
18
19
|
check_strict_req_fact_pairing/1,% Returns list of malformed strict req/fact pairing violations
|
|
19
20
|
check_strict_readiness/1, % Returns list of strict readiness audit violations
|
|
20
21
|
check_predicate_verifiability/1,% Returns list of unverifiable predicate-lane requirement links
|
|
21
22
|
check_logic_coverage/1, % Returns list of incomplete declared logical claim manifests
|
|
23
|
+
check_rule_safety/1,
|
|
24
|
+
check_rule_verifiability/1,
|
|
25
|
+
check_semantic_completeness/1,
|
|
22
26
|
run_checks_json/0, % Entry point for JSON output
|
|
23
27
|
violation_id_text/2 % Extract text from entity ID term (exported for testing)
|
|
24
28
|
]).
|
|
@@ -28,6 +32,7 @@
|
|
|
28
32
|
:- use_module('kb.pl').
|
|
29
33
|
:- use_module('../schema/entities.pl', [entity_type/1, required_property/2]).
|
|
30
34
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
35
|
+
:- use_module('logic_ir.pl', [logic_rule_safety/2, logic_rule_from_props/2, logic_rule_conflict/3, logic_rule_conflict_witness/3, logic_rules_stratified/1]).
|
|
31
36
|
|
|
32
37
|
% Required fields for all entities
|
|
33
38
|
required_fields([id, title, status, created_at, updated_at, source]).
|
|
@@ -35,7 +40,7 @@ required_fields([id, title, status, created_at, updated_at, source]).
|
|
|
35
40
|
% Relationship types to check for dangling references
|
|
36
41
|
all_relationship_types([
|
|
37
42
|
depends_on, verified_by, validates, specified_by,
|
|
38
|
-
constrains, requires_property, requires_predicate, supersedes, relates_to
|
|
43
|
+
constrains, requires_property, requires_predicate, requires_rule, supersedes, relates_to
|
|
39
44
|
]).
|
|
40
45
|
|
|
41
46
|
%% check_all(-ViolationsDict)
|
|
@@ -56,6 +61,9 @@ check_all(ViolationsDict) :-
|
|
|
56
61
|
check_strict_readiness(StrictReadiness),
|
|
57
62
|
check_predicate_verifiability(PredicateVerifiability),
|
|
58
63
|
check_logic_coverage(LogicCoverage),
|
|
64
|
+
check_rule_safety(RuleSafety),
|
|
65
|
+
check_rule_verifiability(RuleVerifiability),
|
|
66
|
+
check_semantic_completeness(SemanticCompleteness),
|
|
59
67
|
ViolationsDict = _{
|
|
60
68
|
must_priority_coverage: MustPriority,
|
|
61
69
|
symbol_coverage: SymbolCoverage,
|
|
@@ -69,7 +77,10 @@ check_all(ViolationsDict) :-
|
|
|
69
77
|
strict_req_fact_pairing: StrictReqFactPairing,
|
|
70
78
|
strict_readiness: StrictReadiness,
|
|
71
79
|
predicate_verifiability: PredicateVerifiability,
|
|
72
|
-
logic_coverage: LogicCoverage
|
|
80
|
+
logic_coverage: LogicCoverage,
|
|
81
|
+
rule_safety: RuleSafety,
|
|
82
|
+
rule_verifiability: RuleVerifiability,
|
|
83
|
+
semantic_completeness: SemanticCompleteness
|
|
73
84
|
}.
|
|
74
85
|
|
|
75
86
|
%% check_must_priority_coverage(-Violations)
|
|
@@ -408,9 +419,25 @@ strict_fact_shape_violation(violation(
|
|
|
408
419
|
Description = First,
|
|
409
420
|
Suggestion = "Ensure predicate facts have predicate_name, non-empty predicate_args, canonical_key, and assert/deny polarity when present"
|
|
410
421
|
)
|
|
422
|
+
; Kind = rule_schema
|
|
423
|
+
-> findall(Msg, rule_schema_shape_error(Props, Msg), Errors),
|
|
424
|
+
( Errors = []
|
|
425
|
+
-> fail
|
|
426
|
+
; Errors = [First|_],
|
|
427
|
+
Description = First,
|
|
428
|
+
Suggestion = "Ensure rule_schema facts declare rule_name plus same-length argument_names and argument_types"
|
|
429
|
+
)
|
|
430
|
+
; Kind = rule
|
|
431
|
+
-> findall(Msg, rule_shape_error(Props, Msg), Errors),
|
|
432
|
+
( Errors = []
|
|
433
|
+
-> fail
|
|
434
|
+
; Errors = [First|_],
|
|
435
|
+
Description = First,
|
|
436
|
+
Suggestion = "Ensure rule facts contain a safe kibi.logic.v1 rule_ir, full rule_hash, rule_schema_id, rule_name, and semantic_key"
|
|
437
|
+
)
|
|
411
438
|
; % Unknown fact_kind - report as malformed
|
|
412
439
|
format(string(Description), "Unknown fact_kind: ~w", [Kind]),
|
|
413
|
-
Suggestion = "Use one of: subject, property_value, observation, meta, predicate_schema, predicate"
|
|
440
|
+
Suggestion = "Use one of: subject, property_value, observation, meta, predicate_schema, predicate, rule_schema, rule"
|
|
414
441
|
),
|
|
415
442
|
|
|
416
443
|
( memberchk(source=Source0, Props)
|
|
@@ -503,6 +530,42 @@ predicate_shape_error(Props, "Predicate fact polarity must be assert or deny") :
|
|
|
503
530
|
normalize_term_atom(RawPolarity, Polarity),
|
|
504
531
|
\+ memberchk(Polarity, [assert, deny]).
|
|
505
532
|
|
|
533
|
+
rule_schema_shape_error(Props, "Rule schema fact missing required field: rule_name") :-
|
|
534
|
+
\+ memberchk(rule_name=_, Props).
|
|
535
|
+
rule_schema_shape_error(Props, "Rule schema fact missing required field: argument_names") :-
|
|
536
|
+
memberchk(rule_name=_, Props),
|
|
537
|
+
\+ memberchk(argument_names=_, Props).
|
|
538
|
+
rule_schema_shape_error(Props, "Rule schema fact missing required field: argument_types") :-
|
|
539
|
+
memberchk(argument_names=_, Props),
|
|
540
|
+
\+ memberchk(argument_types=_, Props).
|
|
541
|
+
rule_schema_shape_error(Props, "Rule schema fact argument_names and argument_types must have equal lengths") :-
|
|
542
|
+
memberchk(argument_names=RawNames, Props),
|
|
543
|
+
memberchk(argument_types=RawTypes, Props),
|
|
544
|
+
checks_normalize_atom_list(RawNames, Names),
|
|
545
|
+
checks_normalize_atom_list(RawTypes, Types),
|
|
546
|
+
length(Names, NameCount),
|
|
547
|
+
length(Types, TypeCount),
|
|
548
|
+
NameCount =\= TypeCount.
|
|
549
|
+
|
|
550
|
+
rule_shape_error(Props, "Rule fact missing required field: rule_ir") :-
|
|
551
|
+
\+ memberchk(rule_ir=_, Props).
|
|
552
|
+
rule_shape_error(Props, "Rule fact missing required field: rule_hash") :-
|
|
553
|
+
memberchk(rule_ir=_, Props),
|
|
554
|
+
\+ memberchk(rule_hash=_, Props).
|
|
555
|
+
rule_shape_error(Props, "Rule fact missing required field: rule_schema_id") :-
|
|
556
|
+
memberchk(rule_hash=_, Props),
|
|
557
|
+
\+ memberchk(rule_schema_id=_, Props).
|
|
558
|
+
rule_shape_error(Props, "Rule fact missing required field: rule_name") :-
|
|
559
|
+
memberchk(rule_schema_id=_, Props),
|
|
560
|
+
\+ memberchk(rule_name=_, Props).
|
|
561
|
+
rule_shape_error(Props, "Rule fact missing required field: semantic_key") :-
|
|
562
|
+
memberchk(rule_name=_, Props),
|
|
563
|
+
\+ memberchk(semantic_key=_, Props).
|
|
564
|
+
rule_shape_error(Props, "Rule fact rule_ir failed kibi.logic.v1 safety validation") :-
|
|
565
|
+
memberchk(rule_ir=_, Props),
|
|
566
|
+
logic_rule_safety(Props, Errors),
|
|
567
|
+
Errors \= [].
|
|
568
|
+
|
|
506
569
|
checks_normalize_integer(Raw, Integer) :-
|
|
507
570
|
( Raw = ^^(Value, _Type)
|
|
508
571
|
-> checks_normalize_integer(Value, Integer)
|
|
@@ -525,19 +588,140 @@ checks_normalize_atom_list(Raw, Atoms) :-
|
|
|
525
588
|
%% check_domain_contradictions(-Violations)
|
|
526
589
|
% Finds all pairs of requirements with contradicting required properties.
|
|
527
590
|
check_domain_contradictions(Violations) :-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
591
|
+
check_domain_contradiction_witnesses(Witnesses),
|
|
592
|
+
maplist(contradiction_witness_violation, Witnesses, Violations).
|
|
593
|
+
|
|
594
|
+
%% check_domain_contradiction_witnesses(-Witnesses)
|
|
595
|
+
% Exact, JSON-safe evidence for every strict-property, ground-predicate, and
|
|
596
|
+
% safe-rule contradiction result. Rule overlap that cannot be proven or
|
|
597
|
+
% disproven remains status=unresolved rather than becoming consistency.
|
|
598
|
+
check_domain_contradiction_witnesses(Witnesses) :-
|
|
599
|
+
findall(Witness, req_conflict_witness(_ReqA, _ReqB, Witness), GroundWitnesses),
|
|
600
|
+
findall(Witness, rule_contradiction_witness(Witness), RuleWitnesses),
|
|
601
|
+
append(GroundWitnesses, RuleWitnesses, Witnesses0),
|
|
602
|
+
sort(Witnesses0, Witnesses).
|
|
603
|
+
|
|
604
|
+
contradiction_witness_violation(Witness, violation(
|
|
605
|
+
'domain-contradictions',
|
|
606
|
+
EntityId,
|
|
607
|
+
Description,
|
|
608
|
+
Suggestion,
|
|
609
|
+
""
|
|
610
|
+
)) :-
|
|
611
|
+
Witness.requirements = [ReqA, ReqB],
|
|
612
|
+
format(string(EntityId), "~w/~w", [ReqA, ReqB]),
|
|
613
|
+
( Witness.kind == rule
|
|
614
|
+
-> Description = Witness.reason,
|
|
615
|
+
Suggestion = "Align the opposing rule heads, separate their scopes, or add an explicit supersedes relationship"
|
|
616
|
+
; format(string(Description), "~w [strict-readiness: contradiction-ready]", [Witness.reason]),
|
|
617
|
+
Suggestion = "Supersede one requirement or align both to the same canonical logical term"
|
|
618
|
+
).
|
|
619
|
+
|
|
620
|
+
opposing_rule_requirements(ReqA, ReqB, RuleA, RuleB) :-
|
|
621
|
+
opposing_rule_requirement_facts(ReqA, ReqB, _FactA, _FactB, RuleA, RuleB).
|
|
622
|
+
|
|
623
|
+
opposing_rule_requirement_facts(ReqA, ReqB, FactA, FactB, RuleA, RuleB) :-
|
|
624
|
+
kb:current_req(ReqA),
|
|
625
|
+
kb:current_req(ReqB),
|
|
626
|
+
ReqA @< ReqB,
|
|
627
|
+
kb_relationship(requires_rule, ReqA, FactA),
|
|
628
|
+
kb_relationship(requires_rule, ReqB, FactB),
|
|
629
|
+
FactA @< FactB,
|
|
630
|
+
kb_entity(FactA, fact, PropsA),
|
|
631
|
+
kb_entity(FactB, fact, PropsB),
|
|
632
|
+
memberchk(fact_kind=KindA, PropsA),
|
|
633
|
+
memberchk(fact_kind=KindB, PropsB),
|
|
634
|
+
normalize_term_atom(KindA, rule),
|
|
635
|
+
normalize_term_atom(KindB, rule),
|
|
636
|
+
logic_rule_from_props(PropsA, RuleA),
|
|
637
|
+
logic_rule_from_props(PropsB, RuleB).
|
|
638
|
+
|
|
639
|
+
rule_contradiction_witness(Witness) :-
|
|
640
|
+
opposing_rule_requirement_facts(ReqA, ReqB, FactA, FactB, RuleA, RuleB),
|
|
641
|
+
logic_rule_conflict_witness(RuleA, RuleB, InternalWitness),
|
|
642
|
+
InternalWitness.status \= disjoint,
|
|
643
|
+
rule_conflict_side(ReqA, FactA, Left),
|
|
644
|
+
rule_conflict_side(ReqB, FactB, Right),
|
|
645
|
+
rule_comparison_evidence(InternalWitness, Comparison),
|
|
646
|
+
format(string(Reason), "Rule conflict (~w) between ~w and ~w", [InternalWitness.status, ReqA, ReqB]),
|
|
647
|
+
Witness = _{
|
|
648
|
+
kind: rule,
|
|
649
|
+
status: InternalWitness.status,
|
|
650
|
+
requirements: [ReqA, ReqB],
|
|
651
|
+
reason: Reason,
|
|
652
|
+
left: Left,
|
|
653
|
+
right: Right,
|
|
654
|
+
comparison: Comparison
|
|
655
|
+
}.
|
|
656
|
+
|
|
657
|
+
rule_conflict_side(ReqId, FactId, Side) :-
|
|
658
|
+
evidence_entity_source(ReqId, req, RequirementSource),
|
|
659
|
+
kb_entity(FactId, fact, Props),
|
|
660
|
+
evidence_property_text(Props, source, FactSource),
|
|
661
|
+
evidence_property_text(Props, claim_key, ClaimKey),
|
|
662
|
+
evidence_property_text(Props, claim_text, ClaimText),
|
|
663
|
+
evidence_property_text(Props, rule_hash, RuleHash),
|
|
664
|
+
evidence_property_text(Props, semantic_key, SemanticKey),
|
|
665
|
+
evidence_property_text(Props, rule_ir, RuleIr),
|
|
666
|
+
evidence_property_integer(Props, claim_span_start, ClaimSpanStart),
|
|
667
|
+
evidence_property_integer(Props, claim_span_end, ClaimSpanEnd),
|
|
668
|
+
Side = _{
|
|
669
|
+
requirementId: ReqId,
|
|
670
|
+
requirementSource: RequirementSource,
|
|
671
|
+
factId: FactId,
|
|
672
|
+
factSource: FactSource,
|
|
673
|
+
claimKey: ClaimKey,
|
|
674
|
+
claimText: ClaimText,
|
|
675
|
+
claimSpan: _{start: ClaimSpanStart, end: ClaimSpanEnd},
|
|
676
|
+
ruleHash: RuleHash,
|
|
677
|
+
semanticKey: SemanticKey,
|
|
678
|
+
ruleIr: RuleIr
|
|
679
|
+
}.
|
|
680
|
+
|
|
681
|
+
rule_comparison_evidence(Internal, Evidence) :-
|
|
682
|
+
term_string(Internal.head_a, HeadA, [quoted(true), max_depth(0)]),
|
|
683
|
+
term_string(Internal.head_b, HeadB, [quoted(true), max_depth(0)]),
|
|
684
|
+
term_string(Internal.body_a, BodyA, [quoted(true), max_depth(0)]),
|
|
685
|
+
term_string(Internal.body_b, BodyB, [quoted(true), max_depth(0)]),
|
|
686
|
+
term_string(Internal.scope_a, ScopeA, [quoted(true), max_depth(0)]),
|
|
687
|
+
term_string(Internal.scope_b, ScopeB, [quoted(true), max_depth(0)]),
|
|
688
|
+
Evidence = _{
|
|
689
|
+
modalityA: Internal.modality_a,
|
|
690
|
+
modalityB: Internal.modality_b,
|
|
691
|
+
headA: HeadA,
|
|
692
|
+
headB: HeadB,
|
|
693
|
+
bodyA: BodyA,
|
|
694
|
+
bodyB: BodyB,
|
|
695
|
+
scopeA: ScopeA,
|
|
696
|
+
scopeB: ScopeB,
|
|
697
|
+
validFromA: Internal.valid_from_a,
|
|
698
|
+
validToA: Internal.valid_to_a,
|
|
699
|
+
validFromB: Internal.valid_from_b,
|
|
700
|
+
validToB: Internal.valid_to_b,
|
|
701
|
+
ruleSchemaA: Internal.rule_schema_a,
|
|
702
|
+
ruleSchemaB: Internal.rule_schema_b
|
|
703
|
+
}.
|
|
704
|
+
|
|
705
|
+
evidence_entity_source(EntityId, Type, Source) :-
|
|
706
|
+
kb_entity(EntityId, Type, Props),
|
|
707
|
+
evidence_property_text(Props, source, Source).
|
|
708
|
+
|
|
709
|
+
evidence_property_text(Props, Key, Text) :-
|
|
710
|
+
(memberchk(Key=Raw, Props) -> evidence_term_text(Raw, Text) ; Text = '').
|
|
711
|
+
|
|
712
|
+
evidence_property_integer(Props, Key, Integer) :-
|
|
713
|
+
(memberchk(Key=Raw, Props), catch(checks_normalize_integer(Raw, Integer), _, fail) -> true ; Integer = -1).
|
|
714
|
+
|
|
715
|
+
evidence_term_text(Raw, Text) :-
|
|
716
|
+
( Raw = ^^(Value, _Type)
|
|
717
|
+
-> evidence_term_text(Value, Text)
|
|
718
|
+
; Raw = literal(type(_, Value))
|
|
719
|
+
-> evidence_term_text(Value, Text)
|
|
720
|
+
; atom(Raw)
|
|
721
|
+
-> Text = Raw
|
|
722
|
+
; string(Raw)
|
|
723
|
+
-> atom_string(Text, Raw)
|
|
724
|
+
; term_string(Raw, String), atom_string(Text, String)
|
|
541
725
|
).
|
|
542
726
|
|
|
543
727
|
%% check_strict_req_fact_pairing(-Violations)
|
|
@@ -816,7 +1000,7 @@ logic_coverage_violation(violation(
|
|
|
816
1000
|
'logic-coverage',
|
|
817
1001
|
ReqId,
|
|
818
1002
|
Description,
|
|
819
|
-
"Ground every declared claim with a property_value
|
|
1003
|
+
"Ground every declared claim with a property_value, predicate, or rule fact via its typed relationship, preserving the same claim_key",
|
|
820
1004
|
Source
|
|
821
1005
|
)) :-
|
|
822
1006
|
kb:current_req(ReqId),
|
|
@@ -901,6 +1085,9 @@ grounded_requirement_claim_fact(ReqId, ClaimKey, FactId) :-
|
|
|
901
1085
|
grounded_requirement_claim_fact(ReqId, ClaimKey, FactId) :-
|
|
902
1086
|
kb_relationship(requires_predicate, ReqId, FactId),
|
|
903
1087
|
ground_fact_claim_key(FactId, predicate, ClaimKey).
|
|
1088
|
+
grounded_requirement_claim_fact(ReqId, ClaimKey, FactId) :-
|
|
1089
|
+
kb_relationship(requires_rule, ReqId, FactId),
|
|
1090
|
+
ground_fact_claim_key(FactId, rule, ClaimKey).
|
|
904
1091
|
|
|
905
1092
|
logical_ground_signature(FactId, predicate(Namespace, Name, Args, Polarity)) :-
|
|
906
1093
|
kb:predicate_fact(FactId, Namespace, Name, Args, Polarity).
|
|
@@ -919,6 +1106,14 @@ logical_ground_signature(
|
|
|
919
1106
|
Scope,
|
|
920
1107
|
Polarity
|
|
921
1108
|
).
|
|
1109
|
+
logical_ground_signature(FactId, rule(SemanticKey)) :-
|
|
1110
|
+
kb_entity(FactId, fact, Props),
|
|
1111
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1112
|
+
normalize_term_atom(RawKind, rule),
|
|
1113
|
+
( memberchk(semantic_key=RawKey, Props)
|
|
1114
|
+
-> normalize_term_atom(RawKey, SemanticKey)
|
|
1115
|
+
; memberchk(rule_hash=RawKey, Props), normalize_term_atom(RawKey, SemanticKey)
|
|
1116
|
+
).
|
|
922
1117
|
|
|
923
1118
|
ground_fact_claim_key(FactId, ExpectedKind, ClaimKey) :-
|
|
924
1119
|
kb_entity(FactId, fact, Props),
|
|
@@ -930,6 +1125,175 @@ ground_fact_claim_key(FactId, ExpectedKind, ClaimKey) :-
|
|
|
930
1125
|
normalize_term_atom(RawClaimText, ClaimText),
|
|
931
1126
|
ClaimText \= ''.
|
|
932
1127
|
|
|
1128
|
+
%% check_rule_safety(-Violations)
|
|
1129
|
+
% Every stored rule must decode through the closed Logic IR vocabulary and
|
|
1130
|
+
% pass the independent Prolog-side safety checks.
|
|
1131
|
+
check_rule_safety(Violations) :-
|
|
1132
|
+
findall(Violation, rule_safety_violation(Violation), Raw),
|
|
1133
|
+
sort(Raw, Violations).
|
|
1134
|
+
|
|
1135
|
+
rule_safety_violation(violation(
|
|
1136
|
+
'rule-safety',
|
|
1137
|
+
FactId,
|
|
1138
|
+
Description,
|
|
1139
|
+
"Use kibi.logic.v1 typed IR; remove raw Prolog, unsafe variables, or unstratified negation",
|
|
1140
|
+
Source
|
|
1141
|
+
)) :-
|
|
1142
|
+
kb_entity(FactId, fact, Props),
|
|
1143
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1144
|
+
normalize_term_atom(RawKind, rule),
|
|
1145
|
+
logic_rule_safety(Props, Errors),
|
|
1146
|
+
Errors \= [],
|
|
1147
|
+
format(string(Description), "Rule fact ~w failed safety validation: ~w", [FactId, Errors]),
|
|
1148
|
+
violation_source(FactId, fact, Source).
|
|
1149
|
+
|
|
1150
|
+
% Local rule checks cannot detect a negation cycle spanning multiple facts.
|
|
1151
|
+
% Run the finite dependency-graph check over the complete stored rule set and
|
|
1152
|
+
% report each participating negated rule as infrastructure-grade unsafe IR.
|
|
1153
|
+
rule_safety_violation(violation(
|
|
1154
|
+
'rule-safety',
|
|
1155
|
+
FactId,
|
|
1156
|
+
"Stored rules contain an unstratified negation dependency cycle",
|
|
1157
|
+
"Separate the negated dependency into strata or replace it with an explicit closed-world fact",
|
|
1158
|
+
Source
|
|
1159
|
+
)) :-
|
|
1160
|
+
findall(Rule, stored_rule_term(Rule), Rules),
|
|
1161
|
+
Rules \= [],
|
|
1162
|
+
\+ logic_rules_stratified(Rules),
|
|
1163
|
+
stored_rule_fact(FactId, _Props, Rule),
|
|
1164
|
+
rule_contains_negation(Rule),
|
|
1165
|
+
violation_source(FactId, fact, Source).
|
|
1166
|
+
|
|
1167
|
+
stored_rule_term(Rule) :-
|
|
1168
|
+
kb_entity(_FactId, fact, Props),
|
|
1169
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1170
|
+
normalize_term_atom(RawKind, rule),
|
|
1171
|
+
logic_rule_from_props(Props, Rule).
|
|
1172
|
+
|
|
1173
|
+
stored_rule_fact(FactId, Props, Rule) :-
|
|
1174
|
+
kb_entity(FactId, fact, Props),
|
|
1175
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1176
|
+
normalize_term_atom(RawKind, rule),
|
|
1177
|
+
logic_rule_from_props(Props, Rule).
|
|
1178
|
+
|
|
1179
|
+
rule_contains_negation(rule(_, _, _Head, Body, Exceptions, _Scope, _From, _To, _Schema, _Variables)) :-
|
|
1180
|
+
( expression_contains_negation(Body)
|
|
1181
|
+
; member(Exception, Exceptions), expression_contains_negation(Exception)
|
|
1182
|
+
),
|
|
1183
|
+
!.
|
|
1184
|
+
|
|
1185
|
+
expression_contains_negation(not(_)).
|
|
1186
|
+
expression_contains_negation(all(Items)) :- member(Item, Items), expression_contains_negation(Item).
|
|
1187
|
+
expression_contains_negation(any(Items)) :- member(Item, Items), expression_contains_negation(Item).
|
|
1188
|
+
expression_contains_negation(count(Atom, _Operator, _Value)) :- expression_contains_negation(Atom).
|
|
1189
|
+
|
|
1190
|
+
%% check_rule_verifiability(-Violations)
|
|
1191
|
+
% A requires_rule edge is meaningful only when it reaches a rule fact whose
|
|
1192
|
+
% schema endpoint exists and whose rule IR is safe.
|
|
1193
|
+
check_rule_verifiability(Violations) :-
|
|
1194
|
+
findall(Violation, rule_verifiability_violation(Violation), Raw),
|
|
1195
|
+
sort(Raw, Violations).
|
|
1196
|
+
|
|
1197
|
+
rule_verifiability_violation(violation(
|
|
1198
|
+
'rule-verifiability',
|
|
1199
|
+
ReqId,
|
|
1200
|
+
Description,
|
|
1201
|
+
"Link requires_rule to fact_kind=rule with a valid rule_schema_id and safe kibi.logic.v1 IR",
|
|
1202
|
+
Source
|
|
1203
|
+
)) :-
|
|
1204
|
+
kb:current_req(ReqId),
|
|
1205
|
+
kb_relationship(requires_rule, ReqId, FactId),
|
|
1206
|
+
kb_entity(FactId, fact, Props),
|
|
1207
|
+
rule_verifiability_description(Props, FactId, Description),
|
|
1208
|
+
violation_source(ReqId, req, Source).
|
|
1209
|
+
|
|
1210
|
+
rule_verifiability_description(Props, FactId, Description) :-
|
|
1211
|
+
( memberchk(fact_kind=RawKind, Props)
|
|
1212
|
+
-> normalize_term_atom(RawKind, Kind)
|
|
1213
|
+
; Kind = legacy
|
|
1214
|
+
),
|
|
1215
|
+
Kind \= rule,
|
|
1216
|
+
format(string(Description), "Requirement uses requires_rule ~w but target is fact_kind=~w", [FactId, Kind]).
|
|
1217
|
+
rule_verifiability_description(Props, FactId, Description) :-
|
|
1218
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1219
|
+
normalize_term_atom(RawKind, rule),
|
|
1220
|
+
\+ memberchk(rule_schema_id=_, Props),
|
|
1221
|
+
format(string(Description), "Rule fact ~w has no rule_schema_id", [FactId]).
|
|
1222
|
+
rule_verifiability_description(Props, FactId, Description) :-
|
|
1223
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1224
|
+
normalize_term_atom(RawKind, rule),
|
|
1225
|
+
memberchk(rule_schema_id=SchemaId, Props),
|
|
1226
|
+
\+ kb_entity(SchemaId, fact, _),
|
|
1227
|
+
format(string(Description), "Rule fact ~w references missing rule schema ~w", [FactId, SchemaId]).
|
|
1228
|
+
rule_verifiability_description(Props, FactId, Description) :-
|
|
1229
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1230
|
+
normalize_term_atom(RawKind, rule),
|
|
1231
|
+
memberchk(rule_schema_id=SchemaId, Props),
|
|
1232
|
+
kb_entity(SchemaId, fact, SchemaProps),
|
|
1233
|
+
\+ valid_rule_schema_props(SchemaProps),
|
|
1234
|
+
format(string(Description), "Rule fact ~w references ~w, which is not a rule_schema fact", [FactId, SchemaId]).
|
|
1235
|
+
rule_verifiability_description(Props, FactId, Description) :-
|
|
1236
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1237
|
+
normalize_term_atom(RawKind, rule),
|
|
1238
|
+
logic_rule_safety(Props, Errors),
|
|
1239
|
+
Errors \= [],
|
|
1240
|
+
format(string(Description), "Rule fact ~w is not safe: ~w", [FactId, Errors]).
|
|
1241
|
+
|
|
1242
|
+
valid_rule_schema_props(Props) :-
|
|
1243
|
+
memberchk(fact_kind=RawKind, Props),
|
|
1244
|
+
normalize_term_atom(RawKind, rule_schema),
|
|
1245
|
+
memberchk(rule_name=Name, Props),
|
|
1246
|
+
nonempty_normalized(Name),
|
|
1247
|
+
memberchk(argument_names=RawNames, Props),
|
|
1248
|
+
memberchk(argument_types=RawTypes, Props),
|
|
1249
|
+
checks_normalize_atom_list(RawNames, Names),
|
|
1250
|
+
checks_normalize_atom_list(RawTypes, Types),
|
|
1251
|
+
same_length(Names, Types).
|
|
1252
|
+
|
|
1253
|
+
nonempty_normalized(Value) :-
|
|
1254
|
+
normalize_term_atom(Value, Atom),
|
|
1255
|
+
Atom \= ''.
|
|
1256
|
+
|
|
1257
|
+
%% check_semantic_completeness(-Violations)
|
|
1258
|
+
% The proposition ledger is additive and optional during migration. When it is
|
|
1259
|
+
% present, every assertive proposition must either be modeled or explicitly
|
|
1260
|
+
% recorded as an unresolved review state; silently missing entries are errors.
|
|
1261
|
+
check_semantic_completeness(Violations) :-
|
|
1262
|
+
findall(Violation, semantic_completeness_violation(Violation), Raw),
|
|
1263
|
+
sort(Raw, Violations).
|
|
1264
|
+
|
|
1265
|
+
semantic_completeness_violation(violation(
|
|
1266
|
+
'semantic-completeness',
|
|
1267
|
+
ReqId,
|
|
1268
|
+
Description,
|
|
1269
|
+
"Run kb_semantic_advisor again, preserve every proposition span, and model or explicitly classify each assertive proposition",
|
|
1270
|
+
Source
|
|
1271
|
+
)) :-
|
|
1272
|
+
kb:current_req(ReqId),
|
|
1273
|
+
kb_entity(ReqId, req, Props),
|
|
1274
|
+
memberchk(semantic_inventory=RawInventory, Props),
|
|
1275
|
+
inventory_entries(RawInventory, Entries),
|
|
1276
|
+
member(Entry, Entries),
|
|
1277
|
+
inventory_entry_status(Entry, Status),
|
|
1278
|
+
memberchk(Status, [missing]),
|
|
1279
|
+
format(string(Description), "Requirement proposition ledger contains an unclassified assertive span (~w)", [Entry]),
|
|
1280
|
+
violation_source(ReqId, req, Source).
|
|
1281
|
+
|
|
1282
|
+
inventory_entries(Raw, Entries) :-
|
|
1283
|
+
( is_list(Raw) -> Entries = Raw
|
|
1284
|
+
; Raw = ^^(Value, _) -> inventory_entries(Value, Entries)
|
|
1285
|
+
; (atom(Raw) ; string(Raw)),
|
|
1286
|
+
catch(atom_json_dict(Raw, JsonEntries, [value_string_as(string)]), _, fail),
|
|
1287
|
+
is_list(JsonEntries)
|
|
1288
|
+
-> Entries = JsonEntries
|
|
1289
|
+
; Entries = []
|
|
1290
|
+
).
|
|
1291
|
+
|
|
1292
|
+
inventory_entry_status(Entry, Status) :-
|
|
1293
|
+
is_dict(Entry), get_dict(status, Entry, Raw), normalize_term_atom(Raw, Status).
|
|
1294
|
+
inventory_entry_status(Entry, Status) :-
|
|
1295
|
+
is_list(Entry), memberchk(status=Raw, Entry), normalize_term_atom(Raw, Status).
|
|
1296
|
+
|
|
933
1297
|
strict_readiness_has_fact_link(ReqId) :-
|
|
934
1298
|
kb_relationship(constrains, ReqId, FactId),
|
|
935
1299
|
kb_entity(FactId, fact, _),
|
|
@@ -1004,6 +1368,9 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
1004
1368
|
check_strict_readiness(StrictReadiness),
|
|
1005
1369
|
check_predicate_verifiability(PredicateVerifiability),
|
|
1006
1370
|
check_logic_coverage(LogicCoverage),
|
|
1371
|
+
check_rule_safety(RuleSafety),
|
|
1372
|
+
check_rule_verifiability(RuleVerifiability),
|
|
1373
|
+
check_semantic_completeness(SemanticCompleteness),
|
|
1007
1374
|
ViolationsDict = _{
|
|
1008
1375
|
must_priority_coverage: MustPriority,
|
|
1009
1376
|
symbol_coverage: SymbolCoverage,
|
|
@@ -1017,7 +1384,10 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
|
|
|
1017
1384
|
strict_req_fact_pairing: StrictReqFactPairing,
|
|
1018
1385
|
strict_readiness: StrictReadiness,
|
|
1019
1386
|
predicate_verifiability: PredicateVerifiability,
|
|
1020
|
-
logic_coverage: LogicCoverage
|
|
1387
|
+
logic_coverage: LogicCoverage,
|
|
1388
|
+
rule_safety: RuleSafety,
|
|
1389
|
+
rule_verifiability: RuleVerifiability,
|
|
1390
|
+
semantic_completeness: SemanticCompleteness
|
|
1021
1391
|
}.
|
|
1022
1392
|
|
|
1023
1393
|
%% violations_dict_to_json(+ViolationsDict, -JsonDict)
|
|
@@ -1038,7 +1408,28 @@ pairs_to_json_pairs([Key-Violations|Rest], [Key-JsonViolations|JsonRest]) :-
|
|
|
1038
1408
|
% Converts a violation(Rule, EntityId, Description, Suggestion, Source) term
|
|
1039
1409
|
% to a JSON-compatible dict.
|
|
1040
1410
|
violation_to_json(Violation, JsonDict) :-
|
|
1041
|
-
violation_term_to_dict(Violation,
|
|
1411
|
+
violation_term_to_dict(Violation, BaseDict),
|
|
1412
|
+
( contradiction_violation_witnesses(Violation, Witnesses),
|
|
1413
|
+
Witnesses \= []
|
|
1414
|
+
-> put_dict(evidence, BaseDict, _{witnesses: Witnesses}, JsonDict)
|
|
1415
|
+
; JsonDict = BaseDict
|
|
1416
|
+
).
|
|
1417
|
+
|
|
1418
|
+
contradiction_violation_witnesses(
|
|
1419
|
+
violation('domain-contradictions', EntityId, Description, _Suggestion, _Source),
|
|
1420
|
+
Witnesses
|
|
1421
|
+
) :-
|
|
1422
|
+
violation_id_text(EntityId, EntityIdText),
|
|
1423
|
+
violation_text(Description, DescriptionText),
|
|
1424
|
+
check_domain_contradiction_witnesses(AllWitnesses),
|
|
1425
|
+
include(witness_matches_violation(EntityIdText, DescriptionText), AllWitnesses, Witnesses).
|
|
1426
|
+
|
|
1427
|
+
witness_matches_violation(EntityIdText, DescriptionText, Witness) :-
|
|
1428
|
+
Witness.requirements = [ReqA, ReqB],
|
|
1429
|
+
format(string(ExpectedEntityId), "~w/~w", [ReqA, ReqB]),
|
|
1430
|
+
ExpectedEntityId == EntityIdText,
|
|
1431
|
+
violation_text(Witness.reason, ReasonText),
|
|
1432
|
+
sub_string(DescriptionText, 0, _, _, ReasonText).
|
|
1042
1433
|
|
|
1043
1434
|
violation_term_to_dict(violation(Rule, EntityId, Description, Suggestion, Source), JsonDict) :-
|
|
1044
1435
|
violation_text(Rule, RuleText),
|