kibi-core 0.26.1 → 0.28.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 +357 -17
- package/src/claim_obligations.pl +16 -1
- package/src/code_certificates.pl +4 -0
- package/src/discovery.pl +65 -23
- package/src/intervals.pl +35 -14
- package/src/kb.pl +288 -36
- package/src/kb_consistency.pl +15 -0
- package/src/logic_ir.pl +130 -161
- package/src/model_check.pl +54 -6
- package/src/requirement_proof.pl +535 -14
- package/src/rule_registry.pl +6 -0
- package/src/status.pl +16 -0
- package/src/units.pl +1 -3
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
|
|
|
@@ -54,7 +57,13 @@ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds,
|
|
|
54
57
|
(catch(parse_time(CheckedAtAtom, CheckedAtStamp), _, fail) -> true ; CheckedAtStamp = -1),
|
|
55
58
|
normalize_integer(MaxAgeSeconds, MaxAge),
|
|
56
59
|
normalize_atom(BindingMode, BindingModeAtom),
|
|
60
|
+
% Everything else in the context follows from the KB, so the proof memo
|
|
61
|
+
% key needs only the caller's parameters.
|
|
62
|
+
variant_sha1(params(Snapshot, CheckedAtAtom, MaxAge, BindingModeAtom, TestBindings), ParamsKey),
|
|
63
|
+
variant_sha1(findings(Contradictions, ContradictionWitnesses, UnsafeRules, UnverifiableRules), FindingsKey),
|
|
57
64
|
Context = _{
|
|
65
|
+
proofMemoKey: ParamsKey,
|
|
66
|
+
ladderMemoKey: FindingsKey,
|
|
58
67
|
contradictions: Contradictions,
|
|
59
68
|
contradictionWitnesses: ContradictionWitnesses,
|
|
60
69
|
unsafeRules: UnsafeRules,
|
|
@@ -68,35 +77,99 @@ requirement_proof_context(VerificationSnapshot, CheckedAt, MaxAgeSeconds,
|
|
|
68
77
|
}.
|
|
69
78
|
|
|
70
79
|
% implements REQ-kibi-conservative-requirement-proof
|
|
80
|
+
% A requirement's proof is kept per KB generation and proof parameters, so
|
|
81
|
+
% the evidence-floor rule, the coverage evidence and a coverage report in one
|
|
82
|
+
% check read each ladder once. Inside a transaction (what-if staging) the
|
|
83
|
+
% KB is not the generation's, so the proof is computed afresh.
|
|
84
|
+
requirement_proof(ReqId, ReqProps, Context, Proof) :-
|
|
85
|
+
( \+ rdf_db:rdf_active_transaction(_),
|
|
86
|
+
get_dict(proofMemoKey, Context, ParamsKey),
|
|
87
|
+
kb:index_verification_key(requirement_proof, IndexKey)
|
|
88
|
+
-> Key = key(IndexKey, ParamsKey),
|
|
89
|
+
( kb:kb_requirement_proof_memo(ReqId, Key, Proof0)
|
|
90
|
+
-> Proof = Proof0
|
|
91
|
+
; compute_requirement_proof(ReqId, ReqProps, Context, Proof),
|
|
92
|
+
( once(kb:kb_requirement_proof_memo(_, Stored, _)),
|
|
93
|
+
Stored \== Key
|
|
94
|
+
-> retractall(kb:kb_requirement_proof_memo(_, _, _))
|
|
95
|
+
; true
|
|
96
|
+
),
|
|
97
|
+
assertz(kb:kb_requirement_proof_memo(ReqId, Key, Proof))
|
|
98
|
+
)
|
|
99
|
+
; compute_requirement_proof(ReqId, ReqProps, Context, Proof)
|
|
100
|
+
).
|
|
101
|
+
|
|
102
|
+
% The stages before passing_e2e read the KB and the context's KB-derived
|
|
103
|
+
% findings (contradictions, unsafe and unverifiable rules) only, never the
|
|
104
|
+
% caller's snapshot, receipt bindings or checkedAt. A check passes a new
|
|
105
|
+
% checkedAt every run, so these stages are kept per KB generation and those
|
|
106
|
+
% findings apart from the full proof; inside a transaction they are computed
|
|
107
|
+
% afresh.
|
|
108
|
+
receipt_independent_stages(ReqId, ReqProps, Context, Ladder) :-
|
|
109
|
+
( \+ rdf_db:rdf_active_transaction(_),
|
|
110
|
+
get_dict(ladderMemoKey, Context, FindingsKey),
|
|
111
|
+
kb:index_verification_key(requirement_ladder, IndexKey)
|
|
112
|
+
-> variant_sha1(ReqProps, PropsKey),
|
|
113
|
+
Key = key(IndexKey, FindingsKey, PropsKey),
|
|
114
|
+
( kb:kb_requirement_ladder_memo(ReqId, Key, Ladder0)
|
|
115
|
+
-> Ladder = Ladder0
|
|
116
|
+
; compute_receipt_independent_stages(ReqId, ReqProps, Context, Ladder),
|
|
117
|
+
( once(kb:kb_requirement_ladder_memo(_, key(Stored, _, _), _)),
|
|
118
|
+
Stored \== IndexKey
|
|
119
|
+
-> retractall(kb:kb_requirement_ladder_memo(_, _, _))
|
|
120
|
+
; true
|
|
121
|
+
),
|
|
122
|
+
retractall(kb:kb_requirement_ladder_memo(ReqId, _, _)),
|
|
123
|
+
assertz(kb:kb_requirement_ladder_memo(ReqId, Key, Ladder))
|
|
124
|
+
)
|
|
125
|
+
; compute_receipt_independent_stages(ReqId, ReqProps, Context, Ladder)
|
|
126
|
+
).
|
|
127
|
+
|
|
128
|
+
compute_receipt_independent_stages(ReqId, ReqProps, Context,
|
|
129
|
+
ladder(SemanticStage, Inventory, LogicStage, ContradictionStage,
|
|
130
|
+
ScenarioStage, ScenarioTestStage, ScenarioTests, SymbolSets)) :-
|
|
131
|
+
semantic_inventory_stage(ReqProps, SemanticStage, Inventory),
|
|
132
|
+
logic_grounding_stage(ReqId, ReqProps, Inventory, Context, LogicStage),
|
|
133
|
+
contradiction_stage(ReqId, LogicStage.status, SemanticStage.status, Context, ContradictionStage),
|
|
134
|
+
scenario_stage(ReqId, ScenarioStage, ScenarioIds),
|
|
135
|
+
scenario_test_stage(ScenarioIds, ScenarioTestStage, ScenarioTests),
|
|
136
|
+
production_symbol_sets(ReqId, SymbolSets).
|
|
137
|
+
|
|
71
138
|
% Exemption first: a current requirement explicitly marked proof_exempt with a
|
|
72
139
|
% reason is intentionally outside E2E-proof scope and must never fall through
|
|
73
140
|
% to the ladder (which would report misleading receipt gaps).
|
|
74
|
-
|
|
141
|
+
compute_requirement_proof(ReqId, ReqProps, _Context, Proof) :-
|
|
75
142
|
kb:current_req(ReqId),
|
|
76
143
|
proof_exempt(ReqProps, Reason),
|
|
77
144
|
!,
|
|
78
145
|
exempt_requirement_proof(Reason, ReqProps, Proof).
|
|
79
|
-
|
|
146
|
+
compute_requirement_proof(ReqId, ReqProps, _Context, Proof) :-
|
|
80
147
|
\+ kb:current_req(ReqId),
|
|
81
148
|
!,
|
|
82
149
|
applicability_exclusion_reason(ReqId, Reason),
|
|
83
150
|
exempt_requirement_proof(Reason, ReqProps, Proof).
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
scenario_stage(ReqId, ScenarioStage, ScenarioIds),
|
|
89
|
-
scenario_test_stage(ScenarioIds, ScenarioTestStage, ScenarioTests),
|
|
151
|
+
compute_requirement_proof(ReqId, ReqProps, Context, Proof) :-
|
|
152
|
+
receipt_independent_stages(ReqId, ReqProps, Context,
|
|
153
|
+
ladder(SemanticStage, Inventory, LogicStage, ContradictionStage,
|
|
154
|
+
ScenarioStage, ScenarioTestStage, ScenarioTests, SymbolSets)),
|
|
90
155
|
passing_e2e_stage(ScenarioTestStage, Context, PassingE2eStage, PassingE2eTests),
|
|
91
156
|
executable_symbol_stage(PassingE2eTests, ExecutableStage, ExecutableSymbols),
|
|
92
|
-
|
|
157
|
+
production_symbol_stage_from_sets(ReqId, SymbolSets, PassingE2eTests, ScenarioTests,
|
|
158
|
+
PassingE2eStage.receiptEvidence, Context,
|
|
159
|
+
ProductionStage, ProductionSymbols),
|
|
93
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),
|
|
94
164
|
Stages = _{
|
|
95
165
|
semanticInventory: SemanticStage,
|
|
96
166
|
logicGrounding: LogicStage,
|
|
97
167
|
contradictions: ContradictionStage,
|
|
98
168
|
scenarios: ScenarioStage,
|
|
99
169
|
scenarioTests: ScenarioTestStage,
|
|
170
|
+
negativePath: NegativePathStage,
|
|
171
|
+
dimension: DimensionStage,
|
|
172
|
+
cases: CasesStage,
|
|
100
173
|
passingE2e: PassingE2eStage,
|
|
101
174
|
executableSymbols: ExecutableStage,
|
|
102
175
|
productionSymbols: ProductionStage,
|
|
@@ -624,6 +697,12 @@ scenario_stage(ReqId, Stage, ScenarioIds) :-
|
|
|
624
697
|
unknown_feasibility_reason(Reason, ReasonText)
|
|
625
698
|
),
|
|
626
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),
|
|
627
706
|
( (ScenarioIds = [] ; InvalidScenarioTargets \= [])
|
|
628
707
|
-> Status = missing
|
|
629
708
|
; InfeasibleScenarios \= []
|
|
@@ -638,6 +717,7 @@ scenario_stage(ReqId, Stage, ScenarioIds) :-
|
|
|
638
717
|
invalidScenarioTargets: InvalidScenarioTargets,
|
|
639
718
|
infeasibleScenarios: InfeasibleScenarios,
|
|
640
719
|
unknownFeasibility: UnknownFeasibility,
|
|
720
|
+
negativeScenarios: NegativeScenarios,
|
|
641
721
|
sources: Sources
|
|
642
722
|
}.
|
|
643
723
|
|
|
@@ -652,6 +732,376 @@ unknown_feasibility_reason(undetermined_validity(_), undetermined_validity).
|
|
|
652
732
|
existing_scenario(ScenarioId) :-
|
|
653
733
|
kb_entity(ScenarioId, scenario, _).
|
|
654
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
|
+
|
|
655
1105
|
scenario_test_stage(ScenarioIds, Stage, ScenarioTests) :-
|
|
656
1106
|
maplist(scenario_test_obligation, ScenarioIds, Obligations),
|
|
657
1107
|
findall(TestId,
|
|
@@ -1211,6 +1661,11 @@ canonical_json_value(Value, Json) :-
|
|
|
1211
1661
|
maplist(canonical_json_value, Value, Parts),
|
|
1212
1662
|
atomics_to_string(Parts, ",", Body),
|
|
1213
1663
|
format(string(Json), "[~s]", [Body]).
|
|
1664
|
+
% JSON true, false and null read back as atoms; json_write would quote them,
|
|
1665
|
+
% so the hash would differ from the one the CLI recorded at ingest.
|
|
1666
|
+
canonical_json_value(Value, Json) :-
|
|
1667
|
+
memberchk(Value, [true, false, null]), !,
|
|
1668
|
+
atom_string(Value, Json).
|
|
1214
1669
|
canonical_json_value(Value, Json) :-
|
|
1215
1670
|
with_output_to(string(Json), json_write(current_output, Value, [width(0)])).
|
|
1216
1671
|
|
|
@@ -1375,6 +1830,14 @@ production_symbol_stage(ReqId, PassingE2eTests, Stage, ProductionSymbols) :-
|
|
|
1375
1830
|
production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, [], Context, Stage, ProductionSymbols).
|
|
1376
1831
|
|
|
1377
1832
|
production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence, Context, Stage, ProductionSymbols) :-
|
|
1833
|
+
production_symbol_sets(ReqId, Sets),
|
|
1834
|
+
production_symbol_stage_from_sets(ReqId, Sets, PassingE2eTests, ScenarioTests,
|
|
1835
|
+
ReceiptEvidence, Context, Stage, ProductionSymbols).
|
|
1836
|
+
|
|
1837
|
+
% The implementing symbols of a requirement, split into structural
|
|
1838
|
+
% (type-shape with a unit-test contract) and production ones, with the
|
|
1839
|
+
% production symbols' coordinates: all read from the KB alone.
|
|
1840
|
+
production_symbol_sets(ReqId, symbol_sets(StructuralSymbols, ProductionSymbols, Coordinates)) :-
|
|
1378
1841
|
findall(SymbolId,
|
|
1379
1842
|
(kb_relationship(implements, SymbolId, ReqId),
|
|
1380
1843
|
kb_entity(SymbolId, symbol, _),
|
|
@@ -1383,13 +1846,17 @@ production_symbol_stage(ReqId, PassingE2eTests, ScenarioTests, ReceiptEvidence,
|
|
|
1383
1846
|
sort(Implementing0, ImplementingSymbols),
|
|
1384
1847
|
include(type_shape_symbol_with_structural_contract, ImplementingSymbols, StructuralSymbols),
|
|
1385
1848
|
exclude(type_shape_symbol_with_structural_contract, ImplementingSymbols, ProductionSymbols),
|
|
1849
|
+
maplist(symbol_coordinate_ref, ProductionSymbols, Coordinates).
|
|
1850
|
+
|
|
1851
|
+
production_symbol_stage_from_sets(ReqId, symbol_sets(StructuralSymbols, ProductionSymbols, Coordinates),
|
|
1852
|
+
PassingE2eTests, ScenarioTests, ReceiptEvidence, Context,
|
|
1853
|
+
Stage, ProductionSymbols) :-
|
|
1386
1854
|
include(symbol_not_covered_by_tests(PassingE2eTests), ProductionSymbols, UncoveredProduction),
|
|
1387
1855
|
structural_unit_receipts(StructuralSymbols, Context, UnitReceipts),
|
|
1388
1856
|
exclude(structural_symbol_covered(PassingE2eTests, UnitReceipts), StructuralSymbols, UncoveredStructural),
|
|
1389
1857
|
append(UncoveredProduction, UncoveredStructural, UncoveredSymbols),
|
|
1390
1858
|
production_stage_status(ProductionSymbols, StructuralSymbols, PassingE2eTests,
|
|
1391
1859
|
UncoveredProduction, UncoveredStructural, Status, StatusReason),
|
|
1392
|
-
maplist(symbol_coordinate_ref, ProductionSymbols, Coordinates),
|
|
1393
1860
|
production_coverage_explanations(
|
|
1394
1861
|
ReqId, ProductionSymbols, StructuralSymbols, UncoveredSymbols,
|
|
1395
1862
|
PassingE2eTests, ScenarioTests, ReceiptEvidence, UnitReceipts-Context, Explanations),
|
|
@@ -2199,14 +2666,58 @@ freshness_gap(stale_proof_receipt).
|
|
|
2199
2666
|
freshness_gap(proof_contract_mismatch).
|
|
2200
2667
|
|
|
2201
2668
|
freshness_only_gaps(Gaps, Stages) :-
|
|
2202
|
-
|
|
2203
|
-
|
|
2669
|
+
% A stale certificate receipt of a unit-scope test is not a gap of its
|
|
2670
|
+
% own, so a stage that waits only on one counts as the freshness gap.
|
|
2671
|
+
( include(freshness_gap, Gaps, [_|_])
|
|
2672
|
+
-> true
|
|
2673
|
+
; negative_path_freshness_only(Stages)
|
|
2674
|
+
; dimension_freshness_only(Stages)
|
|
2675
|
+
; cases_freshness_only(Stages)
|
|
2676
|
+
),
|
|
2677
|
+
exclude(freshness_gap, Gaps, Rest0),
|
|
2678
|
+
( negative_path_freshness_only(Stages)
|
|
2679
|
+
-> exclude(==(happy_path_only), Rest0, Rest1)
|
|
2680
|
+
; Rest1 = Rest0
|
|
2681
|
+
),
|
|
2682
|
+
( dimension_freshness_only(Stages)
|
|
2683
|
+
-> exclude(==(dimension_unrepresented), Rest1, Rest2)
|
|
2684
|
+
; Rest2 = Rest1
|
|
2685
|
+
),
|
|
2686
|
+
( cases_freshness_only(Stages)
|
|
2687
|
+
-> exclude(==(uncovered_case), Rest2, Rest)
|
|
2688
|
+
; Rest = Rest2
|
|
2689
|
+
),
|
|
2204
2690
|
( Rest == []
|
|
2205
2691
|
-> true
|
|
2206
2692
|
; Rest == [missing_production_symbol_coverage],
|
|
2207
2693
|
production_coverage_freshness_only(Stages)
|
|
2208
2694
|
).
|
|
2209
2695
|
|
|
2696
|
+
% The failure side is open only because certificate receipts that prove
|
|
2697
|
+
% every open claim are stale.
|
|
2698
|
+
negative_path_freshness_only(Stages) :-
|
|
2699
|
+
get_dict(negativePath, Stages, Stage),
|
|
2700
|
+
Stage.status == missing,
|
|
2701
|
+
Stage.claims \== [],
|
|
2702
|
+
subtract(Stage.claims, Stage.pendingReproofClaims, []).
|
|
2703
|
+
|
|
2704
|
+
% Every open quantified claim waits only on a stale certificate receipt.
|
|
2705
|
+
dimension_freshness_only(Stages) :-
|
|
2706
|
+
get_dict(dimension, Stages, Stage),
|
|
2707
|
+
Stage.status == missing,
|
|
2708
|
+
findall(Key, (member(Claim, Stage.claims), get_dict(claimKey, Claim, Key)), Keys),
|
|
2709
|
+
Keys \== [],
|
|
2710
|
+
subtract(Keys, Stage.pendingReproofClaims, []).
|
|
2711
|
+
|
|
2712
|
+
% Every uncovered rejection case waits only on a stale certificate receipt.
|
|
2713
|
+
cases_freshness_only(Stages) :-
|
|
2714
|
+
get_dict(cases, Stages, Stage),
|
|
2715
|
+
Stage.status == missing,
|
|
2716
|
+
findall(Key, (member(Row, Stage.uncoveredCases), get_dict(claimKey, Row, Key)), Keys0),
|
|
2717
|
+
sort(Keys0, Keys),
|
|
2718
|
+
Keys \== [],
|
|
2719
|
+
subtract(Keys, Stage.pendingReproofClaims, []).
|
|
2720
|
+
|
|
2210
2721
|
production_coverage_freshness_only(Stages) :-
|
|
2211
2722
|
Stage = Stages.productionSymbols,
|
|
2212
2723
|
forall(member(SymbolId, Stage.uncoveredSymbols),
|
|
@@ -2270,12 +2781,16 @@ prop_text(Props, Key, Text) :-
|
|
|
2270
2781
|
normalize_atom(Raw, Text),
|
|
2271
2782
|
Text \== ''.
|
|
2272
2783
|
|
|
2784
|
+
% The origin is JSON text, read whole: normalize_atom/2 keeps only what
|
|
2785
|
+
% follows the last "/", which cut an origin.ref quoting a URL down to a
|
|
2786
|
+
% fragment of the JSON.
|
|
2273
2787
|
origin_field(Props, Key, Value) :-
|
|
2274
2788
|
memberchk(origin=Raw, Props),
|
|
2275
|
-
|
|
2789
|
+
kb:source_value_atom(Raw, Json),
|
|
2276
2790
|
catch(atom_json_dict(Json, Origin, [value_string_as(atom)]), _, fail),
|
|
2791
|
+
is_dict(Origin),
|
|
2277
2792
|
get_dict(Key, Origin, Value0),
|
|
2278
|
-
|
|
2793
|
+
kb:source_value_atom(Value0, Value),
|
|
2279
2794
|
Value \== ''.
|
|
2280
2795
|
|
|
2281
2796
|
stage_dict(Stages, Stage) :-
|
|
@@ -2332,6 +2847,9 @@ proof_gap_present(infeasible_scenario, Stages) :- Stages.scenarios.infeasibleSce
|
|
|
2332
2847
|
proof_gap_present(unknown_scenario_feasibility, Stages) :-
|
|
2333
2848
|
get_dict(unknownFeasibility, Stages.scenarios, Unknown),
|
|
2334
2849
|
Unknown \= [].
|
|
2850
|
+
proof_gap_present(happy_path_only, Stages) :- Stages.negativePath.status == missing.
|
|
2851
|
+
proof_gap_present(dimension_unrepresented, Stages) :- Stages.dimension.status == missing.
|
|
2852
|
+
proof_gap_present(uncovered_case, Stages) :- Stages.cases.status == missing.
|
|
2335
2853
|
proof_gap_present(missing_scenario_test, Stages) :- memberchk(Stages.scenarios.status, [passed, blocked]), Stages.scenarioTests.status == missing.
|
|
2336
2854
|
proof_gap_present(missing_passing_e2e, Stages) :- Stages.passingE2e.status == missing.
|
|
2337
2855
|
proof_gap_present(missing_proof_receipt, Stages) :- Stages.passingE2e.missingReceiptTests \= [].
|
|
@@ -2361,6 +2879,9 @@ gap_definition(contradiction_check_incomplete, 41, contradictions, "Complete log
|
|
|
2361
2879
|
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.").
|
|
2362
2880
|
gap_definition(missing_scenario, 50, scenarios, "Add a specified_by scenario for the requirement.").
|
|
2363
2881
|
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.").
|
|
2882
|
+
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.").
|
|
2883
|
+
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.").
|
|
2884
|
+
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.").
|
|
2364
2885
|
gap_definition(missing_scenario_test, 51, scenario_tests, "Link the scenario to a test with verified_by or validates.").
|
|
2365
2886
|
gap_definition(missing_passing_e2e, 52, passing_e2e, "Record fresh passing end-to-end evidence on a scenario-backed test.").
|
|
2366
2887
|
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.").
|