kibi-core 0.27.0 → 0.29.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/schema/entities.pl +12 -0
- package/src/cases.pl +311 -0
- package/src/checks.pl +179 -0
- package/src/discovery.pl +9 -2
- package/src/model_check.pl +209 -6
- package/src/requirement_proof.pl +467 -19
- package/src/rule_registry.pl +10 -0
package/src/requirement_proof.pl
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
:- use_module('checks.pl', [
|
|
21
21
|
check_domain_contradictions_and_witnesses/2,
|
|
22
22
|
infeasible_scenario/5,
|
|
23
|
+
negative_scenario_witness/3,
|
|
24
|
+
opposing_assumption/2,
|
|
23
25
|
scenario_feasibility_outcome/2,
|
|
24
26
|
check_rule_safety/1,
|
|
25
27
|
check_rule_verifiability/1
|
|
@@ -28,6 +30,7 @@
|
|
|
28
30
|
:- use_module('code_certificates.pl', [code_certificate_check/2, code_certificate_digest/2]).
|
|
29
31
|
:- use_module('claim_obligations.pl', [claim_obligation/3, obligation_equal/2]).
|
|
30
32
|
:- use_module('model_check.pl', [model_check_obligation/4]).
|
|
33
|
+
:- use_module('cases.pl', [fact_cases/3, case_covering_scenarios/3, case_dict/2, fact_case_waiver/2]).
|
|
31
34
|
|
|
32
35
|
proof_version('kibi.requirement-proof.v5').
|
|
33
36
|
|
|
@@ -155,12 +158,18 @@ compute_requirement_proof(ReqId, ReqProps, Context, Proof) :-
|
|
|
155
158
|
PassingE2eStage.receiptEvidence, Context,
|
|
156
159
|
ProductionStage, ProductionSymbols),
|
|
157
160
|
source_coordinate_stage(ReqProps, ExecutableSymbols, ProductionSymbols, CoordinateStage),
|
|
161
|
+
negative_path_stage(ReqId, ScenarioStage, ScenarioTestStage, Context, NegativePathStage),
|
|
162
|
+
dimension_stage(ReqId, ScenarioTestStage, Context, DimensionStage),
|
|
163
|
+
cases_stage(ReqId, ScenarioStage, ScenarioTestStage, Context, CasesStage),
|
|
158
164
|
Stages = _{
|
|
159
165
|
semanticInventory: SemanticStage,
|
|
160
166
|
logicGrounding: LogicStage,
|
|
161
167
|
contradictions: ContradictionStage,
|
|
162
168
|
scenarios: ScenarioStage,
|
|
163
169
|
scenarioTests: ScenarioTestStage,
|
|
170
|
+
negativePath: NegativePathStage,
|
|
171
|
+
dimension: DimensionStage,
|
|
172
|
+
cases: CasesStage,
|
|
164
173
|
passingE2e: PassingE2eStage,
|
|
165
174
|
executableSymbols: ExecutableStage,
|
|
166
175
|
productionSymbols: ProductionStage,
|
|
@@ -688,6 +697,12 @@ scenario_stage(ReqId, Stage, ScenarioIds) :-
|
|
|
688
697
|
unknown_feasibility_reason(Reason, ReasonText)
|
|
689
698
|
),
|
|
690
699
|
UnknownFeasibility),
|
|
700
|
+
% implements REQ-kibi-happy-path-gap
|
|
701
|
+
% The linked scenarios that exercise this requirement's failure side: they
|
|
702
|
+
% expect rejection or error and assume a value it forbids.
|
|
703
|
+
findall(ScenarioId,
|
|
704
|
+
(member(ScenarioId, ScenarioIds), once(negative_scenario_witness(ScenarioId, ReqId, _))),
|
|
705
|
+
NegativeScenarios),
|
|
691
706
|
( (ScenarioIds = [] ; InvalidScenarioTargets \= [])
|
|
692
707
|
-> Status = missing
|
|
693
708
|
; InfeasibleScenarios \= []
|
|
@@ -702,6 +717,7 @@ scenario_stage(ReqId, Stage, ScenarioIds) :-
|
|
|
702
717
|
invalidScenarioTargets: InvalidScenarioTargets,
|
|
703
718
|
infeasibleScenarios: InfeasibleScenarios,
|
|
704
719
|
unknownFeasibility: UnknownFeasibility,
|
|
720
|
+
negativeScenarios: NegativeScenarios,
|
|
705
721
|
sources: Sources
|
|
706
722
|
}.
|
|
707
723
|
|
|
@@ -716,6 +732,376 @@ unknown_feasibility_reason(undetermined_validity(_), undetermined_validity).
|
|
|
716
732
|
existing_scenario(ScenarioId) :-
|
|
717
733
|
kb_entity(ScenarioId, scenario, _).
|
|
718
734
|
|
|
735
|
+
%% negative_path_stage(+ReqId, +ScenarioStage, +ScenarioTestStage, +Context, -Stage)
|
|
736
|
+
% implements REQ-kibi-happy-path-gap
|
|
737
|
+
% A requirement is not tested while only its happy path is: when it grounds
|
|
738
|
+
% a claim (a property value, predicate or rule), at least one linked
|
|
739
|
+
% scenario must expect rejection or error and assume a value or situation
|
|
740
|
+
% the requirement forbids. Claims a fresh certificate proves at code scope
|
|
741
|
+
% decide their failure side already and need none. Status is not_required
|
|
742
|
+
% (no such claim, or all certified), passed, waived (an approved
|
|
743
|
+
% failure_side_waiver) or missing.
|
|
744
|
+
negative_path_stage(ReqId, ScenarioStage, ScenarioTestStage, Context, Stage) :-
|
|
745
|
+
findall(Key-FactId,
|
|
746
|
+
( negative_path_link(Link),
|
|
747
|
+
kb_relationship(Link, ReqId, FactId),
|
|
748
|
+
fact_claim_key_or_id(FactId, Key)
|
|
749
|
+
),
|
|
750
|
+
ClaimFacts0),
|
|
751
|
+
sort(ClaimFacts0, ClaimFacts),
|
|
752
|
+
certified_claim_keys(ReqId, ScenarioTestStage, Context, Certified),
|
|
753
|
+
exclude(certified_claim(Certified), ClaimFacts, OpenFacts),
|
|
754
|
+
pairs_keys(OpenFacts, Open0),
|
|
755
|
+
sort(Open0, Open),
|
|
756
|
+
pending_certified_claim_keys(ScenarioTestStage, Context, Open, Pending),
|
|
757
|
+
( get_dict(negativeScenarios, ScenarioStage, Negatives) -> true ; Negatives = [] ),
|
|
758
|
+
failure_side_waiver(ReqId, Waiver),
|
|
759
|
+
( Open == []
|
|
760
|
+
-> Status = not_required
|
|
761
|
+
; Negatives \== []
|
|
762
|
+
-> Status = passed
|
|
763
|
+
; get_dict(approvedBy, Waiver, _)
|
|
764
|
+
-> Status = waived
|
|
765
|
+
; Status = missing
|
|
766
|
+
),
|
|
767
|
+
( Status == missing
|
|
768
|
+
-> findall(_{claimKey: Key, factId: FactId, assumption: Assumption, expects: rejection},
|
|
769
|
+
(member(Key-FactId, OpenFacts), once(opposing_assumption(FactId, Assumption))),
|
|
770
|
+
Suggestions)
|
|
771
|
+
; Suggestions = []
|
|
772
|
+
),
|
|
773
|
+
Stage = _{
|
|
774
|
+
status: Status,
|
|
775
|
+
claims: Open,
|
|
776
|
+
certifiedClaims: Certified,
|
|
777
|
+
negativeScenarios: Negatives,
|
|
778
|
+
suggestedNegatives: Suggestions,
|
|
779
|
+
pendingReproofClaims: Pending,
|
|
780
|
+
waiver: Waiver
|
|
781
|
+
}.
|
|
782
|
+
|
|
783
|
+
% Open claims a certificate test proves (its contract lists them in proves)
|
|
784
|
+
% whose receipt is only older than the code: re-running kibi prove certifies
|
|
785
|
+
% them, so the evidence floor treats them like any other stale receipt.
|
|
786
|
+
pending_certified_claim_keys(ScenarioTestStage, Context, Open, Keys) :-
|
|
787
|
+
( get_dict(tests, ScenarioTestStage, Tests) -> true ; Tests = [] ),
|
|
788
|
+
findall(Key,
|
|
789
|
+
( member(TestId, Tests),
|
|
790
|
+
test_receipt_evidence(Context, [unit, integration, end_to_end], TestId, Evidence),
|
|
791
|
+
get_dict(state, Evidence, stale),
|
|
792
|
+
kb_entity(TestId, test, Props),
|
|
793
|
+
memberchk(proof_contract=RawContract, Props),
|
|
794
|
+
proof_contract_dict(RawContract, Contract),
|
|
795
|
+
get_dict(proves, Contract, Proves),
|
|
796
|
+
is_list(Proves),
|
|
797
|
+
member(RawKey, Proves),
|
|
798
|
+
normalize_atom(RawKey, Key),
|
|
799
|
+
memberchk(Key, Open)
|
|
800
|
+
),
|
|
801
|
+
Keys0),
|
|
802
|
+
sort(Keys0, Keys).
|
|
803
|
+
|
|
804
|
+
%% failure_side_waiver(+ReqId, -Waiver)
|
|
805
|
+
% A requirement whose claims state a capability or an output shape has no
|
|
806
|
+
% input to refuse, so its failure side can be waived: failure_side_waiver
|
|
807
|
+
% gives the reason and failure_side_waiver_approved_by names the human who
|
|
808
|
+
% accepted it. Only an approved waiver satisfies the stage; a reason alone
|
|
809
|
+
% is reported as pending approval. Waiver is _{} when none is recorded.
|
|
810
|
+
failure_side_waiver(ReqId, Waiver) :-
|
|
811
|
+
( kb_entity(ReqId, req, Props),
|
|
812
|
+
memberchk(failure_side_waiver=RawReason, Props),
|
|
813
|
+
normalize_atom(RawReason, Reason),
|
|
814
|
+
Reason \== ''
|
|
815
|
+
-> atom_string(Reason, ReasonText),
|
|
816
|
+
( memberchk(failure_side_waiver_approved_by=RawBy, Props),
|
|
817
|
+
normalize_atom(RawBy, By),
|
|
818
|
+
By \== ''
|
|
819
|
+
-> atom_string(By, ByText),
|
|
820
|
+
Waiver = _{reason: ReasonText, approvedBy: ByText}
|
|
821
|
+
; Waiver = _{reason: ReasonText}
|
|
822
|
+
)
|
|
823
|
+
; Waiver = _{}
|
|
824
|
+
).
|
|
825
|
+
|
|
826
|
+
certified_claim(Certified, Key-_) :- memberchk(Key, Certified).
|
|
827
|
+
|
|
828
|
+
%% cases_stage(+ReqId, +ScenarioStage, +ScenarioTestStage, +Context, -Stage)
|
|
829
|
+
% implements REQ-kibi-case-coverage
|
|
830
|
+
% Edge cases derived from the requirement's typed property claims
|
|
831
|
+
% (cases.pl): the bound and the value past it, zero, one and many for a
|
|
832
|
+
% count, each member of a set and a value outside it. A case is covered
|
|
833
|
+
% when a linked scenario, other than a draft, assumes a value in its class
|
|
834
|
+
% and expects the outcome the claim predicts. Every case whose predicted
|
|
835
|
+
% outcome is rejection must be covered: that is where a test of the one
|
|
836
|
+
% case that works hides the case that breaks. Uncovered success cases and
|
|
837
|
+
% cases no claim decides (unspecified) are reported, not blocking. Claims
|
|
838
|
+
% a fresh certificate proves at code scope are decided already. A grounding
|
|
839
|
+
% fact can carry a case_waiver with case_waiver_approved_by. Status is
|
|
840
|
+
% not_required (no rejection case), passed, waived (every uncovered
|
|
841
|
+
% rejection case is on a fact with an approved waiver) or missing.
|
|
842
|
+
cases_stage(ReqId, ScenarioStage, ScenarioTestStage, Context, Stage) :-
|
|
843
|
+
findall(FactId, kb_relationship(requires_property, ReqId, FactId), Facts0),
|
|
844
|
+
sort(Facts0, Facts),
|
|
845
|
+
certified_claim_keys(ReqId, ScenarioTestStage, Context, Certified),
|
|
846
|
+
( get_dict(scenarios, ScenarioStage, ScenarioIds) -> true ; ScenarioIds = [] ),
|
|
847
|
+
foldl(fact_case_rows(Certified, ScenarioIds), Facts, rows([], false), rows(Rows0, LimitReached)),
|
|
848
|
+
reverse(Rows0, Rows),
|
|
849
|
+
include(case_row_blocking, Rows, Blocking),
|
|
850
|
+
partition(case_row_waived, Blocking, WaivedRows, OpenRows),
|
|
851
|
+
findall(Key, (member(Row, OpenRows), get_dict(claimKey, Row, Key)), OpenKeys0),
|
|
852
|
+
sort(OpenKeys0, OpenKeys),
|
|
853
|
+
pending_certified_claim_keys(ScenarioTestStage, Context, OpenKeys, Pending),
|
|
854
|
+
case_summary(Rows, Summary),
|
|
855
|
+
( \+ (member(Row, Rows), get_dict(predicted, Row, rejection))
|
|
856
|
+
-> Status = not_required
|
|
857
|
+
; OpenRows == [],
|
|
858
|
+
WaivedRows == []
|
|
859
|
+
-> Status = passed
|
|
860
|
+
; OpenRows == []
|
|
861
|
+
-> Status = waived
|
|
862
|
+
; Status = missing
|
|
863
|
+
),
|
|
864
|
+
Stage = _{
|
|
865
|
+
status: Status,
|
|
866
|
+
cases: Rows,
|
|
867
|
+
uncoveredCases: OpenRows,
|
|
868
|
+
waivedCases: WaivedRows,
|
|
869
|
+
summary: Summary,
|
|
870
|
+
limitReached: LimitReached,
|
|
871
|
+
pendingReproofClaims: Pending
|
|
872
|
+
}.
|
|
873
|
+
|
|
874
|
+
fact_case_rows(Certified, ScenarioIds, FactId, rows(Acc0, Limit0), rows(Acc, Limit)) :-
|
|
875
|
+
( fact_cases(FactId, Cases, FactLimit),
|
|
876
|
+
Cases = [case(_, ClaimKey, _, _, _, _, _)|_],
|
|
877
|
+
\+ memberchk(ClaimKey, Certified)
|
|
878
|
+
-> fact_case_waiver(FactId, Waiver),
|
|
879
|
+
foldl(case_row(ScenarioIds, Waiver), Cases, Acc0, Acc),
|
|
880
|
+
( FactLimit == true -> Limit = true ; Limit = Limit0 )
|
|
881
|
+
; Acc = Acc0,
|
|
882
|
+
Limit = Limit0
|
|
883
|
+
).
|
|
884
|
+
|
|
885
|
+
case_row(ScenarioIds, Waiver, Case, Acc, [Row|Acc]) :-
|
|
886
|
+
case_dict(Case, Dict),
|
|
887
|
+
case_covering_scenarios(ScenarioIds, Case, Covering),
|
|
888
|
+
( Covering \== []
|
|
889
|
+
-> Coverage = covered
|
|
890
|
+
; get_dict(predicted, Dict, unspecified)
|
|
891
|
+
-> Coverage = unspecified
|
|
892
|
+
; get_dict(approvedBy, Waiver, _)
|
|
893
|
+
-> Coverage = waived
|
|
894
|
+
; Coverage = uncovered
|
|
895
|
+
),
|
|
896
|
+
Row0 = Dict.put(_{coverage: Coverage, scenarios: Covering}),
|
|
897
|
+
( Waiver == _{} -> Row = Row0 ; Row = Row0.put(waiver, Waiver) ).
|
|
898
|
+
|
|
899
|
+
% A rejection case blocks until a scenario covers it or its fact's waiver
|
|
900
|
+
% is approved.
|
|
901
|
+
case_row_blocking(Row) :-
|
|
902
|
+
get_dict(predicted, Row, rejection),
|
|
903
|
+
get_dict(coverage, Row, Coverage),
|
|
904
|
+
memberchk(Coverage, [uncovered, waived]).
|
|
905
|
+
|
|
906
|
+
case_row_waived(Row) :- get_dict(coverage, Row, waived).
|
|
907
|
+
|
|
908
|
+
case_summary(Rows, _{generated: Generated, covered: Covered, uncovered: Uncovered,
|
|
909
|
+
unspecified: Unspecified, waived: Waived}) :-
|
|
910
|
+
length(Rows, Generated),
|
|
911
|
+
coverage_count(Rows, covered, Covered),
|
|
912
|
+
coverage_count(Rows, uncovered, Uncovered),
|
|
913
|
+
coverage_count(Rows, unspecified, Unspecified),
|
|
914
|
+
coverage_count(Rows, waived, Waived).
|
|
915
|
+
|
|
916
|
+
coverage_count(Rows, Coverage, Count) :-
|
|
917
|
+
aggregate_all(count, (member(Row, Rows), get_dict(coverage, Row, Coverage)), Count).
|
|
918
|
+
|
|
919
|
+
%% dimension_stage(+ReqId, +ScenarioTestStage, +Context, -Stage)
|
|
920
|
+
% implements REQ-kibi-dimension-gap
|
|
921
|
+
% A claim that quantifies over a collection (each, every, all) or bounds a
|
|
922
|
+
% cardinality (at least, at most, exactly, no more than) must be grounded on what
|
|
923
|
+
% varies: a count or bound, a set, a rule over a variable or a state
|
|
924
|
+
% machine. A claim whose grounding facts are all booleans or ground
|
|
925
|
+
% predicates states the verdict, not the dimension, so a scenario with one
|
|
926
|
+
% file can pass where many files break. Claims a fresh certificate proves
|
|
927
|
+
% at code scope are decided already. A grounding fact can carry a
|
|
928
|
+
% dimension_waiver reason and a dimension_waiver_approved_by human for a
|
|
929
|
+
% quantifier word that does not range over anything ("without any network
|
|
930
|
+
% access"). Only, any and some are left out: they mostly scope a
|
|
931
|
+
% condition ("only when", "before any write") rather than range over one. Status is not_required (no such claim), waived (each one has an
|
|
932
|
+
% approved waiver) or missing.
|
|
933
|
+
dimension_stage(ReqId, ScenarioTestStage, Context, Stage) :-
|
|
934
|
+
findall(Key-FactId,
|
|
935
|
+
( negative_path_link(Link),
|
|
936
|
+
kb_relationship(Link, ReqId, FactId),
|
|
937
|
+
fact_claim_key_or_id(FactId, Key)
|
|
938
|
+
),
|
|
939
|
+
ClaimFacts0),
|
|
940
|
+
sort(ClaimFacts0, ClaimFacts),
|
|
941
|
+
pairs_keys(ClaimFacts, Keys0),
|
|
942
|
+
sort(Keys0, Keys),
|
|
943
|
+
certified_claim_keys(ReqId, ScenarioTestStage, Context, Certified),
|
|
944
|
+
findall(Claim,
|
|
945
|
+
( member(Key, Keys),
|
|
946
|
+
\+ memberchk(Key, Certified),
|
|
947
|
+
findall(FactId, member(Key-FactId, ClaimFacts), FactIds),
|
|
948
|
+
unrepresented_quantified_claim(Key, FactIds, Claim)
|
|
949
|
+
),
|
|
950
|
+
Claims),
|
|
951
|
+
partition(approved_dimension_waiver, Claims, Waived, Open),
|
|
952
|
+
findall(Key, (member(Claim, Open), get_dict(claimKey, Claim, Key)), OpenKeys),
|
|
953
|
+
pending_certified_claim_keys(ScenarioTestStage, Context, OpenKeys, Pending),
|
|
954
|
+
( Claims == []
|
|
955
|
+
-> Status = not_required
|
|
956
|
+
; Open == []
|
|
957
|
+
-> Status = waived
|
|
958
|
+
; Status = missing
|
|
959
|
+
),
|
|
960
|
+
Stage = _{
|
|
961
|
+
status: Status,
|
|
962
|
+
claims: Open,
|
|
963
|
+
waivedClaims: Waived,
|
|
964
|
+
pendingReproofClaims: Pending
|
|
965
|
+
}.
|
|
966
|
+
|
|
967
|
+
% A claim whose text quantifies and whose every grounding fact is a boolean
|
|
968
|
+
% property value or a ground predicate.
|
|
969
|
+
unrepresented_quantified_claim(Key, FactIds, Claim) :-
|
|
970
|
+
FactIds = [_|_],
|
|
971
|
+
forall(member(FactId, FactIds), dimensionless_fact(FactId)),
|
|
972
|
+
claim_text_of(FactIds, Text),
|
|
973
|
+
claim_quantifier(Text, Quantifier),
|
|
974
|
+
!,
|
|
975
|
+
quantifier_rewrites(Quantifier, Rewrites),
|
|
976
|
+
dimension_waiver(FactIds, Waiver),
|
|
977
|
+
atom_string(Quantifier, QuantifierText),
|
|
978
|
+
Claim = _{
|
|
979
|
+
claimKey: Key,
|
|
980
|
+
claimText: Text,
|
|
981
|
+
quantifier: QuantifierText,
|
|
982
|
+
factIds: FactIds,
|
|
983
|
+
rewrites: Rewrites,
|
|
984
|
+
waiver: Waiver
|
|
985
|
+
}.
|
|
986
|
+
|
|
987
|
+
dimensionless_fact(FactId) :-
|
|
988
|
+
kb_entity(FactId, fact, Props),
|
|
989
|
+
memberchk(fact_kind=RawKind, Props),
|
|
990
|
+
normalize_atom(RawKind, Kind),
|
|
991
|
+
( Kind == predicate
|
|
992
|
+
-> true
|
|
993
|
+
; Kind == property_value,
|
|
994
|
+
memberchk(value_type=RawType, Props),
|
|
995
|
+
normalize_atom(RawType, bool)
|
|
996
|
+
).
|
|
997
|
+
|
|
998
|
+
claim_text_of(FactIds, Text) :-
|
|
999
|
+
member(FactId, FactIds),
|
|
1000
|
+
kb_entity(FactId, fact, Props),
|
|
1001
|
+
memberchk(claim_text=Raw, Props),
|
|
1002
|
+
normalize_atom(Raw, Atom),
|
|
1003
|
+
Atom \== '',
|
|
1004
|
+
!,
|
|
1005
|
+
atom_string(Atom, Text).
|
|
1006
|
+
claim_text_of(FactIds, Text) :-
|
|
1007
|
+
member(FactId, FactIds),
|
|
1008
|
+
kb_entity(FactId, fact, Props),
|
|
1009
|
+
memberchk(title=Raw, Props),
|
|
1010
|
+
!,
|
|
1011
|
+
normalize_atom(Raw, Atom),
|
|
1012
|
+
atom_string(Atom, Text).
|
|
1013
|
+
|
|
1014
|
+
%% claim_quantifier(+Text, -Quantifier)
|
|
1015
|
+
% The first quantifier over a collection (each, every, all) or cardinality
|
|
1016
|
+
% bound (exactly, at least, at most, no more than) in Text.
|
|
1017
|
+
claim_quantifier(Text, Quantifier) :-
|
|
1018
|
+
string_lower(Text, Lower),
|
|
1019
|
+
split_string(Lower, " \t\n\r.,;:()[]\"'`/!?-", " \t\n\r.,;:()[]\"'`/!?-", Words0),
|
|
1020
|
+
exclude(==(""), Words0, Words),
|
|
1021
|
+
quantifier_in(Words, Quantifier).
|
|
1022
|
+
|
|
1023
|
+
quantifier_in(["no", "more", "than"|_], no_more_than) :- !.
|
|
1024
|
+
quantifier_in(["at", "least"|_], at_least) :- !.
|
|
1025
|
+
quantifier_in(["at", "most"|_], at_most) :- !.
|
|
1026
|
+
quantifier_in([Word|_], Quantifier) :-
|
|
1027
|
+
memberchk(Word, ["each", "every", "all", "exactly"]),
|
|
1028
|
+
!,
|
|
1029
|
+
atom_string(Quantifier, Word).
|
|
1030
|
+
quantifier_in([_|Rest], Quantifier) :-
|
|
1031
|
+
quantifier_in(Rest, Quantifier).
|
|
1032
|
+
|
|
1033
|
+
% The typed groundings that represent what a quantifier ranges over, most
|
|
1034
|
+
% likely first.
|
|
1035
|
+
quantifier_rewrites(Quantifier, ["bound"]) :-
|
|
1036
|
+
memberchk(Quantifier, [at_least, at_most, exactly, no_more_than]),
|
|
1037
|
+
!.
|
|
1038
|
+
quantifier_rewrites(_, ["rule", "count", "set"]).
|
|
1039
|
+
|
|
1040
|
+
%% dimension_waiver(+FactIds, -Waiver)
|
|
1041
|
+
% A grounding fact's dimension_waiver reason and the human who approved it
|
|
1042
|
+
% in dimension_waiver_approved_by. An approved waiver on any grounding fact
|
|
1043
|
+
% wins; otherwise a reason alone is reported as pending approval. Waiver is
|
|
1044
|
+
% _{} when none is recorded.
|
|
1045
|
+
dimension_waiver(FactIds, Waiver) :-
|
|
1046
|
+
findall(W, (member(FactId, FactIds), fact_dimension_waiver(FactId, W)), Waivers),
|
|
1047
|
+
( member(W, Waivers), get_dict(approvedBy, W, _)
|
|
1048
|
+
-> Waiver = W
|
|
1049
|
+
; Waivers = [W|_]
|
|
1050
|
+
-> Waiver = W
|
|
1051
|
+
; Waiver = _{}
|
|
1052
|
+
).
|
|
1053
|
+
|
|
1054
|
+
fact_dimension_waiver(FactId, Waiver) :-
|
|
1055
|
+
kb_entity(FactId, fact, Props),
|
|
1056
|
+
memberchk(dimension_waiver=RawReason, Props),
|
|
1057
|
+
normalize_atom(RawReason, Reason),
|
|
1058
|
+
Reason \== '',
|
|
1059
|
+
atom_string(Reason, ReasonText),
|
|
1060
|
+
( memberchk(dimension_waiver_approved_by=RawBy, Props),
|
|
1061
|
+
normalize_atom(RawBy, By),
|
|
1062
|
+
By \== ''
|
|
1063
|
+
-> atom_string(By, ByText),
|
|
1064
|
+
Waiver = _{reason: ReasonText, approvedBy: ByText}
|
|
1065
|
+
; Waiver = _{reason: ReasonText}
|
|
1066
|
+
).
|
|
1067
|
+
|
|
1068
|
+
approved_dimension_waiver(Claim) :-
|
|
1069
|
+
get_dict(approvedBy, Claim.waiver, _).
|
|
1070
|
+
|
|
1071
|
+
negative_path_link(requires_property).
|
|
1072
|
+
negative_path_link(requires_predicate).
|
|
1073
|
+
negative_path_link(requires_rule).
|
|
1074
|
+
|
|
1075
|
+
fact_claim_key_or_id(FactId, Key) :-
|
|
1076
|
+
( kb_entity(FactId, fact, Props),
|
|
1077
|
+
memberchk(claim_key=Raw, Props),
|
|
1078
|
+
normalize_atom(Raw, Key0),
|
|
1079
|
+
Key0 \== ''
|
|
1080
|
+
-> Key = Key0
|
|
1081
|
+
; Key = FactId
|
|
1082
|
+
).
|
|
1083
|
+
|
|
1084
|
+
% Claims of ReqId a passed receipt binds with a derived obligation outside
|
|
1085
|
+
% intent scope: a certificate decides them, both sides.
|
|
1086
|
+
certified_claim_keys(ReqId, ScenarioTestStage, Context, Keys) :-
|
|
1087
|
+
( get_dict(tests, ScenarioTestStage, Tests) -> true ; Tests = [] ),
|
|
1088
|
+
findall(Key,
|
|
1089
|
+
( member(TestId, Tests),
|
|
1090
|
+
test_receipt_evidence(Context, [unit, integration, end_to_end], TestId, Evidence),
|
|
1091
|
+
get_dict(state, Evidence, passed),
|
|
1092
|
+
get_dict(claimBindings, Evidence, Recorded),
|
|
1093
|
+
member(Entry, Recorded),
|
|
1094
|
+
inventory_entry_field(Entry, requirement_id, RawReq),
|
|
1095
|
+
normalize_atom(RawReq, ReqId),
|
|
1096
|
+
inventory_entry_field(Entry, claim_key, RawKey),
|
|
1097
|
+
normalize_atom(RawKey, Key),
|
|
1098
|
+
test_claim_binding(ReqId, Key, [TestId-Evidence], Binding),
|
|
1099
|
+
get_dict(binding, Binding, derived),
|
|
1100
|
+
\+ get_dict(scope, Binding, intent)
|
|
1101
|
+
),
|
|
1102
|
+
Keys0),
|
|
1103
|
+
sort(Keys0, Keys).
|
|
1104
|
+
|
|
719
1105
|
scenario_test_stage(ScenarioIds, Stage, ScenarioTests) :-
|
|
720
1106
|
maplist(scenario_test_obligation, ScenarioIds, Obligations),
|
|
721
1107
|
findall(TestId,
|
|
@@ -2016,15 +2402,15 @@ receipt_claim_fields(Receipt, Evidence0, Evidence) :-
|
|
|
2016
2402
|
claim_evidence(ReqId, Props, Inventory, ProofStatus, ScenarioTestStage, Context,
|
|
2017
2403
|
ClaimEvidence, ProvedTests, Rejections) :-
|
|
2018
2404
|
( get_dict(tests, ScenarioTestStage, Tests) -> true ; Tests = [] ),
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2405
|
+
% The bindings are read whatever the proof status, so a claim that is not
|
|
2406
|
+
% proved because the requirement is not tested end to end still shows the
|
|
2407
|
+
% binding each receipt holds, and a rejected binding its reason.
|
|
2408
|
+
findall(TestId-Evidence,
|
|
2409
|
+
(member(TestId, Tests),
|
|
2410
|
+
test_receipt_evidence(Context, [unit, integration, end_to_end], TestId, Evidence),
|
|
2411
|
+
get_dict(state, Evidence, passed),
|
|
2412
|
+
get_dict(claimBindings, Evidence, _)),
|
|
2413
|
+
BoundTests),
|
|
2028
2414
|
intent_claim_level(Props, IntentLevel),
|
|
2029
2415
|
maplist(claim_entry(ReqId, ProofStatus, IntentLevel, BoundTests), Inventory, Entries0),
|
|
2030
2416
|
exclude(==(none), Entries0, ClaimEvidence),
|
|
@@ -2072,17 +2458,25 @@ claim_entry(ReqId, ProofStatus, IntentLevel, BoundTests, InventoryEntry, Entry)
|
|
|
2072
2458
|
; Status \== modeled
|
|
2073
2459
|
-> Entry = Base.put(_{applicable: true, level: IntentLevel, bindings: [],
|
|
2074
2460
|
blocker: unresolved_semantic_proposition})
|
|
2075
|
-
; ProofStatus \== tested
|
|
2076
|
-
-> Entry = Base.put(_{applicable: true, level: IntentLevel, bindings: []})
|
|
2077
2461
|
; findall(Binding, test_claim_binding(ReqId, Key, BoundTests, Binding), Bindings0),
|
|
2078
2462
|
sort(Bindings0, Bindings),
|
|
2079
2463
|
include(binding_is(rejected), Bindings, RejectedBindings),
|
|
2080
2464
|
maplist(binding_rejection(Key), RejectedBindings, Rejections),
|
|
2081
|
-
(
|
|
2082
|
-
->
|
|
2083
|
-
|
|
2465
|
+
( ProofStatus \== tested
|
|
2466
|
+
-> % A derived binding proves nothing until the requirement is
|
|
2467
|
+
% tested end to end; the claim keeps the intent level and
|
|
2468
|
+
% says why.
|
|
2469
|
+
Entry1 = Base.put(_{applicable: true, level: IntentLevel, bindings: Bindings}),
|
|
2470
|
+
( member(B, Bindings), get_dict(binding, B, derived)
|
|
2471
|
+
-> Entry0 = Entry1.put(notProved, requirement_not_tested)
|
|
2472
|
+
; Entry0 = Entry1
|
|
2473
|
+
)
|
|
2474
|
+
; ( member(B, Bindings), get_dict(binding, B, derived), \+ get_dict(scope, B, intent)
|
|
2475
|
+
-> Level = proved
|
|
2476
|
+
; Level = tested
|
|
2477
|
+
),
|
|
2478
|
+
Entry0 = Base.put(_{applicable: true, level: Level, bindings: Bindings})
|
|
2084
2479
|
),
|
|
2085
|
-
Entry0 = Base.put(_{applicable: true, level: Level, bindings: Bindings}),
|
|
2086
2480
|
( Rejections == []
|
|
2087
2481
|
-> Entry = Entry0
|
|
2088
2482
|
; Entry = Entry0.put(rejections, Rejections)
|
|
@@ -2280,14 +2674,58 @@ freshness_gap(stale_proof_receipt).
|
|
|
2280
2674
|
freshness_gap(proof_contract_mismatch).
|
|
2281
2675
|
|
|
2282
2676
|
freshness_only_gaps(Gaps, Stages) :-
|
|
2283
|
-
|
|
2284
|
-
|
|
2677
|
+
% A stale certificate receipt of a unit-scope test is not a gap of its
|
|
2678
|
+
% own, so a stage that waits only on one counts as the freshness gap.
|
|
2679
|
+
( include(freshness_gap, Gaps, [_|_])
|
|
2680
|
+
-> true
|
|
2681
|
+
; negative_path_freshness_only(Stages)
|
|
2682
|
+
; dimension_freshness_only(Stages)
|
|
2683
|
+
; cases_freshness_only(Stages)
|
|
2684
|
+
),
|
|
2685
|
+
exclude(freshness_gap, Gaps, Rest0),
|
|
2686
|
+
( negative_path_freshness_only(Stages)
|
|
2687
|
+
-> exclude(==(happy_path_only), Rest0, Rest1)
|
|
2688
|
+
; Rest1 = Rest0
|
|
2689
|
+
),
|
|
2690
|
+
( dimension_freshness_only(Stages)
|
|
2691
|
+
-> exclude(==(dimension_unrepresented), Rest1, Rest2)
|
|
2692
|
+
; Rest2 = Rest1
|
|
2693
|
+
),
|
|
2694
|
+
( cases_freshness_only(Stages)
|
|
2695
|
+
-> exclude(==(uncovered_case), Rest2, Rest)
|
|
2696
|
+
; Rest = Rest2
|
|
2697
|
+
),
|
|
2285
2698
|
( Rest == []
|
|
2286
2699
|
-> true
|
|
2287
2700
|
; Rest == [missing_production_symbol_coverage],
|
|
2288
2701
|
production_coverage_freshness_only(Stages)
|
|
2289
2702
|
).
|
|
2290
2703
|
|
|
2704
|
+
% The failure side is open only because certificate receipts that prove
|
|
2705
|
+
% every open claim are stale.
|
|
2706
|
+
negative_path_freshness_only(Stages) :-
|
|
2707
|
+
get_dict(negativePath, Stages, Stage),
|
|
2708
|
+
Stage.status == missing,
|
|
2709
|
+
Stage.claims \== [],
|
|
2710
|
+
subtract(Stage.claims, Stage.pendingReproofClaims, []).
|
|
2711
|
+
|
|
2712
|
+
% Every open quantified claim waits only on a stale certificate receipt.
|
|
2713
|
+
dimension_freshness_only(Stages) :-
|
|
2714
|
+
get_dict(dimension, Stages, Stage),
|
|
2715
|
+
Stage.status == missing,
|
|
2716
|
+
findall(Key, (member(Claim, Stage.claims), get_dict(claimKey, Claim, Key)), Keys),
|
|
2717
|
+
Keys \== [],
|
|
2718
|
+
subtract(Keys, Stage.pendingReproofClaims, []).
|
|
2719
|
+
|
|
2720
|
+
% Every uncovered rejection case waits only on a stale certificate receipt.
|
|
2721
|
+
cases_freshness_only(Stages) :-
|
|
2722
|
+
get_dict(cases, Stages, Stage),
|
|
2723
|
+
Stage.status == missing,
|
|
2724
|
+
findall(Key, (member(Row, Stage.uncoveredCases), get_dict(claimKey, Row, Key)), Keys0),
|
|
2725
|
+
sort(Keys0, Keys),
|
|
2726
|
+
Keys \== [],
|
|
2727
|
+
subtract(Keys, Stage.pendingReproofClaims, []).
|
|
2728
|
+
|
|
2291
2729
|
production_coverage_freshness_only(Stages) :-
|
|
2292
2730
|
Stage = Stages.productionSymbols,
|
|
2293
2731
|
forall(member(SymbolId, Stage.uncoveredSymbols),
|
|
@@ -2351,12 +2789,16 @@ prop_text(Props, Key, Text) :-
|
|
|
2351
2789
|
normalize_atom(Raw, Text),
|
|
2352
2790
|
Text \== ''.
|
|
2353
2791
|
|
|
2792
|
+
% The origin is JSON text, read whole: normalize_atom/2 keeps only what
|
|
2793
|
+
% follows the last "/", which cut an origin.ref quoting a URL down to a
|
|
2794
|
+
% fragment of the JSON.
|
|
2354
2795
|
origin_field(Props, Key, Value) :-
|
|
2355
2796
|
memberchk(origin=Raw, Props),
|
|
2356
|
-
|
|
2797
|
+
kb:source_value_atom(Raw, Json),
|
|
2357
2798
|
catch(atom_json_dict(Json, Origin, [value_string_as(atom)]), _, fail),
|
|
2799
|
+
is_dict(Origin),
|
|
2358
2800
|
get_dict(Key, Origin, Value0),
|
|
2359
|
-
|
|
2801
|
+
kb:source_value_atom(Value0, Value),
|
|
2360
2802
|
Value \== ''.
|
|
2361
2803
|
|
|
2362
2804
|
stage_dict(Stages, Stage) :-
|
|
@@ -2413,6 +2855,9 @@ proof_gap_present(infeasible_scenario, Stages) :- Stages.scenarios.infeasibleSce
|
|
|
2413
2855
|
proof_gap_present(unknown_scenario_feasibility, Stages) :-
|
|
2414
2856
|
get_dict(unknownFeasibility, Stages.scenarios, Unknown),
|
|
2415
2857
|
Unknown \= [].
|
|
2858
|
+
proof_gap_present(happy_path_only, Stages) :- Stages.negativePath.status == missing.
|
|
2859
|
+
proof_gap_present(dimension_unrepresented, Stages) :- Stages.dimension.status == missing.
|
|
2860
|
+
proof_gap_present(uncovered_case, Stages) :- Stages.cases.status == missing.
|
|
2416
2861
|
proof_gap_present(missing_scenario_test, Stages) :- memberchk(Stages.scenarios.status, [passed, blocked]), Stages.scenarioTests.status == missing.
|
|
2417
2862
|
proof_gap_present(missing_passing_e2e, Stages) :- Stages.passingE2e.status == missing.
|
|
2418
2863
|
proof_gap_present(missing_proof_receipt, Stages) :- Stages.passingE2e.missingReceiptTests \= [].
|
|
@@ -2442,6 +2887,9 @@ gap_definition(contradiction_check_incomplete, 41, contradictions, "Complete log
|
|
|
2442
2887
|
gap_definition(unknown_scenario_feasibility, 49, scenarios, "Link the property values the success scenario relies on with assumes facts that a current requirement constrains, so its feasibility can be decided.").
|
|
2443
2888
|
gap_definition(missing_scenario, 50, scenarios, "Add a specified_by scenario for the requirement.").
|
|
2444
2889
|
gap_definition(infeasible_scenario, 49, scenarios, "Set expects: rejection on the scenario, correct its assumes fact, or record a human-approved exception requirement that exempts the forbidding requirement.").
|
|
2890
|
+
gap_definition(dimension_unrepresented, 47, dimension, "Ground the quantified claim on what varies: an integer count or bound, a set of members, a rule over a variable, or a state machine. If the quantifier word ranges over nothing, record a dimension_waiver on the grounding fact for a person to approve, and record their approval in the same pull request.").
|
|
2891
|
+
gap_definition(uncovered_case, 46, cases, "Cover each rejection case: add a scenario that assumes a value in the case's class (the case's assumption), expects rejection or error, is linked with specified_by and is verified by a passing end-to-end test. If a person accepts the cases as covered another way, record a case_waiver on the grounding fact for them to approve, and record their approval in the same pull request.").
|
|
2892
|
+
gap_definition(happy_path_only, 48, negative_path, "Add a scenario that expects rejection or error and assumes a value this requirement forbids, link it with specified_by, and verify it with a passing end-to-end test.").
|
|
2445
2893
|
gap_definition(missing_scenario_test, 51, scenario_tests, "Link the scenario to a test with verified_by or validates.").
|
|
2446
2894
|
gap_definition(missing_passing_e2e, 52, passing_e2e, "Record fresh passing end-to-end evidence on a scenario-backed test.").
|
|
2447
2895
|
gap_definition(missing_proof_receipt, 53, passing_e2e, "Run the configured proof integration through kibi prove and append its kibi.proof-receipt.v1 result for the scenario-backed proof-bearing test.").
|
package/src/rule_registry.pl
CHANGED
|
@@ -18,6 +18,7 @@ known_rule('must-priority-coverage').
|
|
|
18
18
|
known_rule('symbol-coverage').
|
|
19
19
|
known_rule('symbol-traceability').
|
|
20
20
|
known_rule('symbol-granularity').
|
|
21
|
+
known_rule('symbol-coordinates-missing').
|
|
21
22
|
known_rule('no-dangling-refs').
|
|
22
23
|
known_rule('source-relationship-parity').
|
|
23
24
|
known_rule('branch-store-not-compiled').
|
|
@@ -28,6 +29,7 @@ known_rule('deprecated-adr-no-successor').
|
|
|
28
29
|
known_rule('superseded-requirement-open').
|
|
29
30
|
known_rule('scenario-feasibility').
|
|
30
31
|
known_rule('scenario-feasibility-unknown').
|
|
32
|
+
known_rule('scenario-outcome-mismatch').
|
|
31
33
|
known_rule('exception-claim-keys').
|
|
32
34
|
known_rule('numeric-string-value').
|
|
33
35
|
known_rule('rule-key-arguments-missing').
|
|
@@ -76,6 +78,7 @@ rule_enforcement_class('must-priority-coverage', canonical).
|
|
|
76
78
|
rule_enforcement_class('symbol-coverage', canonical).
|
|
77
79
|
rule_enforcement_class('symbol-traceability', canonical).
|
|
78
80
|
rule_enforcement_class('symbol-granularity', canonical).
|
|
81
|
+
rule_enforcement_class('symbol-coordinates-missing', advisory).
|
|
79
82
|
rule_enforcement_class('no-dangling-refs', canonical).
|
|
80
83
|
rule_enforcement_class('source-relationship-parity', canonical).
|
|
81
84
|
rule_enforcement_class('branch-store-not-compiled', canonical).
|
|
@@ -86,6 +89,7 @@ rule_enforcement_class('deprecated-adr-no-successor', canonical).
|
|
|
86
89
|
rule_enforcement_class('superseded-requirement-open', canonical).
|
|
87
90
|
rule_enforcement_class('scenario-feasibility', canonical).
|
|
88
91
|
rule_enforcement_class('scenario-feasibility-unknown', advisory).
|
|
92
|
+
rule_enforcement_class('scenario-outcome-mismatch', canonical).
|
|
89
93
|
rule_enforcement_class('exception-claim-keys', canonical).
|
|
90
94
|
rule_enforcement_class('numeric-string-value', advisory).
|
|
91
95
|
rule_enforcement_class('rule-key-arguments-missing', advisory).
|
|
@@ -134,6 +138,7 @@ rule_implementation('must-priority-coverage', prolog).
|
|
|
134
138
|
rule_implementation('symbol-coverage', prolog).
|
|
135
139
|
rule_implementation('symbol-traceability', prolog).
|
|
136
140
|
rule_implementation('symbol-granularity', typescript).
|
|
141
|
+
rule_implementation('symbol-coordinates-missing', typescript).
|
|
137
142
|
rule_implementation('no-dangling-refs', prolog).
|
|
138
143
|
rule_implementation('source-relationship-parity', typescript).
|
|
139
144
|
rule_implementation('branch-store-not-compiled', typescript).
|
|
@@ -144,6 +149,7 @@ rule_implementation('deprecated-adr-no-successor', prolog).
|
|
|
144
149
|
rule_implementation('superseded-requirement-open', prolog).
|
|
145
150
|
rule_implementation('scenario-feasibility', prolog).
|
|
146
151
|
rule_implementation('scenario-feasibility-unknown', prolog).
|
|
152
|
+
rule_implementation('scenario-outcome-mismatch', prolog).
|
|
147
153
|
rule_implementation('exception-claim-keys', prolog).
|
|
148
154
|
rule_implementation('numeric-string-value', prolog).
|
|
149
155
|
rule_implementation('rule-key-arguments-missing', prolog).
|
|
@@ -198,6 +204,7 @@ rule_predicate('deprecated-adr-no-successor', check_deprecated_adrs).
|
|
|
198
204
|
rule_predicate('superseded-requirement-open', check_superseded_requirement_open).
|
|
199
205
|
rule_predicate('scenario-feasibility', check_scenario_feasibility).
|
|
200
206
|
rule_predicate('scenario-feasibility-unknown', check_scenario_feasibility_unknown).
|
|
207
|
+
rule_predicate('scenario-outcome-mismatch', check_scenario_outcome_mismatch).
|
|
201
208
|
rule_predicate('exception-claim-keys', check_exception_claim_keys).
|
|
202
209
|
rule_predicate('numeric-string-value', check_numeric_string_values).
|
|
203
210
|
rule_predicate('rule-key-arguments-missing', check_rule_key_arguments_missing).
|
|
@@ -235,6 +242,7 @@ rule_predicate_arity('deprecated-adr-no-successor', 1).
|
|
|
235
242
|
rule_predicate_arity('superseded-requirement-open', 1).
|
|
236
243
|
rule_predicate_arity('scenario-feasibility', 1).
|
|
237
244
|
rule_predicate_arity('scenario-feasibility-unknown', 1).
|
|
245
|
+
rule_predicate_arity('scenario-outcome-mismatch', 1).
|
|
238
246
|
rule_predicate_arity('exception-claim-keys', 1).
|
|
239
247
|
rule_predicate_arity('numeric-string-value', 1).
|
|
240
248
|
rule_predicate_arity('rule-key-arguments-missing', 1).
|
|
@@ -266,6 +274,7 @@ rule_description('must-priority-coverage', 'Every current must-priority requirem
|
|
|
266
274
|
rule_description('symbol-coverage', 'Production symbols need qualifying coverage via covered_by plus a canonical requirement/scenario test path').
|
|
267
275
|
rule_description('symbol-traceability', 'Production symbols must directly implement requirements for ownership; covered_by is coverage only and executable_for is test identity only').
|
|
268
276
|
rule_description('symbol-granularity', 'A symbol with traceability relationships must not link its source file coarsely while narrower behavioral symbols are available, unless it carries an allowed granularity_reason; the staged gate\'s symbol_granularity_violation evaluated KB-wide').
|
|
277
|
+
rule_description('symbol-coordinates-missing', 'Advisory: an active symbol names a source file that exists, but Kibi could not place it there (the declaration was not found, the file is unreadable to the analyzer, or a declared plugin or grammar pack is missing), so its link is checked against no code').
|
|
269
278
|
rule_description('no-dangling-refs', 'All relationship targets must exist in the KB').
|
|
270
279
|
rule_description('source-relationship-parity', 'Authored Markdown and relationship-shard edges must exactly match the compiled KB').
|
|
271
280
|
rule_description('branch-store-not-compiled', 'The current branch\'s KB store must be compiled from the authored .kb/ sources; a missing or empty store is reported once, naming kibi sync, instead of every store-backed rule reading an empty KB').
|
|
@@ -276,6 +285,7 @@ rule_description('deprecated-adr-no-successor', 'Deprecated ADRs must have a suc
|
|
|
276
285
|
rule_description('superseded-requirement-open', 'A requirement that another requirement supersedes must have status closed, and supersedes links between requirements must not form a cycle').
|
|
277
286
|
rule_description('scenario-feasibility', 'A scenario that expects success must not assume property values that cannot hold, alone or together, with what current requirements require through typed property values or rules, unless an approved exception requirement waives that requirement or the listed clauses').
|
|
278
287
|
rule_description('scenario-feasibility-unknown', 'Advisory: a scenario that expects success assumes nothing, assumes values that contradict each other, assumes a property value no governing requirement constrains comparably (type, unit or operator mismatch, or conflicting requirements), leaves a governing rule\'s conditions undecided, or conflicts only with constraints whose validity window may not cover it, so its feasibility cannot be decided').
|
|
288
|
+
rule_description('scenario-outcome-mismatch', 'A scenario that expects rejection or error must assume a value or situation a current requirement forbids: when every assumption is governed by current requirements and they permit all of them together, the knowledge base predicts success and the scenario exercises no failure side').
|
|
279
289
|
rule_description('exception-claim-keys', 'An exception requirement\'s exempts_claims must name claim keys of a requirement it exempts, and requires an exempts link').
|
|
280
290
|
rule_description('numeric-string-value', 'Advisory: a property value stores a numeric-looking string where the comparison is numeric (ordering operator, or an int/number fact on the same property); the finding names the fact and the typed value, without converting it').
|
|
281
291
|
rule_description('rule-key-arguments-missing', 'Advisory: an opposing rule pair stays unresolved only because a body predicate declares no key_arguments; the finding names the predicate and the key positions that would decide the pair').
|